I suppose it's about time I updated my Simple Rails Multi-Tenancy post to use the latest rails 3.1.1. Not much has changed from the 3.0 beta to 3.1.1 in terms of the method I use to achieve multi-tenancy but the code has become a bit cleaner (but the patch required is a bit larger). This time around I'll skip the commands and just go straight to the code itself.
app/models/tenant.rb
class Tenant < ActiveRecord::Base
class << self
def current
Thread.current[:tenant]
end
def current=(tenant)
Thread.current[:tenant] = tenant
end
end
validates :name, :host, presence: true
def with
previous, Tenant.current = Tenant.current, self
yield
ensure
Tenant.current = previous
end
end
app/models/tenant_scoped_model.rb
class TenantScopedModel < ActiveRecord::Base
class << self
def build_default_scope
if method(:default_scope).owner != ActiveRecord::Base.singleton_class
evaluate_default_scope { default_scope }
elsif default_scopes.any?
evaluate_default_scope do
default_scopes.inject(relation) do |default_scope, scope|
if scope.is_a?(Hash)
default_scope.apply_finder_options(scope)
elsif !scope.is_a?(ActiveRecord::Relation) && scope.respond_to?(:call)
if scope.respond_to?(:arity) && scope.arity == 1
scope = scope.call(self)
else
scope = scope.call
end
default_scope.merge(scope)
else
default_scope.merge(scope)
end
end
end
end
end
end
self.abstract_class = true
belongs_to :tenant
default_scope do |model|
model.where(:tenant_id => Tenant.current)
end
end
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
around_filter :with_tenant
protected
attr_reader :current_tenant
def with_tenant
@current_tenant = Tenant.find_by_host!(request.host)
@current_tenant.with { yield }
end
end
app/helpers/application_helper.rb
module ApplicationHelper
attr_reader :current_tenant
end
All of the migrations and the products model remain the same.
Demo sites are still set up: 3 that exist in the tenant table and 1 that does not. All code is available in a github repository.
Hi Samuel – trying to implement your strategy on rails 3.2.1 and getting this stack trace when trying to list products:
activerecord (3.2.1) lib/active_record/relation.rb:500:in
with_default_scope'
exec_queries’activerecord (3.2.1) lib/active_record/relation.rb:166:in
activerecord (3.2.1) lib/active_record/relation.rb:159:in
block in to_a'
logging_query_plan’activerecord (3.2.1) lib/active_record/explain.rb:40:in
activerecord (3.2.1) lib/active_record/relation.rb:158:in
to_a'
all’activerecord (3.2.1) lib/active_record/relation/finder_methods.rb:159:in
activerecord (3.2.1) lib/active_record/querying.rb:5:in
all'
index’app/controllers/products_controller.rb:5:in
You can recreate easily in your example code by upgrading the gem file (I am also using sqlite):
gem ‘rails’, ‘3.2.1’
group :assets do
gem ‘sass-rails’, ‘~> 3.2.3’
gem ‘coffee-rails’, ‘~> 3.2.1’
gem ‘uglifier’, ‘>= 1.0.3’
end
Do you have any idea how to fix this?
Many thanks
Sorry – formatting not very good!