複数のDBにまたがるRailsの運用方法まとめ



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](https://github.com/rails/rails/blob/15ef55efb591e5379486ccf53dd3e13f416564f6/activerecord/lib/active_record/tasks/mysql_database_tasks.rb)からDB操作メソッドを呼んでいますが、PostgreSQLが接続先の場合は、[PostgreSQLDatabaseTasks](https://github.com/rails/rails/blob/15ef55efb591e5379486ccf53dd3e13f416564f6/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb)を指定してください。

### 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を管理するmigrationを別リポジトリにして単独管理](http://qiita.com/fukumone/items/87b77c37c33773a97daf)
- [新しくdb用のディレクトリを切る](http://shepherdmaster.hateblo.jp/entry/2016/07/04/013055)
  

といった方法も検討して良いかもしれません（自分の場合は他サービスのDBのほんの一部のみ使うことを想定していたため大味すぎると判断し見送りました）。