16
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Rails7】複数DBに接続し、DB毎にschemaやmigrationを管理したい

Last updated at Posted at 2023-09-05

はじめに

はじめまして!現在プログラミングスクール 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のように指定します。)

初期段階のファイルの中身はこのようになっています。

config/database.yml
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ガイドに詳しく書かれています。

簡単に手順をまとめると下記のようになりそうです。

  1. 複数DBに接続するための設定
  2. モデルとDBの紐付け
  3. 複数DBのコマンド実行例
  4. DB単位でmigrationの実行

2つのデータベースに接続することを仮定し、DB名はそれぞれfish,meatとします。

1. 複数DBに接続するための設定

config/database.yml
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が作成されたことがわかります。
Image from Gyazo

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配下にそれぞれファイルが作成されます。

db/migrate_fish/xxxxxxxxxxxx_create_fish_foods.rb
class CreateFishFoods < ActiveRecord::Migration[7.0]
  def change
    create_table :fish_foods do |t|
      t.string :name

      t.timestamps
    end
  end
end

db/migrate_fish/xxxxxxxxxxxx_create_meat_foods.rb
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を継承したベースとなるモデルファイル付が作成されます。

app/models/meat_record.rb
class MeatRecord < ApplicationRecord
  self.abstract_class = true

  connects_to database: { writing: :meat, reading: :meat }
end
app/models/fish_record.rb
class FishRecord < ApplicationRecord
  self.abstract_class = true

  connects_to database: { writing: :fish, reading: :fish }
end

{ writing: :fish, reading: :fish }のハッシュで書き込みと読み込みの権限について指定しています。

self.abstract_class = trueの挙動については別途記事作成予定です。

また、Railsはこのベースを継承した形でモデルファイルを生成してくれます。

app/models/meat/food.rb
class Meat::Food < MeatRecord
end
app/models/fish/food.rb
class Fish::Food < FoodRecord
end

4.DB単位でmigrationの実行

DB名を末尾に指定することで簡単に指定できます。

migrate
rails db:migrate:fish
rollback
rails db:rollback:meat

statusコマンドでもDB単位で表示してくれます。

status
 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毎に自動で管理されます。

db/meat_schema.rb
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
db/fish_schema.rb
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

実際にデータを作成できていることも確認できます。

console
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周りを改めて学習できたので良い機会になりました!
最後まで読んでいただきありがとうございました!

16
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?