1
0

【Rails】ミドルウェアでパスを見てDBを切り替える

Posted at

概要

つい昨日パスでアクセスするDBを変更する方法をまとめて書きました

結果的にcocernに共通処理として本来ControllerやModelが持つべき処理を切り出して、各々で読み込む対応をしましたが中身のないModelやControllerが大量に作成されることになりました

DBへ接続しに行くときのミドルウェアで接続先DBを変更できればめちゃいいじゃんと思いちょっと試してみました

サンプルコードはこちらです

実際にミドルウェアを書いて読み込ませる

アクセスがあったときのDBの切り替えは下記のように定義します

  • /hoge/contentsでアクセスがあった場合
    • defaultの設定を利用する(db名はkanban_development)
  • /fuga/contentsでアクセスがあった場合
    • renewalの設定を利用する(db名はboard_development)
  • パスの第一階層がその他の場合は /hoge/contents と同じ挙動にさせます

今回はミドルウェアとしてDatabaseSwitcherを定義し、その中でパスを見てデータベース名を切り替えています

app/middleware/database_switcher.rb
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を抜き出しています

続いてアプリケーション設定として定義したミドルウェアを利用するよう設定を行います

config/application.rb
~~~~
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側に実施)

schema.rb
# 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はカラムを指定して表示しているのでここを可変にします

app/views/contents/index.html.erb
# 変更前
<% @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みたいなのを定義して利用するのも良さそうです

app/models/content.rb
class Content < ContentBaseRecord
  def view_attributes
    attributes.except('id', 'created_at', 'updated_at')
  end
end
app/views/contents/index.html.erb
<% @contents.each do |content| %>
  <% content.view_attributes.each do |key, value| %>
    <p><%= key %>: <%= value %></p>
  <% end %>
<% end %>

実際にはこのようになります

output.gif

まとめ

前回は標準的なやり方に則ってみましたが、今回はミドルウェアでコネクションを変更する方法を試してみました

これで中身のないクラスができることはなさそうなので良かった

サンプルコード

参照・引用

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