2017-07-26
Start by configuring your rails app for multiple DBs:
1 2 3 4 5 6 7 8 9 10 11 12 | default: &default adapter: sqlite3 pool: 5 timeout: 5000 production: { <<: *default, database: db/production.sqlite3 } development: { <<: *default, database: db/development.sqlite3 } # additional DB :cash: production: { <<: *default, database: db/production_cash.sqlite3 } development: { <<: *default, database: db/development_cash.sqlite3 } |
Share a common connection between desired models (in the example I use namespaced models1):
1 2 3 4 5 6 7 | # /app/models/cash/shared_base.rb class Cash::SharedBase < ActiveRecord::Base establish_connection configurations[:cash][Rails.env] self.abstract_class = true end class Cash::Account < Cash::SharedBase; ...; end # /app/models/cash/account.rb class Cash::Movement < Cash::SharedBase; ...; end # /app/models/cash/movement.rb |
Modify your migrations so they can use your custom connection and save the current schema version to the main DB:
1 2 3 4 5 | # /db/migrate/XXXXXXXXXXXXXXXX_create_cash_accounts.rb class CreateCashAccounts < ActiveRecord::Migration def connection; Cash::SharedBase.connection; end def change; ...; end end |
downside note: migration rollback will not work.
If you want to create a unique db/schema.rb dump, you can use this simple rake task:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | # /lib/tasks/db.rake namespace :db do desc 'Dump a unique schema for all databases' task 'schema:dump_all': :environment do def dump_conn(c) f = StringIO.new ActiveRecord::SchemaDumper.dump c, f # https://github.com/rails/rails/issues/3497#issuecomment-5419288 f.string.split("\n") end # dump all schemas in memory arb = ActiveRecord::Base schemas = [ dump_conn(arb.connection) ] schemas += arb.configurations.keys.reject{|k| k.is_a? String }. map{|db| dump_conn arb.establish_connection(arb.configurations[db][Rails.env]).connection } # join schemas lines = [] schemas.each_with_index do |schema, i| lines += schema.grep(/^ActiveRecord::Schema/) if lines.empty? # save header lines += schema.grep(/^\s/) # append rows lines << "\n # #{'-'*76}\n" if (i+1) < schemas.size # separator end lines << 'end' # close block File.open(Rails.root.join('db/schema.rb').to_s, 'w'){|f| f.puts lines.join("\n") } end # dump_all # ---------------------------------------------------------------------------- Rake::Task["db:schema:dump"].enhance do # hook into existing task Rake::Task["db:schema:dump_all"].invoke end end |
Enable ActiveRecord's query cache for the new connections:
1 2 3 4 5 6 7 8 9 10 11 12 | class ApplicationController < ActionController::Base around_action :cache_other_db_connections private def cache_other_db_connections Cash::SharedBase.connection.cache { yield } # if you have more connections make more around filters of just nest the calls: # M1.connection.cache{ M2.connection.cache { ... {yield} } } end end |
And last but not least, rails uses a transaction for each create/update so pay attention to multi-DB transactions as the ActiveRecord::Rollback exception does not propagate and thus it will not rollback the outer transaction if raised within an inner transaction (read Rails Multi-Database Best Practices Roundup for a more in depth explanation).
References:
- Migrations schema_migrations with multiple databases
- Using Rails Migration on different database than standard production or development
- Rails Multi-Database Best Practices Roundup
-
You can generate namespaced models with the standard rails generator:
rails g model NamespaceName::ModelName ...
. ↩