Module: MultiTenant::ModelExtensionsClassMethods
- Defined in:
- lib/activerecord-multi-tenant/model_extensions.rb
Overview
Extension to the model to allow scoping of models to the current tenant. This is done by adding the multitenant method to the models that need to be scoped. This method is called in the model declaration. Adds scoped_by_tenant? partition_key, primary_key and inherited methods to the model
Constant Summary collapse
- DEFAULT_ID_FIELD =
'id'.freeze
Instance Method Summary collapse
-
#multi_tenant(tenant_name, options = {}) ⇒ Object
executes when multi_tenant method is called in the model.
Instance Method Details
#multi_tenant(tenant_name, options = {}) ⇒ Object
executes when multi_tenant method is called in the model. This method adds the following methods to the model that calls it. scoped_by_tenant? - returns true if the model is scoped by tenant partition_key - returns the partition key for the model primary_key - returns the primary key for the model
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/activerecord-multi-tenant/model_extensions.rb', line 16 def multi_tenant(tenant_name, = {}) if to_s.underscore.to_sym == tenant_name || (!table_name.nil? && table_name.singularize.to_sym == tenant_name) unless MultiTenant.with_write_only_mode_enabled? # This is the tenant model itself. Workaround for https://github.com/citusdata/citus/issues/687 before_create lambda { id = if self.class.columns_hash[self.class.primary_key].type == :uuid SecureRandom.uuid else self.class.connection.select_value( "SELECT nextval('#{self.class.table_name}_#{self.class.primary_key}_seq'::regclass)" ) end self.id ||= id } end else class << self def scoped_by_tenant? true end # Allow partition_key to be set from a superclass if not already set in this class def partition_key @partition_key ||= ancestors.detect { |k| k.instance_variable_get(:@partition_key) } .try(:instance_variable_get, :@partition_key) end # Avoid primary_key errors when using composite primary keys (e.g. id, tenant_id) def primary_key if defined?(PRIMARY_KEY_NOT_SET) ? !PRIMARY_KEY_NOT_SET.equal?(@primary_key) : @primary_key return @primary_key end primary_object_keys = Array.wrap(connection.schema_cache.primary_keys(table_name)) - [partition_key] @primary_key = if primary_object_keys.size == 1 primary_object_keys.first elsif connection.schema_cache.columns_hash(table_name).include? DEFAULT_ID_FIELD DEFAULT_ID_FIELD end end def inherited(subclass) super MultiTenant.register_multi_tenant_model(subclass) end end MultiTenant.register_multi_tenant_model(self) @partition_key = [:partition_key] || MultiTenant.partition_key(tenant_name) partition_key = @partition_key # Create an implicit belongs_to association only if tenant class exists if MultiTenant.tenant_klass_defined?(tenant_name, ) belongs_to tenant_name, **.slice(:class_name, :inverse_of, :optional) .merge(foreign_key: [:partition_key]) end # New instances should have the tenant set after_initialize proc { |record| if MultiTenant.current_tenant_id && (!record.attribute_present?(partition_key) || record.public_send(partition_key.to_sym).nil?) record.public_send("#{partition_key}=".to_sym, MultiTenant.current_tenant_id) end } # Below block adds the following methods to the model that calls it. # partition_key= - returns the partition key for the model.class << self 'partition' method defined above # is the getter method. Here, there is additional check to assure that the tenant id is not changed once set # tenant_name- returns the name of the tenant model. Its setter and getter methods defined separately # Getter checks for the tenant association and if it is not loaded, returns the current tenant id set # in the MultiTenant module to_include = Module.new do define_method "#{partition_key}=" do |tenant_id| write_attribute(partition_key.to_s, tenant_id) # Rails 5 `attribute_will_change!` uses the attribute-method-call rather than `read_attribute` # and will raise ActiveModel::MissingAttributeError if that column was not selected. # This is rescued as NoMethodError and in MRI attribute_was is assigned an arbitrary Object was = send("#{partition_key}_was") was_nil_or_skipped = was.nil? || was.instance_of?(Object) if send("#{partition_key}_changed?") && persisted? && !was_nil_or_skipped raise MultiTenant::TenantIsImmutable end tenant_id end if MultiTenant.tenant_klass_defined?(tenant_name, ) define_method "#{tenant_name}=" do |model| super(model) if send("#{partition_key}_changed?") && persisted? && !send("#{partition_key}_was").nil? raise MultiTenant::TenantIsImmutable end model end define_method tenant_name.to_s do if !association(tenant_name.to_sym).loaded? && !MultiTenant.current_tenant_is_id? && MultiTenant.current_tenant_id && public_send(partition_key) == MultiTenant.current_tenant_id MultiTenant.current_tenant else super() end end end end include to_include # Below blocks sets tenant_id for the current session with the tenant_id of the record # If the tenant is not set for the `session.After` the save operation current session tenant is set to nil # If tenant is set for the session, save operation is performed as it is around_save lambda { |record, block| record_tenant = record.attribute_was(partition_key) if persisted? && MultiTenant.current_tenant_id.nil? && !record_tenant.nil? MultiTenant.with(record.public_send(partition_key)) { block.call } else block.call end } around_update lambda { |record, block| record_tenant = record.attribute_was(partition_key) if MultiTenant.current_tenant_id.nil? && !record_tenant.nil? MultiTenant.with(record.public_send(partition_key)) { block.call } else block.call end } around_destroy lambda { |record, block| if MultiTenant.current_tenant_id.nil? MultiTenant.with(record.public_send(partition_key)) { block.call } else block.call end } end end |