Railsはデフォルトでは一つのアプリケーションに対して一つのデータベースが対になってますが、サービスを運用している過程で他のサービスで使われているDBに跨った実装をしたくなります(APIを生やせばいいじゃんというツッコミは無しで)。
今回はそんな場合にどうRailsで対応するかについてまとめてみました。
どうやるか
以下ではデフォルトのDBをMainDB、他のサービスで使われているDBをSubDBとし、
database.ymlに定義された設定は下記とします。
default: &default
adapter: mysql2
encoding: utf8
pool: 5
development:
<<: *default
host: hoge
database: hoge
username: hoge
password: hoge
development_sub:
<<: *default
host: fuga
database: fuga
username: fuga
password: fuga
test:
<<: *default
host: foo
database: foo
username: foo
password: foo
test_sub:
<<: *default
host: bar
database: bar
username: bar
password: bar
~production, stagingも同様
DBの生成
SubDBからdumpしたsqlテーブル定義ファイルをdbディレクトリの下に置きます。
rails_app
├─ app
├─ db ── migrate
│ ├──── schema.rb
│ ├──── seeds.rb
│ ├──── sub_db.sql
そしてlib/tasks
の下にsb_db.sqlを実行するためのrakeタスクを設定します。
namespace :sub do
task :set_sub_config do
require 'active_record'
@config = YAML.load_file(Rails.root.join('config', 'database.yml'))
@structure_path = Rails.root.join('db', 'sub_db.sql')
@db = ActiveRecord::Tasks::MySQLDatabaseTasks.new(@config["development_sub"])
end
namespace :db do
task create: :set_sub_config do
@db.create
end
task structure_load: :set_sub_config do
@db.structure_load(@structure_path, nil)
end
task drop: :set_sub_config do
@db.drop
end
end
end
set_sub_config
は何をしているかというと、active_recordにdatabse.ymlの中の必要な設定を渡す操作をしています。set_sub_config
にdevelopmentかtestといった引数を渡して@config["#{arg}_sub"]
といった分岐処理を書いてもいいかもしれません。
active-recordに設定を渡したあとは、db
の名前空間でデータベースの操作をします。
rails sub:db:create
でSubDBのデータベースを作成し、
rails sub:db:structure_load
でテーブルを作成し、
rails sub:db:drop
でデータベースを削除します。
今回はMySQLが接続先として想定しているためMySQLDatabaseTasksからDB操作メソッドを呼んでいますが、PostgreSQLが接続先の場合は、PostgreSQLDatabaseTasksを指定してください。
modelとの連携
class Sub < ActiveRecord::Base
self.abstract_class = true
establish_connection("#{Rails.env}_sub".to_sym)
end
class Sub::Account < Sub
end
modelに接続先DBをSubDBに切り替えたクラスSubを作り、接続先がSubDBのmodelは全てこのSubを継承するようにして作成します。
テスト環境のための設定
主にFactoryGirlとDatabaseCleanerについての設定です。
MainDBにつなぐAccount
モデルと、SubDBにつなぐSub::Account
モデルがあるとします。
FactoryGirl.define do
factory :account do
end
end
FactoryGirl.define do
factory :sub_account, class: Sub::Account do
end
end
通常はクラス名=ファクトリ名
なのでaccountモデルに対応するファクトリ名は:account
となるのですが、上記のようにSubDBにつなぐ側には明示的にclass名を指定してあげてください。
また、SubDB側にマスターデータがある場合、毎テスト毎にマスターデータを再構築するのは効率が悪いので、DatabaseCleanerの操作をスキップする必要があります。
以下のコードははSubDBのaccountsテーブルにマスターデータがあるという想定のもとでDatabaseCleanerの設定をしています。
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
DatabaseCleaner[:active_record, connection: :sub_test].strategy =
:truncation, { except: [:accounts] }
FactoryGirl.reload
end
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run
end
end
まとめ
開発環境で気軽にDBを再構築できるようになったり、E2Eでテスト管理できるようになり、開発上の負担が大変減りました。
また、こちらの方々のように
といった方法も検討して良いかもしれません(自分の場合は他サービスのDBのほんの一部のみ使うことを想定していたため大味すぎると判断し見送りました)。