概要
つい昨日パスでアクセスするDBを変更する方法をまとめて書きました
結果的にcocernに共通処理として本来ControllerやModelが持つべき処理を切り出して、各々で読み込む対応をしましたが中身のないModelやControllerが大量に作成されることになりました
DBへ接続しに行くときのミドルウェアで接続先DBを変更できればめちゃいいじゃんと思いちょっと試してみました
サンプルコードはこちらです
実際にミドルウェアを書いて読み込ませる
アクセスがあったときのDBの切り替えは下記のように定義します
- /hoge/contentsでアクセスがあった場合
- defaultの設定を利用する(db名はkanban_development)
- /fuga/contentsでアクセスがあった場合
- renewalの設定を利用する(db名はboard_development)
- パスの第一階層がその他の場合は /hoge/contents と同じ挙動にさせます
今回はミドルウェアとしてDatabaseSwitcher
を定義し、その中でパスを見てデータベース名を切り替えています
class DatabaseSwitcher
def initialize(app)
@app = app
end
def call(env)
switch_database(env)
@app.call(env)
end
private
def switch_database(env)
request = Rack::Request.new(env)
db = case request.path
when %r{^/hoge}
:default
when %r{^/fuga}
:renewal
else
:default
end
ActiveRecord::Base.establish_connection(db)
end
end
Rackで渡されるenv
にはリクエスト情報が入っているものの、不必要なデータがあるので一度Rack::Requestになおしてからぱpathを抜き出しています
続いてアプリケーション設定として定義したミドルウェアを利用するよう設定を行います
~~~~
require_relative '../app/middleware/database_switcher'
module App
class Application < Rails::Application
~~~
~~~
config.middleware.use DatabaseSwitcher
middleware一覧を確認すると末尾に追加されていることが確認できます
もし、異なる場所にミドルウェアを入れたい場合はinsert_before
,insert_after
などを利用しましょう
$ docker compose exec web bundle exec rails middleware
...
use Rack::ConditionalGet
use Rack::ETag
use Rack::TempfileReaper
use DatabaseSwitcher
run App::Application.routes
他にも前回の内容からいくつか変える必要があるものの大枠はこれでいけます
接続先のスキーマが異なる場合
本来は同じテーブル構造にしていましたが、あえて異なるテーブル構造にしてたとしても問題なく表示されるようにします
2つあるデータベースの片方だけcontentsテーブルにカラム追加を行います
(今回はdefault側に実施)
# default側
ActiveRecord::Schema[7.1].define(version: 2023_11_30_130217) do
create_table "contents", charset: "utf8mb4", force: :cascade do |t|
t.string "title"
t.string "body"
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
# renewal側
ActiveRecord::Schema[7.1].define(version: 2023_11_29_171139) do
create_table "contents", charset: "utf8mb4", force: :cascade do |t|
t.string "title"
t.string "body"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
既存のviewはカラムを指定して表示しているのでここを可変にします
# 変更前
<% @contents.each do |content| %>
<p>title: <%= content.title %></p>
<p>body: <%= content.body %></p>
<% end %>
# 変更後
<% @contents.each do |content| %>
<% content.attributes.each do |key, value| %>
<p><%= key %>: <%= value %></p>
<% end %>
<% end %>
このようになりますが不要なattributeがあればモデル側にview_attributesみたいなのを定義して利用するのも良さそうです
class Content < ContentBaseRecord
def view_attributes
attributes.except('id', 'created_at', 'updated_at')
end
end
<% @contents.each do |content| %>
<% content.view_attributes.each do |key, value| %>
<p><%= key %>: <%= value %></p>
<% end %>
<% end %>
実際にはこのようになります
まとめ
前回は標準的なやり方に則ってみましたが、今回はミドルウェアでコネクションを変更する方法を試してみました
これで中身のないクラスができることはなさそうなので良かった
サンプルコード
参照・引用