acts_as_tenant 2.2.1 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f08b36c305d545f35106ccce014d60c77b143f697a219c0eff6fe064b797b45c
4
- data.tar.gz: 95c5783b42a93e692594c7593d6e7d8dd31ca131a109a7497b6c79171419d417
3
+ metadata.gz: 7c0fbfe96e632768ed0f7dfae94d955c791c6ce1b724ef84e0ce6c2ca0fe0d88
4
+ data.tar.gz: 4294ce8d41e28d09c730f28ef22d2a28aa59ac1a4d4aba8e0b12ea3dea410719
5
5
  SHA512:
6
- metadata.gz: 25f1ba646a1f44f8071cf4bc03aab45a3464857aa0e79ddb6ae27dfbad4ec61b6241f46c86106b8bae88e45966cf120a4f82854c8e8536630d69ddf7648f41fb
7
- data.tar.gz: 3d093c37d7bb737d57a6ec4c4e89aadbd55b23635d34aa70c6b06955933d25189378e1176de05f2d8e27e29632233c6a078c6f4c4687d7a856cba616233ab2ff
6
+ metadata.gz: 4c41db14ddabafb233b66c0ac39d2fddd4fb03742ed874ca8a8027e3b46927f70e7eef1d2e65b157073e02b79de95f492859449cc6cd45c828cd4590463191b5
7
+ data.tar.gz: 3e9f92da5f007404e79e0381a8a065f04515b5d96f451ce1d91636a8d2c37bac71a0944d2404d53fde07f01e98b26f194317e5be9fbaf66aac9fc155c4ef4ae6
data/README.md CHANGED
@@ -128,6 +128,8 @@ end
128
128
  This approach is useful when running background processes for a specified tenant. For example, by putting this in your worker's run method,
129
129
  any code in this block will be scoped to the current tenant. All methods that set the current tenant are thread safe.
130
130
 
131
+ The tenant is stored in `ActiveSupport::CurrentAttributes`, which is isolated per thread. On servers that run requests in fibers, such as Falcon, set `config.active_support.isolation_level = :fiber` so each request keeps its own tenant.
132
+
131
133
  **Note:** If the current tenant is not set by one of these methods, Acts_as_tenant will be unable to apply the proper scope to your models. So make sure you use one of the two methods to tell acts_as_tenant about the current tenant.
132
134
 
133
135
  ### Disabling tenant checking for a block
@@ -197,11 +199,12 @@ Project.tasks.all # => all tasks with account_id => 3
197
199
 
198
200
  Acts_as_tenant uses Rails' `default_scope` method to scope models. Rails 3.1 changed the way `default_scope` works in a good way. A user defined `default_scope` should integrate seamlessly with the one added by `acts_as_tenant`.
199
201
 
200
- Because the scoping comes from `default_scope`, queries built from the model are scoped when a tenant is set. This includes `update_all`, `delete_all` and `destroy_all`, and `insert_all` and `upsert_all` set the current tenant. These are **not** scoped or checked:
202
+ Because the scoping comes from `default_scope`, queries built from the model are scoped when a tenant is set. This includes `update_all`, `delete_all` and `destroy_all`. `insert_all` and `upsert_all` set the current tenant on the rows they write, but they skip validations. `upsert_all` also matches existing rows by primary key across all tenants, so passing another tenant's id overwrites that record and moves it to the current tenant. Never pass ids from user input to `upsert_all`. These are **not** scoped or checked:
201
203
 
202
204
  * Any query when no tenant is set, unless `require_tenant` is enabled
203
205
  * Queries that remove the default scope, such as `unscoped`
204
206
  * Raw SQL, such as `find_by_sql`, `connection.execute` and `exec_query`
207
+ * `update_counters`, `increment_counter` and `decrement_counter`, which Rails runs without default scopes
205
208
  * `update_column` and `update_columns`, which skip the check that prevents changing a record's tenant
206
209
 
207
210
  New records are assigned the current tenant unless they already have one. A record assigned to a different tenant fails validation, so to create records for another tenant, wrap them in `ActsAsTenant.with_tenant(other_tenant) { ... }`.
@@ -12,8 +12,16 @@ module ActsAsTenant
12
12
 
13
13
  def find_tenant_by_subdomain_or_domain
14
14
  subdomain = request.subdomains.public_send(subdomain_lookup)
15
- query = subdomain.present? ? {tenant_primary_column => subdomain.downcase} : {tenant_second_column => request.domain.downcase}
16
- ActsAsTenant.current_tenant = tenant_class.where(query).first
15
+ domain = request.domain
16
+
17
+ # request.domain is nil for IP addresses, so there is no tenant to look up
18
+ query = if subdomain.present?
19
+ {tenant_primary_column => subdomain.downcase}
20
+ elsif domain.present?
21
+ {tenant_second_column => domain.downcase}
22
+ end
23
+
24
+ ActsAsTenant.current_tenant = query && tenant_class.find_by(query)
17
25
  end
18
26
  end
19
27
  end
@@ -16,6 +16,7 @@ module ActsAsTenant
16
16
  tenant_reflection = reflect_on_association(tenant)
17
17
  fkey = tenant_reflection.foreign_key.to_sym
18
18
  polymorphic_type = tenant_reflection.foreign_type&.to_sym
19
+ tenant_attributes = [fkey, polymorphic_type].compact
19
20
 
20
21
  # Polymorphic tenants are stored with polymorphic_name, like Rails does. Records saved before
21
22
  # that used the class name, which differs for STI tenants, so match both when scoping.
@@ -65,6 +66,13 @@ module ActsAsTenant
65
66
  end
66
67
  }, on: :create
67
68
 
69
+ # Returns the model class named by a polymorphic type column, or nil when the name
70
+ # is not a model, such as "Kernel" or "String" submitted in form params
71
+ polymorphic_class = lambda do |name|
72
+ klass = name&.safe_constantize
73
+ klass if klass.is_a?(Class) && klass < ActiveRecord::Base
74
+ end
75
+
68
76
  # Returns [tenant id, tenant class] for a record, or nil if it has no tenant.
69
77
  # Classes are normalized with polymorphic_name so STI tenants compare equal.
70
78
  tenant_identity = lambda do |model|
@@ -74,7 +82,7 @@ module ActsAsTenant
74
82
  if id
75
83
  type = if reflection.polymorphic?
76
84
  stored_type = model.read_attribute(reflection.foreign_type)
77
- stored_type&.safe_constantize&.polymorphic_name || stored_type
85
+ polymorphic_class.call(stored_type)&.polymorphic_name || stored_type
78
86
  else
79
87
  reflection.klass.polymorphic_name
80
88
  end
@@ -104,7 +112,6 @@ module ActsAsTenant
104
112
  current_id, current_type = current_tenant_identity.call
105
113
  next unless current_id
106
114
 
107
- tenant_attributes = [fkey, polymorphic_type].compact
108
115
  next unless record.new_record? || tenant_attributes.any? { |attr| record.will_save_change_to_attribute?(attr) }
109
116
 
110
117
  record_tenant = tenant_identity.call(record)
@@ -143,8 +150,7 @@ module ActsAsTenant
143
150
  next unless record.validate_tenant_association?(association)
144
151
 
145
152
  klass = if association.polymorphic?
146
- type = record.read_attribute(association.foreign_type)&.safe_constantize
147
- type if type.is_a?(Class) && type < ActiveRecord::Base
153
+ polymorphic_class.call(record.read_attribute(association.foreign_type))
148
154
  else
149
155
  # Raises for composite keys whose column counts don't match, like loading the association would
150
156
  association.check_validity! if attrs.size > 1
@@ -166,12 +172,15 @@ module ActsAsTenant
166
172
  end
167
173
  end
168
174
 
169
- # Tenant writers raise if the tenant changes on a persisted record
175
+ # Tenant writers raise if the tenant changes on a persisted record. The change is
176
+ # undone before raising so it isn't saved later, for example by update_attribute.
170
177
  to_include = Module.new {
171
- define_method :"#{fkey}=" do |integer|
172
- write_attribute(fkey, integer)
173
- raise_if_tenant_changed
174
- integer
178
+ tenant_attributes.each do |attr|
179
+ define_method :"#{attr}=" do |value|
180
+ write_attribute(attr, value)
181
+ raise_if_tenant_changed
182
+ value
183
+ end
175
184
  end
176
185
 
177
186
  define_method :"#{tenant}=" do |model|
@@ -181,12 +190,15 @@ module ActsAsTenant
181
190
  end
182
191
 
183
192
  define_method :raise_if_tenant_changed do
184
- raise ActsAsTenant::Errors::TenantIsImmutable if !ActsAsTenant.mutable_tenant? && tenant_modified?
193
+ if tenant_modified? && !ActsAsTenant.mutable_tenant?
194
+ restore_attributes(tenant_attributes)
195
+ raise ActsAsTenant::Errors::TenantIsImmutable
196
+ end
185
197
  end
186
198
  private :raise_if_tenant_changed
187
199
 
188
200
  define_method :tenant_modified? do
189
- will_save_change_to_attribute?(fkey) && persisted? && attribute_in_database(fkey).present?
201
+ persisted? && attribute_in_database(fkey).present? && tenant_attributes.any? { |attr| will_save_change_to_attribute?(attr) }
190
202
  end
191
203
  }
192
204
  include to_include
@@ -1,3 +1,3 @@
1
1
  module ActsAsTenant
2
- VERSION = "2.2.1"
2
+ VERSION = "2.2.2"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_tenant
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 2.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erwin Matthijssen