はじめに
はじめまして!現在プログラミングスクール RUNTEQに通っています。
Railsの個人開発で複数DBで管理したい構成にしたかったので、その方法について練習しながらまとめてみました。
環境
- Ruby 3.2.2
- Ruby on Rails 7.0.7
- SQLite
対象読者
- Railsで複数データベースを管理したい
- DB単位でスキーマファイルを管理したい
- DB単位でmigrateやrollbackをしたい
database.ymlについて
DB切り替えの前に、RailsのDB管理について復習しておきます。
rails new
した段階で、config/database.yml
が生成されます。
database.yml
とはRailsがDBに接続するための情報を記載したファイルです。
rails new
でDBを指定しない場合、デフォルトではSQLiteが使われる仕様となっています。
(DBを変更したい場合は、rails new app_name -d postgresql
のように指定します。)
初期段階のファイルの中身はこのようになっています。
default: &default
adapter: sqlite3
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
development:
<<: *default
database: db/development.sqlite3
test:
<<: *default
database: db/test.sqlite3
production:
<<: *default
database: db/production.sqlite3
各コードの説明
default: &default
&はアンカーと呼ばれ&default
で指定しておくと、他の部分で*default
として呼び出すことができる
<<: *default
<<をインジェクトと呼び、アンカー指定した変数をマージすることができる。yml記号の一つ
#1と2は一緒
# パターン1
development:
<<: *default
database: db/development.sqlite3
# パターン2
development:
adapter: sqlite3
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
database: db/development.sqlite3
adapter: sqlite3
DBの種類
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
同時に接続を使い回すことができる上限数。今回であれば5
timeout: 5000
接続のタイムアウト時間
手順
基本的な手順はRailsガイドに詳しく書かれています。
- Active Record で複数のデータベース利用(https://railsguides.jp/active_record_multiple_databases.html)
簡単に手順をまとめると下記のようになりそうです。
- 複数DBに接続するための設定
- モデルとDBの紐付け
- 複数DBのコマンド実行例
- DB単位でmigrationの実行
2つのデータベースに接続することを仮定し、DB名はそれぞれfish,meatとします。
1. 複数DBに接続するための設定
default: &default
adapter: sqlite3
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
development:
fish:
<<: *default
database: fish_development
migrations_paths: db/migrate_fish
meat:
<<: *default
database: meat_development
migrations_paths: db/migrate_meat
test:
fish:
<<: *default
database: fish_test
migrations_paths: db/migrate_fish
meat:
<<: *default
database: meat_test
migrations_paths: db/migrate_meat
production:
fish:
<<: *default
database: fish_production
migrations_paths: db/migrate_fish
meat:
<<: *default
database: meat_production
migrations_paths: db/migrate_meat
DB毎にmigrationを管理したい場合は、migrations_pathsを用いてパスを指定する必要があります。
一方で、migrationの管理が必要ない場合はdatabase_tasks: falseと指定します。
rails db:create
を実行すると、複数のDBが作成されたことがわかります。
2.モデルとDBの紐付け
DBに対応するモデルを名前空間で区別するためのディレクトリ構成を考えます。
app/models配下にそれぞれディレクトリとクラスを生成する構成です。名前空間で区切ることによって、テーブル名の衝突を防ぎます。
- app
- models
- fish
- fish.rb
- meat
- meat.rb
3. 複数DBのコマンド実行例
## foodsモデルを作成
rails g model Fish::Food name:string --database fish
## foodsモデルを作成
rails g model Meat::Food name:string --database meat
上記コマンドを実行すると、migrations_paths: db/migrate_xxx配下にそれぞれファイルが作成されます。
class CreateFishFoods < ActiveRecord::Migration[7.0]
def change
create_table :fish_foods do |t|
t.string :name
t.timestamps
end
end
end
class CreateMeatFoods < ActiveRecord::Migration[7.0]
def change
create_table :meat_foods do |t|
t.string :name
t.timestamps
end
end
end
この時テーブル名がDB名_テーブル名となっている為、必要であればテーブル名に変更が必要です。またrails db:migrate
実行時にmodels配下に専用のmodule(テーブル名の接頭辞にDB名を付与するmodule)も作成されるので削除しておく必要があります。
モデル作成時にmodels配下にApplicationRecord
を継承したベースとなるモデルファイル付が作成されます。
class MeatRecord < ApplicationRecord
self.abstract_class = true
connects_to database: { writing: :meat, reading: :meat }
end
class FishRecord < ApplicationRecord
self.abstract_class = true
connects_to database: { writing: :fish, reading: :fish }
end
{ writing: :fish, reading: :fish }
のハッシュで書き込みと読み込みの権限について指定しています。
self.abstract_class = trueの挙動については別途記事作成予定です。
また、Railsはこのベースを継承した形でモデルファイルを生成してくれます。
class Meat::Food < MeatRecord
end
class Fish::Food < FoodRecord
end
4.DB単位でmigrationの実行
DB名を末尾に指定することで簡単に指定できます。
rails db:migrate:fish
rails db:rollback:meat
statusコマンドでもDB単位で表示してくれます。
❯ rails db:migrate:status
database: fish_development
Status Migration ID Migration Name
--------------------------------------------------
down 20230905080236 Create fish foods
database: meat_development
Status Migration ID Migration Name
--------------------------------------------------
down 20230905080245 Create meat foods
schemaもDB毎に自動で管理されます。
ActiveRecord::Schema[7.0].define(version: 2023_09_05_082528) do
create_table "foods", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
ActiveRecord::Schema[7.0].define(version: 2023_09_05_082528) do
create_table "foods", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
実際にデータを作成できていることも確認できます。
Loading development environment (Rails 7.0.7.2)
irb(main):001> Fish::Food.create(name: "tuna")
TRANSACTION (0.1ms) begin transaction
Fish::Food Create (1.7ms) INSERT INTO "foods" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "tuna"], ["created_at", "2023-09-05 08:37:43.464324"], ["updated_at", "2023-09-05 08:37:43.464324"]]
TRANSACTION (0.7ms) commit transaction
=>
#<Fish::Food:0x000000010fa1ce20
id: 1,
name: "tuna",
created_at: Tue, 05 Sep 2023 08:37:43.464324000 UTC +00:00,
updated_at: Tue, 05 Sep 2023 08:37:43.464324000 UTC +00:00>
irb(main):002> Meat::Food.create(name: "beef")
TRANSACTION (0.0ms) begin transaction
Meat::Food Create (1.6ms) INSERT INTO "foods" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "beef"], ["created_at", "2023-09-05 08:38:03.926502"], ["updated_at", "2023-09-05 08:38:03.926502"]]
TRANSACTION (0.9ms) commit transaction
=>
#<Meat::Food:0x0000000110a1cf50
id: 1,
name: "beef",
created_at: Tue, 05 Sep 2023 08:38:03.926502000 UTC +00:00,
updated_at: Tue, 05 Sep 2023 08:38:03.926502000 UTC +00:00>
まとめ
DB周りを改めて学習できたので良い機会になりました!
最後まで読んでいただきありがとうございました!