Rails fixtures with multiple databases - ruby-on-rails

I'm trying to add test fixtures to a rails (v 3.1.3) app that uses multiple databases.
The fixture should be applied only to the applications own test sqlite database:
test:
adapter: sqlite3
database: db/test.sqlite3
pool: 5
timeout: 5000
The 2 other databases are mysql and also have test instances called %dbname%_test and are already filled with test data.
After I added the fixtures all my test fail with "ActiveRecord::StatementInvalid: Mysql::Error: Unknown column" errors as rails is trying to apply the fixture to
one of the mysql databases.
Is there a way to specify which database a fixture is made for?

Fixtures-init code (in activerecord-3.2.6/lib/active_record/fixtures.rb) has a bug: in the "initialize" method, in the "else" on line 544, #connection needs to be initialized to #model_class.connection. (Without that, #connection is simply "connection" which points to the wrong DB.)

In database.yml, add a connection for each of the other databases for each environment:
other_one_test:
adapter: mysql
# ...
other_two_test:
# ...
other_one_development:
# ...
other_two_development:
# ...
In your models that use the other databases, connect to them using
establish_connection("other_one_test")
You'll probably want to create a Base class model for each different database connection and use that for all models connecting to that database:
class OtherOneBase < ActiveRecord::Base
establish_connection "other_one_#{Rails.env}"
end
class SomeModel < OtherOneBase
end
If you set up your models correctly, your fixtures will use the correct database.

Related

How to seperate Admin and User database in Rails

I have devise gem in my application. I've been playing around with making some tables etc. Now I want to separate the database for the admin and the database for the user. I know its one single database. But, I'm not sure how to get it going in Rails.
you can configure multiple databases in database.yml file
production:
primary:
database: my_primary_database
user: root
adapter: mysql
secondary:
database: my_secondary_database
user: secondary_root
adapter: mysql
migrations_paths: db/secondary_migrate
and then in your modal, you can mention which database to use
class AnimalsBase < ApplicationRecord
self.abstract_class = true
connects_to database: { writing: :secondary }
end
checkout this link for more detail https://edgeguides.rubyonrails.org/active_record_multiple_databases.html
**For rails 4.2 - 5 ** you can use this gem Multiverse
P.S: Rails 6 is coming with a more neat solution for this, a stable build for rails 6 is now available you can upgrade to newer version too.
Rails have some limitation in which connecting only single database is major one. So I used to switch database using configuration setup in database.yml where you will have development & admin_development, (test & production etc. also)
You can get following constants,
ADMINCONFIG = YAML.load_file("#{Rails.root}/config/database.yml")['admin_development']
DEVCONFIG = YAML.load_file("#{Rails.root}/config/database.yml")['development']
And later whenever you need, you can switch from one to another as per requirement (through controller),
ActiveRecord::Base.connection(ADMINCONFIG)

Rails Multi Tenancy seeding issue

I created a multi tenancy app with Apartment gem and rails 5. I successfully create a new tenant, but I want to seed it. When I run the seeds file it states that the seed has run for this new tenant (Seeding tenant_name tenant), but there's no data there, only on public schema.
I can see 2 schemas created on PostgreSQL db,the public and the new one, but it only populates public schema. Why?
Tried putting on seeds.rb:
Apartment::Tenant.switch!('tenant_name')
And:
if Apartment::Tenant.current == "tenant_name"...
But no good.
Anyone?
Thanks in advance!
Your approach is correct, but please make sure these:
Ensure schema_search_path in PG:
Example: database.yml should look like:
default: &default
adapter: postgresql
schema_search_path: 'public,shared_extensions'
encoding: unicode
pool: 5
prepared_statements: false
development:
<<: *default
database: your_development_db
2. For schema-specific data population, run statement inside tenant switch block:
In seed.rb, first create tenant, then switch in that tenant like this:
Apartment::Tenant.switch('tenant_name') do
# Do all stuff here inside this block
# User.create(user_attributes) will create use only inside `tenant_name` schema
end
Cheer!

Multiple databases in Rails

Can this be done? In a single application, that manages many projects with SQLite.
What I want is to have a different database for each project my app is managing.. so multiple copies of an identically structured database, but with different data in them. I'll be choosing which copy to use base on params on the URI.
This is done for 1. security.. I'm a newbe in this kind of programming and I don't want it to happen that for some reason while working on a Project another one gets corrupted.. 2. easy backup and archive of old projects
Rails by default is not designed for a multi-database architecture and, in most cases, it doesn't make sense at all.
But yes, you can use different databases and connections.
Here's some references:
ActiveRecord: Connection to multiple databases in different models
Multiple Database Connections in Ruby on Rails
Magic Multi-Connections
If you are able to control and configure each Rails instance, and you can afford wasting resources because of them being on standby, save yourself some trouble and just change the database.yml to modify the database connection used on every instance. If you are concerned about performance this approach won't cut it.
For models bound to a single unique table on only one database you can call establish_connection inside the model:
establish_connection "database_name_#{RAILS_ENV}"
As described here: http://apidock.com/rails/ActiveRecord/Base/establish_connection/class
You will have some models using tables from one database and other different models using tables from other databases.
If you have identical tables, common on different databases, and shared by a single model, ActiveRecord won't help you. Back in 2009 I required this on a project I was working on, using Rails 2.3.8. I had a database for each customer, and I named the databases with their IDs. So I created a method to change the connection inside ApplicationController:
def change_database database_id = params[:company_id]
return if database_id.blank?
configuration = ActiveRecord::Base.connection.instance_eval { #config }.clone
configuration[:database] = "database_name_#{database_id}_#{RAILS_ENV}"
MultipleDatabaseModel.establish_connection configuration
end
And added that method as a before_filter to all controllers:
before_filter :change_database
So for each action of each controller, when params[:company_id] is defined and set, it will change the database to the correct one.
To handle migrations I extended ActiveRecord::Migration, with a method that looks for all the customers and iterates a block with each ID:
class ActiveRecord::Migration
def self.using_databases *args
configuration = ActiveRecord::Base.connection.instance_eval { #config }
former_database = configuration[:database]
companies = args.blank? ? Company.all : Company.find(args)
companies.each do |company|
configuration[:database] = "database_name_#{company[:id]}_#{RAILS_ENV}"
ActiveRecord::Base.establish_connection configuration
yield self
end
configuration[:database] = former_database
ActiveRecord::Base.establish_connection configuration
end
end
Note that by doing this, it would be impossible for you to make queries within the same action from two different databases. You can call change_database again but it will get nasty when you try using methods that execute queries, from the objects no longer linked to the correct database. Also, it is obvious you won't be able to join tables that belong to different databases.
To handle this properly, ActiveRecord should be considerably extended. There should be a plugin by now to help you with this issue. A quick research gave me this one:
DB-Charmer: http://kovyrin.github.com/db-charmer/
I'm willing to try it. Let me know what works for you.
I got past this by adding this to the top of my models using the other database
class Customer < ActiveRecord::Base
ENV["RAILS_ENV"] == "development" ? host = 'devhost' : host = 'prodhost'
self.establish_connection(
:adapter => "mysql",
:host => "localhost",
:username => "myuser",
:password => "mypass",
:database => "somedatabase"
)
You should also check out this project called DB Charmer:
http://kovyrin.net/2009/11/03/db-charmer-activerecord-connection-magic-plugin/
DbCharmer is a simple yet powerful plugin for ActiveRecord that does a few things:
Allows you to easily manage AR models’ connections (switch_connection_to method)
Allows you to switch AR models’ default connections to a separate servers/databases
Allows you to easily choose where your query should go (on_* methods family)
Allows you to automatically send read queries to your slaves while masters would handle all the updates.
Adds multiple databases migrations to ActiveRecord
It's worth noting, in all these solutions you need to remember to close custom database connections. You will run out of connections and see weird request timeout issues otherwise.
An easy solution is to clear_active_connections! in an after_filter in your controller.
after_filter :close_custom_db_connection
def close_custom_db_connection
MyModelWithACustomDBConnection.clear_active_connections!
end
in your config/database.yml do something like this
default: &default
adapter: postgresql
encoding: unicode
pool: 5
development:
<<: *default
database: mysite_development
test:
<<: *default
database: mysite_test
production:
<<: *default
host: 10.0.1.55
database: mysite_production
username: postgres_user
password: <%= ENV['DATABASE_PASSWORD'] %>
db2_development:
<<: *default
database: db2_development
db2_test:
<<: *default
database: db2_test
db2_production:
<<: *default
host: 10.0.1.55
database: db2_production
username: postgres_user
password: <%= ENV['DATABASE_PASSWORD'] %>
then in your models you can reference db2 with
class Customers < ActiveRecord::Base
establish_connection "db2_#{Rails.env}".to_sym
end
What you've described in the question is multitenancy (identically structured databases with different data in each). The Apartment gem is great for this.
For the general question of multiple databases in Rails: ActiveRecord supports multiple databases, but Rails doesn’t provide a way to manage them. I recently created the Multiverse gem to address this.
The best solution I have found so far is this:
There are 3 database architectures that we can approach.
Single Database for Single Tenant
Separate Schema for Each Tenant
Shared Schema for Tenants
Note: they have certain pros and cons depends on your use case.
I got this from this Blog! Stands very helpful for me.
You can use the gem Apartment for rails
Video reference you may follow at Gorails for apartment
As of Rails 6, multiple databases are supported: https://guides.rubyonrails.org/active_record_multiple_databases.html#generators-and-migrations
Sorry for the late and obvious answer, but figured it's viable since it's supported now.

DB connection problem in production

I have a separate DB for one model in my application and in development
mode the connection is working properly, in production however it isn't.
production:
adapter: mysql
host: myhost
username: root
password:
database: production_db
users_production:
adapter: mysql
host: myhost
username: root
password:
database: other_db
The model that connects to the other database is called User but the
table it references in other_db is smf_users so my User.rb looks like
this:
class User < ActiveRecord::Base
establish_connection "users_#{RAILS_ENV}"
set_table_name "smf_users"
end
In production I'm getting this error:
Mysql::Error: Table 'production_db. smf_users' doesn't exist:
Note how it is trying to connect to the wrong database and so isn't
finding the correct table. As I say, this works in development mode.
Any suggestions?
I've found when using multiple databases that odd errors show up when doing associations. Any chance you've got another model out there which belongs_to :users and you're expecting it to Just Work? Otherwise, you have to look at caching -- it's easily possible that Rails is failing to cache the connection data for your extra database correctly.
Try:
establish_connection configurations[RAILS_ENV]["users_#{RAILS_ENV}"]
User.connection
establish_connection needs a hash of the connection information. This should return if from database.yml.
You might want to check your log for a message like the following:
"users_production database is not configured"
That message would be thrown by ActiveRecord::Base if it can't find the congifuration by string in database.yml.
You might find this useful: http://magicmodels.rubyforge.org/magic_multi_connections/

Rails RSpec with Multiple Databases

I run a Rails app, and we're in the process of splitting out our signup process to a separate app. The signup app has its own separate database (for CMS and collecting prospects), but it also needs to have access to the main database. This works really well using ActiveRecord::Base.establish_connection.
However, I'd like to be able to write some specs. The trouble is, how can I write specs/tests without clearing out my development database every time my tests run? If I go into the console in test mode, it's obvious the the test mode is hooked into the development database from my main app.
Here's what my database.yml file looks like:
development:
database: signup_dev
test:
database: signup_test
main_app_dev:
database: main_app_dev
main_app_test:
database: main_app_test
Based on this file, I'd like establish_connection to connect to connect to the database my_app_dev in development mode, and my_app_test in test mode. Any ideas?
We have a gem that is basically a collection of ActiveRecord models that connect to our legacy system. In our case we have all those models contained in a module from which all models related to the legacy database connects.
module Legacy
class Base < ActiveRecord::Base
establish_connection :legacy
end
class User < Base
end
end
With this setup it makes it really easy to switch out the database connection. If you really go for that automated detection you can put logic in your base class to determine which database to use:
module Legacy
class Base < ActiveRecord::Base
if Rails.env == 'test'
establish_connection :legacy_test
else
establish_connection :legacy
end
end
Or simply tell your module which connection to use in your spec helper:
# spec/spec_helper.rb
Legacy::Base.establish_connection(ActiveRecord::Base.configurations['legacy_test'])
Personally I would recommend the second option. Of course both solutions depend on namespaced models.
Peer
Ryan, we were also in the process of migrating from one datastore to another. We needed to develop against two databases and maintain separate migrations and fixtures for each.
I created a gem called Secondbase to help with this. Essentially, it allows you to manage two databases seamlessly in a single Rails app. Perhaps it will solve your issue as well: https://github.com/karledurante/secondbase
Here's what I came up with as a mixin:
# lib/establish_connection_to_master_database.rb
module EstablishConnectionToMasterDatabase
def establish_connection_to_master_database
case RAILS_ENV
when "development"
establish_connection :master_dev
when "test"
establish_connection :master_test
when "production"
establish_connection :master
end
end
end
ActiveRecord::Base.send(:extend, EstablishConnectionToMasterDatabase)
# models/subscription.rb
class Subscription < ActiveRecord::Base
establish_connection_to_master_database
end
# config/initializers/config.rb
require 'establish_connection_to_master_database'
In order for this to work with RSpec, this needs to be loaded in an initializer - apparently loading it in the environment file causes it to be loaded it too late, and it won't work.
We just used interpolation for this:
class ServiceModel < ActiveRecord::Base
establish_connection :"main_app_#{Rails.env}"
end
The funny :"main_app_" syntax makes a symbol from a string. This could be also written "main_app_#{Rails.env}".to_sym. In either case with Rails 4.1 this must be a symbol (under 3.2 we had just used a string).

Resources