1
0

RailsでパスによってDBを切り替えれないか

Last updated at Posted at 2023-11-29

概要

Rails6からマルチデータベースを利用できるようになりました
リードレプリカ構成で負荷をうまく分散させたり、取得するモデル単位でデータベースのコネクションを切り替えることができるようになりました

同一のモデルを取得するけれど、パスによって取得するDBを切り替えることを試したので書き残します

今回試した内容の結論から書くと「concernで無理やり解決した」となります

どうやってDBを切り替えるのか

切り替えたいモデルが継承している抽象クラスにconnects_to databaseを記述すれば切り替えができます

下記のような切り替えたいモデルとしてCardがあったとき、それが継承しているApplicationRecordに対して
writingのDBとreadingのDBを指定すると、必要に応じて切り替わります

app/models/card.rb
class Card < ApplicationRecord
end
app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

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

もう少し手順を1つ1つ確認していくと下記のようなことが必要です

  1. database.ymlの設定
  2. 該当のモデル・テーブル
  3. 該当のモデルが継承している抽象クラスでのDB接続設定(↑で書いてある内容)

database.ymlの設定

database.ymlの設定としては下記の通りです(一部省略)
紛らわしいですが、default配下のdefaultの設定を利用するので今回はkanban_developmentのDBへデータの変更削除などを行いに行きます

config/database.yml
default:
  default:
    port: 3306
    database: kanban_development
  renewal:
    port: 3306
    database: board_development
    migrations_paths: db/board_migrate

該当のモデル・テーブル

これはそのままですが下記のモデルの作成とテーブルを作成する必要があります

app/models/card.rb
class Card < ApplicationRecord
end
db/schema.rb
ActiveRecord::Schema[7.1].define(version: 2023_11_29_171139) do
  create_table "cards", charset: "utf8mb4", force: :cascade do |t|
    t.string "title"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end
end

該当のモデルが継承している抽象クラスでのDB接続設定

↑でも書いていた通り、connects_to database:...の設定が必要です

app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

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

writing: :defaultwriting: :renewalと変更すると
接続先のdatabaseがkanban_developmentboard_developmentとなります

config/database.yml
default:
  default:
    port: 3306
    database: kanban_development
  renewal:
    port: 3306
    database: board_development
    migrations_paths: db/board_migrate

実際にパスで切り替えて行くにはどうしたか

  1. database.ymlの設定と各DBに対して同一のmigrationを作成する
  2. 異なる抽象クラスを用意する
  3. 動的にクラスを変更するためcontrollerに対してmoduleを定義

大きく分けてこの3ステップくらいです

サンプルコードはこちらに上げています

database.ymlの設定と各DBに対して同一のmigrationを作成する

database.ymlについては↑でも載せているものを利用します
kanban_developmentのmigraitonはdb/migrate配下に作成していき
board_developmentのmaigrationはmigrations_pathsで指定してる db/board_migrate配下へ作成します

config/database.yml
default:
  default:
    port: 3306
    database: kanban_development
  renewal:
    port: 3306
    database: board_development
    migrations_paths: db/board_migrate

異なる抽象クラスを用意する

接続先の切り替えはモデルが継承している抽象クラスで定義するので異なる抽象クラスとそれぞれを継承したモデルが必要です

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  connects_to database: { writing: :default, reading: :default }
end

class Card::Content < ApplicationRecord
  self.table_name = 'contents'
end
class RenewalRecord < ActiveRecord::Base
  self.abstract_class = true
  connects_to database: { writing: :renewal }
end

class Post::Content < RenewalRecord
  self.table_name = 'contents'
end

動的にクラスを変更するためcontrollerに対してmoduleを定義

moduleとしてモジュールを利用した側での名前を利用して動的にデータを取得するモジュールを作成しました

app/controllers/concerns/contents_controllerble.rb
module ContentsControllerble
  extend ActiveSupport::Concern

  included do
    def index
      @contents = content_model.all
    end

    private

    def content_model
      namespace = self.class.name.deconstantize
      "#{namespace}::Content".constantize
    end
  end
end

実際にこのモジュールを利用する側はincludeしているだけです

各種controller
class Post::ContentsController < ApplicationController
  include ContentsControllerble
end

class Card::ContentsController < ApplicationController
  include ContentsControllerble
end

処理の流れ

  • /post/contentsにアクセスがあった場合
    • Post::ContentsControllerのindexアクションが実行
    • 実態はモジュールにあり、Post::Contentモデルのデータを取得する
    • Post::ContentモデルはRenewalRecordを継承している
      • RenewalRecordでは接続先をrenewal_developmentとしている
    • renewal_developmentのcontentsテーブルのデータを取得してくる
  • /card/contentsにアクセスがあった場合
    • Card::ContentsControllerのindexアクションが実行
    • 実態はモジュールにあり、Card::Contentモデルのデータを取得する
    • Card::ContentモデルはApplicationRecordを継承している
      • ApplicationRecordでは接続先をkanban_developmentとしている
    • kanban_developmentのcontentsテーブルのデータを取得してくる

output.gif

まとめ

サンプルコードを見てもらうとわかりますが、結構モジュールを読み込むだけのファイルができます

冒頭に書いた通りconcernで解決したのであまり良い結果とは言えません...
結局同一のテーブル構造を実現させるなら水平シャーディングを利用すると複数のDBに対して同一のデータが保存できそうです

ただ、その場合は複数DBでテーブル構造を共有するので各DBごとのカスタマイズ性が落ちそう
水平シャーディングについても聞きかじった程度なので見ていきます

参照・引用

1
0
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
1
0