apartmentを使ってマルチテナントを実装する

井原(@ihara2525)です。

サブドメインでデータを分けたい(例えばhttps://bitjourney.slack.comみたいな)場合、RailscastsにあるMultitenancy with Scopesみたいに実現するのもありですが、その中でも参照されている、apartmentというgemを使うと、一つのRailsアプリケーションから複数のDBを扱う実装が、簡単にできそうです。

まずGemfileにapartmentを追加してbundle。

Gemfile

source 'https://rubygems.org' do
  ...
  gem 'apartment', '1.0.0'
  ...
end

apartmentの設定ファイルをつくります。

rails g apartment:install

config/initializers/apartment.rbがつくられるので、中を編集。サブドメインで分ける場合は以下な感じになると思います。

config/initializers/apartment.rb

require 'apartment/elevators/subdomain'

Apartment.configure do |config|

  config.excluded_models = %w(Tenant)

  config.tenant_names = -> { Tenant.pluck(:name) }

  config.prepend_environment = !Rails.env.production?
end

Rails.application.config.middleware.use 'Apartment::Elevators::Subdomain'

一点、config.prepent_environmentはデフォルトではコメントアウトされていて、ドキュメントによると

By default, and only when not using PostgreSQL schemas, Apartment will prepend the environment to the tenant name to ensure there is no conflict between your environments. This is mainly for the benefit of your development and test environments. Uncomment the line below if you want to disable this behaviour in production.

とのことなので、デフォルトでRails.envをDB名の頭につけてくれそうに読めたのですが、つけてくれなかったのでコメントを外しています。 上記の設定だとproductionのときはfalseになるので、TenantのnameでDBがつくられそうです。

テナントを管理するモデルをつくります、今回はそのままTenantという名前で。nameでそれぞれを識別することにします。null: false等適宜つけておきましょう。

rails g model tenant name:string
rake db:migrate

で、試しに一つテナントをつくってみます。通常db/seeds.rbとかseed-fuとかを使うんだと思います。

rails c
[1] pry(main)> Tenant.create(name: 'bitjourney')

で、テナントを作成。

rake apartment:create

これでdevelopment_bitjourneyというDBがつくられます。 その後はマイグレーションを実行すると、これらのDB全てに反映されていきます。

rake db:migrate

これで、bitjourney.lvh.me:3000等、Tenantがある場合にはページが表示されますし、unknown.lvh.me:3000等Tenantがない場合はApartment::TenantNotFoundが発生します。いい感じですね。

f:id:ihara2525:20150522163146p:plain

というわけで、こういうのって、マルチテナントっていうんですね。何という名前で調べれば良いかわかっていませんでした。