マルチテナントで ActiveSupport::CurrentAttributes を使おうと考えた

マルチテナントのアプリケーションを作っていて、サブドメインでアクセスしたらそのテナントのデータを表示したい。

apartment gem を使うのが一般的だと思うが、 builderscon tokyo 2018つらくないマルチテナンシーを求めて: 全て見せます! SmartHR データベース移行プロジェクトの裏側 という発表を聞いたので、せっかくなら organization_id を全テーブルに持たせる感じのデータ構造にしようと考えた。

以下のような作戦です。

(1) サブドメインを rack middleware で見て、 ActiveSupport::CurrentAttributes に保存する

class OrganizationInjector
  def initialize(app)
    @app = app
  end

  def call(env)
    req = ActionDispatch::Request.new(env)
    Current.organization = Organization.find_by!(subdomain: req.subdomain)
    @app.call(env)
  end
end

(2) モデルでは default_scope で制御する

class Article < AR::Base
  default_scope { Current.organization && where(organization: Current.organization) }
end

というわけで

となりました。

検索して全部見る

使っていくぞ!!! と叫んでコードを書き出して、ふと

Current.organization 作るんだったら current_user じゃなくて Current.user でも良いなぁ」

と思ったので、GitHub でひたすら ActiveSupport::CurrentAttributes を使っているコードを検索して、検索結果を 100 ページまで全部見た。

このときに、ちょっとでも参考になる可能性があるな、と思ったらとりあえず ghq get して 中を読む ようにしているんだけど、その過程で 3 つの良い出会いがあった。

(今回は検索結果 100 ページで中を見たのは 17 レポジトリなので、異常に S/N 比が悪い。検索力が低い……。

operator_recordable に出会った

https://github.com/yujideveloper/operator_recordable

と以前から妄想していた、record_with_operator の AS::CurrentAttributes 版があるのを知った。

動く実装がなくなって久しいので完全に最高。

rectify に出会った

https://github.com/andypike/rectify

導入しやすい trailblazer みたいなヤツです。

個人的には Rectify::Command の記法と、テストの stub 方法が充実しているのが気に入った。 まだ日本語の記事無いっぽいので狙い目。コードリーディング会で読むのにちょうど良いサイズだと思う。

動画も見た。 http://andypike.com/blog/conferences/rubyc-2016/

このレールは良いレールなので選んでも良いなぁ。

rails_multitenant に出会った

https://github.com/salsify/rails-multitenant

グローバルな変数に入れて default_scope でやるって発想は完全に同じで、 実装方法が AS::CurrentAttributes ではなく自前なのが違う。

グローバルなコンテキストに GlobalContextRegistry って名前を付けてるのが最高。触ると危ないヤツは危ないことが分かる名前であるべきだ。(なので AS::CurrentAttributes のクラス名は Current ではない方がヨサソウ)

中身は Thread.current ですね。

https://github.com/salsify/rails-multitenant/blob/v0.10.0/lib/rails_multitenant/global_context_registry.rb

自分で Thread.current 扱ってるよりもみんなの目を通ったコードの方が安全だろう、というのが AS::CurrentAttributes を使おうと思ったキッカケなので、gemify されてるならコレにしようかな。サクッと読める (すぐ自前に乗り換えられる) サイズなのも良い。

salsify は salsify_rubocop がとても似た音楽性を持っていて好きな会社なので、gem を使えて嬉しい。

全部見た結果

という印象です。他の使い道がある場合は教えて欲しい。