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

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

https://qiita.com/yothio/items/84daecd154624cdd059e

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

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

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

https://github.com/yothio/rails_multi_db_test/tree/modify_database_for_middleware



## 実際にミドルウェアを書いて読み込ませる
アクセスがあったときのDBの切り替えは下記のように定義します
- /hoge/contentsでアクセスがあった場合
  - defaultの設定を利用する(db名はkanban_development)
- /fuga/contentsでアクセスがあった場合
  - renewalの設定を利用する(db名はboard_development)
- パスの第一階層がその他の場合は /hoge/contents と同じ挙動にさせます

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


```ruby: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を抜き出しています

https://www.rubydoc.info/gems/rack/Rack/Request


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


```ruby:config/application.rb
~~~~
require_relative '../app/middleware/database_switcher'

module App
  class Application < Rails::Application
    ~~~
    ~~~

    config.middleware.use DatabaseSwitcher
```

middleware一覧を確認すると末尾に追加されていることが確認できます
もし、異なる場所にミドルウェアを入れたい場合は`insert_before`,`insert_after`などを利用しましょう


```bash
$ docker compose exec web bundle exec rails middleware
...
use Rack::ConditionalGet
use Rack::ETag
use Rack::TempfileReaper
use DatabaseSwitcher
run App::Application.routes
```


https://railsguides.jp/rails_on_rack.html#%E3%83%9F%E3%83%89%E3%83%AB%E3%82%A6%E3%82%A7%E3%82%A2%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B

他にも前回の内容からいくつか変える必要があるものの大枠はこれでいけます

## 接続先のスキーマが異なる場合
本来は同じテーブル構造にしていましたが、あえて異なるテーブル構造にしてたとしても問題なく表示されるようにします

2つあるデータベースの片方だけcontentsテーブルにカラム追加を行います
（今回はdefault側に実施）

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

<img width=400 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199819/b3fab45d-9afc-3bb4-2146-a3a83df43167.png />

このようになりますが不要なattributeがあればモデル側にview_attributesみたいなのを定義して利用するのも良さそうです

```ruby:app/models/content.rb
class Content < ContentBaseRecord
  def view_attributes
    attributes.except('id', 'created_at', 'updated_at')
  end
end
```

```erb: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199819/5e95163b-0256-a336-d46e-282e993424e4.gif)

## まとめ
[前回](https://qiita.com/yothio/items/84daecd154624cdd059e)は標準的なやり方に則ってみましたが、今回はミドルウェアでコネクションを変更する方法を試してみました

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

サンプルコード

https://github.com/yothio/rails_multi_db_test/tree/modify_database_for_middleware


## 参照・引用

https://railsguides.jp/rails_on_rack.html#