はじめに
Railsで複数データベースを利用するにあたり、ハマりポイントがいくつかあったので忘備録として残しておきます。
対象読者
- Railsで複数データベースを管理したい
- 複数データベースを使用する場合のモデルの取り扱い方法を知りたい。クラス名がコンフリクトするのを回避したい
- データベース単位でスキーマファイルを管理したい
- データベース単位でmigrateやrollbackをしたい
- 既に別リポジトリで管理しているデータベースに接続できるようにしたい。(が、migration管理はしたくない)
Railsで複数データベースを扱う記事は公式ドキュメント含め多数見つかりましたが、上記を全て解決する記事がなかったので今回まとめることにしました。
今回やること
1リポジトリで3つのデータベースに接続。
わかりやすくするため、データベース名はcat, dog, birdとします。
cat: 別リポジトリで管理。migrateファイルは使用しない。
dog: 当リポジトリで管理
bird: 当リポジトリで管理
設定手順とコマンド実行事例
それでは具体的に、
- 複数データベースに接続するための設定
- モデルとデータベースの紐付け
- モデルのディレクトリ構成例
- データベースを操作するコマンド実行例
について説明していきます。
複数データベースに接続するための設定
初めに、config/database.ymlにて、複数データベースに接続するための設定をします。
defaultであれば環境名のネストがあるだけですが、複数データベースを使う場合、さらにデータベース名でネストさせます。
default: &default
adapter: postgresql
encoding: utf8
url: <%= ENV['DATABASE_URL'] %>
pool: 5
development:
cat:
<<: *default
database: cat_development
database_tasks: false
dog:
<<: *default
database: dog_development
migrations_paths: db/migrate_dog
bird:
<<: *default
database: bird_development
migrations_paths: db/migrate_bird
test:
cat:
<<: *default
database: cat_test
database_tasks: false
dog:
<<: *default
database: dog_test
migrations_paths: db/migrate_dog
bird:
<<: *default
database: bird_test
migrations_paths: db/migrate_cat
staging:
cat:
<<: *default
database: cat_staging
database_tasks: false
dog:
<<: *default
database: dog_staging
migrations_paths: db/migrate_dog
bird:
<<: *default
database: bird_staging
migrations_paths: db/migrate_bird
production:
cat:
<<: *default
database: cat_production
database_tasks: false
dog:
<<: *default
database: dog_production
migrations_paths: db/migrate_dog
bird:
<<: *default
database: bird_production
migrations_paths: db/migrate_bird
-
migration管理したくない場合、
database_tasks: false
をつけるのがポイントです。(参考) -
各データベースのmigrationファイルを管理するディレクトリは
migration_paths
で設定します。
今回、dogとbirdをmigration管理するので、以下のようなディレトリ構成としました。
- db
- migrate_dog
- migrate_bird
モデルとデータベースの紐付け
続いて、モデルとデータベースを紐付けます。
今回は、catデータベースをデフォルトのデータベースとして使うため、ApplicationRecordクラスの接続先をcatに指定します。
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
connects_to database: { writing: :cat, reading: :cat }
end
これで、モデルはdefaultでcatデータベースを参照するようになりました。
次に、dogとbirdについても設定していきますが、ここからひと工夫が必要です。
データベースに対応するモデルを名前空間で区別するためのディレクトリ構成
少し寄り道をして、複数データベースへの接続をモデルでどのように表現できるか考えてみます。
データベース間でテーブル名のコンフリクトを避けるべく、以下のように扱えると理想です。
Dog::User.create(name: 'わんちゃん')
Bird::User.crate(name: '鳥ちゃん')
実現のため、moduleを用いて名前空間を作ります。
以下のように、models下にデータベース用のディレクトリを作成し、
- app
- models
- dog
- bird
データベース名のディレクトリ直下にのApplicationRecordもどきのBaseクラスを作成します。
- app
- models
- dog
- base.rb
- bird
- base.rb
class Dog::Base < ActiveRecord::Base
self.abstract_class = true
connects_to database: { writing: :dog, reading: :dog }
end
class Bird::Base < ActiveRecord::Base
self.abstract_class = true
connects_to database: { writing: :bird, reading: :bird }
end
あとは、各Userクラスで上記を継承すればOKです。
class Dog::User < Dog::Base
end
class Bird::User < Bird::Base
end
※ 便宜上、user.rb
のコードを書いてますが、上記ファイルは後のrails g model
コマンドにより自動で生成されます。
これで、データベースに対応するモデルを名前空間で区別できるようになりました。
データベースを操作するコマンド実行例
それでは実際に各データベースにusersテーブル(モデル)を作成していきます。
--database
オプションでデータベースの指定が可能です。
## catデータベースにユーザーを作成
rails g model User name:string --database cat
## dogデータベースにユーザーを作成
rails g model Dog::User name:string --database dog
## birdデータベースにユーザーを作成
rails g model Bird::User name:string --database bird
- 名前空間をつけることで、各データベース用に作成したmodels下のディレクトリにモデルが生成されます。
- その場合、migrationファイルのテーブル名が、
dog_users
のようにデータベース名が接頭辞についてしまいます。気に入らない場合はmigrationファイルを編集して、テーブル名をusers
にしてください。
class CreateDogUsers < ActiveRecord::Migration[6.1]
def change
create_table :dog_users do |t| ## ← "users"に変更
t.string :name
t.timestamps
end
end
end
- catデータベースはデフォルトのモデルを使用するため、名前空間は必要ありません。(※ もちろん、明示的にmodels/catディレクトリを作成すれば、
Cat::User
のように扱えます。)
上記を実行すると、models/DB名/user.rb
がそれぞれ作成されます。
加えて、Railsが親切に各データベース用のApplicationRecordを作ってくれているはずです。
class DogRecord < ApplicationRecord
self.abstract_class = true
connects_to database: { writing: :dog }
end
models/DB名/user.rb
を見てみると、上記クラスを継承しています。
class Dog::User < DogRecord
end
Railsがよしなに生成してくれたXXXRecordクラスを継承しても大丈夫ですが、私は同じ階層にbaseクラスを定義するのが好みなので、Dog::Baseを継承させました。(完全に好みの問題です)
class Dog::User < Dog::Base
end
データベース単位でmigrationの実行
いよいよmigrationを実行します。(DBが存在している前提です。)
rails db:migrate:[DB名]
とすることでデータベース単位でmigrationを実行できます。
rails db:migrate:cat
もちろん、rollbackもできます。
rails db:rollback:cat
※ rollback:[DB名]
はRails6.1.0からサポートされました。私は当初、6.0.3
で動かしており、rollbackだけデータベース指定できない沼にハマりました。(参考)
データベース単位でのスキーマファイル管理
migrationを実行すると、自動で[DB名]_schema.rbというファイルがそれぞれ生成されます。非常に便利ですね。
- db
- cat_schema.rb
- dog_schema.rb
- bird_schema.rb
挙動確認
rails consoleより以下を実行したところ、無事に各データベースにuserレコードが作成されることを確認できました。
[1] pry(main)> Dog::User.create!(name: 'test')
TRANSACTION (0.4ms) BEGIN
Dog::User Create (6.7ms) INSERT INTO "users" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["name", "test"], ["created_at", "2022-12-20 07:27:27.997621"], ["updated_at", "2022-12-20 07:27:27.997621"]]
TRANSACTION (3.3ms) COMMIT
=> #<Dog::User:0x00007fa950501710 id: 1, name: "test", created_at: Tue, 20 Dec 2022 07:27:27.997621000 UTC +00:00, updated_at: Tue, 20 Dec 2022 07:27:27.997621000 UTC +00:00>
まとめ
以上により、
- データベース名を名前空間として区別しながらのモデル操作
- データベース単位でスキーマファイルを管理
- データベース単位でのmigrateやrollback
を実現した上で、複数データベースを扱えるようになりました。
この記事がRailsで複数データベースを管理したい方にとって役に立てれば幸いです。