module Composable module Tenant def self.without_tenant(*tenants) old_excluded_tenants = Composable::Tenant::DataSet.excluded_tenants Composable::Tenant::DataSet.excluded_tenants = Set.new(tenants.map(&:to_sym)) yield ensure Composable::Tenant::DataSet.excluded_tenants = old_excluded_tenants end def self.set_tenant(tenant, record) Composable::Tenant::DataSet.tenant = [tenant, record] record end module ModelExtensions extend ActiveSupport::Concern module ClassMethods def tenant(tenant, scope = nil, **options) # Create the association foreign_key = options.fetch(:foreign_key, "#{tenant}_id").to_sym primary_key = options.fetch(:primary_key, :id).to_sym belongs_to tenant, scope, **options default_scope lambda { return if Composable::Tenant::DataSet.excluded_tenants.include?(tenant.to_sym) raise Composable::Tenant::NoTenantSet.new(tenant: tenant) unless Composable::Tenant::DataSet.tenant(tenant) where(foreign_key => Composable::Tenant::DataSet.tenant(tenant).send(primary_key)) }, all_queries: true # Makes sure new instances have tenant set before_validation lambda { |record| return if Composable::Tenant::DataSet.excluded_tenants.include?(tenant.to_sym) record.send("#{foreign_key}=", Composable::Tenant::DataSet.tenant(tenant).send(primary_key)) }, on: :create # Rewrite the accessors to make tenant immutable to_include = Module.new do define_method "#{foreign_key}=" do |integer| write_attribute(foreign_key, integer) raise Composable::Tenant::TenantIsImmutable.new(method_name: "#{foreign_key}=") if send("#{foreign_key}_changed?") && persisted? && !send("#{foreign_key}_was").nil? integer end define_method "#{tenant}=" do |model| super(model) raise Composable::Tenant::TenantIsImmutable.new(method_name: "#{tenant}=") if send("#{foreign_key}_changed?") && persisted? && !send("#{foreign_key}_was").nil? model end end include to_include end end end end end ActiveSupport.on_load(:active_record) do |base| base.include Composable::Tenant::ModelExtensions end