acts_as_tenant 0.6.1 → 1.0.0

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: 157bb94b2c59bdc0699a43f247141e44f4f8b9f41979186d678e996adfdbda2e
4
- data.tar.gz: c4f559b0b12e8e92fc5025f9c926f437f01742cca85c91f4e3d306373a0e3492
3
+ metadata.gz: 0e1def42ea987db7d6104355979247c9e3c7294b91dadf937d8056d0e50ccbc7
4
+ data.tar.gz: 9d834dc2d3a9b67ac2bcff475cd355ca9a17a81bb957f99141df77de219a4b57
5
5
  SHA512:
6
- metadata.gz: f843cea7abb50c0745b51e0850b93176c4a0713dcc875d62c733b4ff1df3b5cd7d9d2bf0b46de892af7009dfe106a3932d6698ed0132a2411466f43ecf243123
7
- data.tar.gz: 1efe052c243a8fc2a7d857d559945f99646f237cb16c22dd6219ea4f94697f2ecd299384ca86bb57758cd37e7773186f2f75becb66998c5c00218ffa0a42f3fd
6
+ metadata.gz: c1291eb290dbe3c79dc9ff6ba4a3e302a3b3d9b65ef94f9f34d5155e0d73ec8554facc2b32bf80d2f813521121f59d64dc9961d313db271f3c0adcdb974ded36
7
+ data.tar.gz: '0596e6fc3b49ef6220b88d6fec0ecbf9e3b7396fb6cfecd894b5324447268268e46301548f59a873a4e10f86d7e345d4e9ad9b7babfe8cda96cce71f86eac38d'
data/README.md CHANGED
@@ -35,7 +35,6 @@ Want to see how it works? Check out [the ActsAsTenant walkthrough video](https:/
35
35
 
36
36
  Installation
37
37
  ------------
38
- acts_as_tenant will only work on Rails 5.2 and up. This is due to changes made to the handling of `default_scope`, an essential pillar of the gem.
39
38
 
40
39
  To use it, add it to your Gemfile:
41
40
 
@@ -45,6 +44,7 @@ gem 'acts_as_tenant'
45
44
 
46
45
  Getting started
47
46
  ===============
47
+
48
48
  There are two steps in adding multi-tenancy to your app with acts_as_tenant:
49
49
 
50
50
  1. setting the current tenant and
@@ -52,6 +52,7 @@ There are two steps in adding multi-tenancy to your app with acts_as_tenant:
52
52
 
53
53
  Setting the current tenant
54
54
  --------------------------
55
+
55
56
  There are three ways to set the current tenant:
56
57
 
57
58
  1. by using the subdomain to lookup the current tenant,
@@ -59,6 +60,7 @@ There are three ways to set the current tenant:
59
60
  3. by setting the current tenant for a block.
60
61
 
61
62
  ### Looking Up Tenants
63
+
62
64
  #### By Subdomain to lookup the current tenant
63
65
 
64
66
  ```ruby
@@ -115,7 +117,7 @@ end
115
117
 
116
118
  This allows the tenant to be set before any other code runs so everything is within the current tenant.
117
119
 
118
- ### Setting the current tenant for a block ###
120
+ ### Setting the current tenant for a block
119
121
 
120
122
  ```ruby
121
123
  ActsAsTenant.with_tenant(current_account) do
@@ -128,16 +130,17 @@ any code in this block will be scoped to the current tenant. All methods that se
128
130
 
129
131
  **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.
130
132
 
131
- ### Disabling tenant checking for a block ###
133
+ ### Disabling tenant checking for a block
132
134
 
133
135
  ```ruby
134
136
  ActsAsTenant.without_tenant do
135
137
  # Tenant checking is disabled for all code in this block
136
138
  end
137
139
  ```
140
+
138
141
  This is useful in shared routes such as admin panels or internal dashboards when `require_tenant` option is enabled throughout the app.
139
142
 
140
- ### Allowing tenant updating for a block ###
143
+ ### Allowing tenant updating for a block
141
144
 
142
145
  ```ruby
143
146
  ActsAsTenant.with_mutable_tenant do
@@ -147,7 +150,7 @@ end
147
150
 
148
151
  This will allow you to change the tenant of a model. This feature is useful for admin screens, where it is ok to allow certain users to change the tenant on existing models in specific cases.
149
152
 
150
- ### Require tenant to be set always ###
153
+ ### Require tenant to be set always
151
154
 
152
155
  If you want to require the tenant to be set at all times, you can configure acts_as_tenant to raise an error when a query is made without a tenant available. See below under configuration options.
153
156
 
@@ -194,7 +197,7 @@ Project.tasks.all # => all tasks with account_id => 3
194
197
 
195
198
  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`.
196
199
 
197
- ### Validating attribute uniqueness ###
200
+ ### Validating attribute uniqueness
198
201
 
199
202
  If you need to validate for uniqueness, chances are that you want to scope this validation to a tenant. You can do so by using:
200
203
 
@@ -204,7 +207,7 @@ validates_uniqueness_to_tenant :name, :email
204
207
 
205
208
  All options available to Rails' own `validates_uniqueness_of` are also available to this method.
206
209
 
207
- ### Custom foreign_key ###
210
+ ### Custom foreign_key
208
211
 
209
212
  You can explicitly specifiy a foreign_key for AaT to use should the key differ from the default:
210
213
 
@@ -212,7 +215,7 @@ You can explicitly specifiy a foreign_key for AaT to use should the key differ f
212
215
  acts_as_tenant(:account, :foreign_key => 'accountID') # by default AaT expects account_id
213
216
  ```
214
217
 
215
- ### Custom primary_key ###
218
+ ### Custom primary_key
216
219
 
217
220
  You can also explicitly specifiy a primary_key for AaT to use should the key differ from the default:
218
221
 
@@ -220,8 +223,7 @@ You can also explicitly specifiy a primary_key for AaT to use should the key dif
220
223
  acts_as_tenant(:account, :primary_key => 'primaryID') # by default AaT expects id
221
224
  ```
222
225
 
223
-
224
- ### Has and belongs to many ###
226
+ ### Has and belongs to many
225
227
 
226
228
  You can scope a model that is part of a HABTM relationship by using the `through` option.
227
229
 
@@ -244,12 +246,16 @@ end
244
246
 
245
247
  Configuration options
246
248
  ---------------------
249
+
247
250
  An initializer can be created to control (currently one) option in ActsAsTenant. Defaults
248
251
  are shown below with sample overrides following. In `config/initializers/acts_as_tenant.rb`:
249
252
 
250
253
  ```ruby
251
254
  ActsAsTenant.configure do |config|
252
255
  config.require_tenant = false # true
256
+
257
+ # Customize the query for loading the tenant in background jobs
258
+ config.job_scope = ->{ all }
253
259
  end
254
260
  ```
255
261
 
@@ -269,8 +275,34 @@ end
269
275
 
270
276
  `ActsAsTenant.should_require_tenant?` is used to determine if a tenant is required in the current context, either by evaluating the lambda provided, or by returning the boolean value assigned to `config.require_tenant`.
271
277
 
278
+ When using `config.require_tenant` alongside the `rails console`, a nice quality of life tweak is to set the tenant in the console session in your initializer script. For example in `config/initializers/acts_as_tenant.rb`:
279
+
280
+ ```ruby
281
+ SET_TENANT_PROC = lambda do
282
+ if defined?(Rails::Console)
283
+ puts "> ActsAsTenant.current_tenant = Account.first"
284
+ ActsAsTenant.current_tenant = Account.first
285
+ end
286
+ end
287
+
288
+ Rails.application.configure do
289
+ if Rails.env.development?
290
+ # Set the tenant to the first account in development on load
291
+ config.after_initialize do
292
+ SET_TENANT_PROC.call
293
+ end
294
+
295
+ # Reset the tenant after calling 'reload!' in the console
296
+ ActiveSupport::Reloader.to_complete do
297
+ SET_TENANT_PROC.call
298
+ end
299
+ end
300
+ end
301
+ ```
302
+
272
303
  belongs_to options
273
- ---------------------
304
+ ------------------
305
+
274
306
  `acts_as_tenant :account` includes the belongs_to relationship.
275
307
  So when using acts_as_tenant on a model, do not add `belongs_to :account` alongside `acts_as_tenant :account`:
276
308
 
@@ -286,16 +318,22 @@ You can add the following `belongs_to` options to `acts_as_tenant`:
286
318
 
287
319
  Example: `acts_as_tenant(:account, counter_cache: true)`
288
320
 
289
- Sidekiq support
290
- ---------------
321
+ Background Processing libraries
322
+ -------------------------------
323
+
324
+ ActsAsTenant supports
291
325
 
292
- ActsAsTenant supports [Sidekiq](http://sidekiq.org/). A background processing library.
293
- Add the following code to your `config/initializers/acts_as_tenant.rb`:
326
+ - ActiveJob - ActsAsTenant will automatically save the current tenant in ActiveJob arguments and set it when the job runs.
327
+
328
+ - [Sidekiq](//sidekiq.org/)
329
+ Add the following code to `config/initializers/acts_as_tenant.rb`:
294
330
 
295
331
  ```ruby
296
332
  require 'acts_as_tenant/sidekiq'
297
333
  ```
298
334
 
335
+ - DelayedJob - [acts_as_tenant-delayed_job](https://github.com/nunommc/acts_as_tenant-delayed_job)
336
+
299
337
  Testing
300
338
  ---------------
301
339
 
@@ -311,7 +349,6 @@ Rails.application.configure do
311
349
  config.middleware.use ActsAsTenant::TestTenantMiddleware
312
350
  end
313
351
  ```
314
-
315
352
  ```ruby
316
353
  # spec_helper.rb
317
354
  config.before(:suite) do |example|
@@ -335,9 +372,9 @@ config.after(:each) do |example|
335
372
  ActsAsTenant.test_tenant = nil
336
373
  end
337
374
  ```
338
-
339
375
  Bug reports & suggested improvements
340
376
  ------------------------------------
377
+
341
378
  If you have found a bug or want to suggest an improvement, please use our issue tracked at:
342
379
 
343
380
  [github.com/ErwinM/acts_as_tenant/issues](http://github.com/ErwinM/acts_as_tenant/issues)
@@ -357,10 +394,12 @@ We use the Appraisal gem to run tests against supported versions of Rails to tes
357
394
 
358
395
  Author & Credits
359
396
  ----------------
397
+
360
398
  acts_as_tenant is written by Erwin Matthijssen & Chris Oliver.
361
399
 
362
400
  This gem was inspired by Ryan Sonnek's [Multitenant](https://github.com/wireframe/multitenant) gem and its use of default_scope.
363
401
 
364
402
  License
365
403
  -------
404
+
366
405
  Copyright (c) 2011 Erwin Matthijssen, released under the MIT license
@@ -0,0 +1,13 @@
1
+ module ActsAsTenant
2
+ module ActiveJobExtensions
3
+ def serialize
4
+ super.merge("current_tenant" => ActsAsTenant.current_tenant&.to_global_id)
5
+ end
6
+
7
+ def deserialize(job_data)
8
+ tenant_global_id = job_data.delete("current_tenant")
9
+ ActsAsTenant.current_tenant = tenant_global_id ? GlobalID::Locator.locate(tenant_global_id) : nil
10
+ super
11
+ end
12
+ end
13
+ end
@@ -9,5 +9,23 @@ module ActsAsTenant
9
9
  def pkey
10
10
  @pkey ||= :id
11
11
  end
12
+
13
+ def job_scope
14
+ @job_scope || ->(relation) { relation.all }
15
+ end
16
+
17
+ # Used for looking job tenants in background jobs
18
+ #
19
+ # Format matches Rails scopes
20
+ #
21
+ # job_scope = ->(relation) {}
22
+ # job_scope = -> {}
23
+ def job_scope=(scope)
24
+ @job_scope = if scope && scope.arity == 0
25
+ proc { instance_exec(&scope) }
26
+ else
27
+ scope
28
+ end
29
+ end
12
30
  end
13
31
  end
@@ -3,7 +3,7 @@ module ActsAsTenant
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  class_methods do
6
- def acts_as_tenant(tenant = :account, **options)
6
+ def acts_as_tenant(tenant = :account, scope = nil, **options)
7
7
  ActsAsTenant.set_tenant_klass(tenant)
8
8
  ActsAsTenant.mutable_tenant!(false)
9
9
 
@@ -14,7 +14,7 @@ module ActsAsTenant
14
14
  fkey = valid_options[:foreign_key] || ActsAsTenant.fkey
15
15
  pkey = valid_options[:primary_key] || ActsAsTenant.pkey
16
16
  polymorphic_type = valid_options[:foreign_type] || ActsAsTenant.polymorphic_type
17
- belongs_to tenant, **valid_options
17
+ belongs_to tenant, scope, **valid_options
18
18
 
19
19
  default_scope lambda {
20
20
  if ActsAsTenant.should_require_tenant? && ActsAsTenant.current_tenant.nil? && !ActsAsTenant.unscoped?
@@ -61,12 +61,16 @@ module ActsAsTenant
61
61
  reflect_on_all_associations(:belongs_to).each do |a|
62
62
  unless a == reflect_on_association(tenant) || polymorphic_foreign_keys.include?(a.foreign_key)
63
63
  validates_each a.foreign_key.to_sym do |record, attr, value|
64
+ next if value.nil?
65
+ next unless record.will_save_change_to_attribute?(attr)
66
+
64
67
  primary_key = if a.respond_to?(:active_record_primary_key)
65
68
  a.active_record_primary_key
66
69
  else
67
70
  a.primary_key
68
71
  end.to_sym
69
- record.errors.add attr, "association is invalid [ActsAsTenant]" unless value.nil? || a.klass.where(primary_key => value).any?
72
+ scope = a.scope || ->(relation) { relation }
73
+ record.errors.add attr, "association is invalid [ActsAsTenant]" unless a.klass.class_eval(&scope).where(primary_key => value).any?
70
74
  end
71
75
  end
72
76
  end
@@ -28,7 +28,9 @@ module ActsAsTenant::Sidekiq
28
28
 
29
29
  def call(worker_class, msg, queue)
30
30
  if msg.has_key?("acts_as_tenant")
31
- account = msg["acts_as_tenant"]["class"].constantize.find msg["acts_as_tenant"]["id"]
31
+ klass = msg["acts_as_tenant"]["class"].constantize
32
+ id = msg["acts_as_tenant"]["id"]
33
+ account = klass.class_eval(&ActsAsTenant.configuration.job_scope).find(id)
32
34
  ActsAsTenant.with_tenant account do
33
35
  yield
34
36
  end
@@ -1,3 +1,3 @@
1
1
  module ActsAsTenant
2
- VERSION = "0.6.1"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -1,5 +1,4 @@
1
- require "request_store"
2
-
1
+ require "active_support/current_attributes"
3
2
  require "acts_as_tenant/version"
4
3
  require "acts_as_tenant/errors"
5
4
 
@@ -8,12 +7,17 @@ module ActsAsTenant
8
7
  autoload :ControllerExtensions, "acts_as_tenant/controller_extensions"
9
8
  autoload :ModelExtensions, "acts_as_tenant/model_extensions"
10
9
  autoload :TenantHelper, "acts_as_tenant/tenant_helper"
10
+ autoload :ActiveJobExtensions, "acts_as_tenant/active_job_extensions"
11
11
 
12
12
  @@configuration = nil
13
13
  @@tenant_klass = nil
14
14
  @@models_with_global_records = []
15
15
  @@mutable_tenant = false
16
16
 
17
+ class Current < ActiveSupport::CurrentAttributes
18
+ attribute :current_tenant, :acts_as_tenant_unscoped
19
+ end
20
+
17
21
  class << self
18
22
  attr_writer :default_tenant
19
23
  end
@@ -57,11 +61,11 @@ module ActsAsTenant
57
61
  end
58
62
 
59
63
  def self.current_tenant=(tenant)
60
- RequestStore.store[:current_tenant] = tenant
64
+ Current.current_tenant = tenant
61
65
  end
62
66
 
63
67
  def self.current_tenant
64
- RequestStore.store[:current_tenant] || test_tenant || default_tenant
68
+ Current.current_tenant || test_tenant || default_tenant
65
69
  end
66
70
 
67
71
  def self.test_tenant=(tenant)
@@ -73,11 +77,11 @@ module ActsAsTenant
73
77
  end
74
78
 
75
79
  def self.unscoped=(unscoped)
76
- RequestStore.store[:acts_as_tenant_unscoped] = unscoped
80
+ Current.acts_as_tenant_unscoped = unscoped
77
81
  end
78
82
 
79
83
  def self.unscoped
80
- RequestStore.store[:acts_as_tenant_unscoped]
84
+ Current.acts_as_tenant_unscoped
81
85
  end
82
86
 
83
87
  def self.unscoped?
@@ -157,3 +161,7 @@ end
157
161
  ActiveSupport.on_load(:action_view) do |base|
158
162
  base.include ActsAsTenant::TenantHelper
159
163
  end
164
+
165
+ ActiveSupport.on_load(:active_job) do |base|
166
+ base.prepend ActsAsTenant::ActiveJobExtensions
167
+ 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: 0.6.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erwin Matthijssen
@@ -9,126 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-01-23 00:00:00.000000000 Z
12
+ date: 2023-12-07 00:00:00.000000000 Z
13
13
  dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: request_store
16
- requirement: !ruby/object:Gem::Requirement
17
- requirements:
18
- - - ">="
19
- - !ruby/object:Gem::Version
20
- version: 1.0.5
21
- type: :runtime
22
- prerelease: false
23
- version_requirements: !ruby/object:Gem::Requirement
24
- requirements:
25
- - - ">="
26
- - !ruby/object:Gem::Version
27
- version: 1.0.5
28
14
  - !ruby/object:Gem::Dependency
29
15
  name: rails
30
16
  requirement: !ruby/object:Gem::Requirement
31
17
  requirements:
32
18
  - - ">="
33
19
  - !ruby/object:Gem::Version
34
- version: '5.2'
20
+ version: '6.0'
35
21
  type: :runtime
36
22
  prerelease: false
37
23
  version_requirements: !ruby/object:Gem::Requirement
38
24
  requirements:
39
25
  - - ">="
40
26
  - !ruby/object:Gem::Version
41
- version: '5.2'
42
- - !ruby/object:Gem::Dependency
43
- name: rspec
44
- requirement: !ruby/object:Gem::Requirement
45
- requirements:
46
- - - ">="
47
- - !ruby/object:Gem::Version
48
- version: '3.0'
49
- type: :development
50
- prerelease: false
51
- version_requirements: !ruby/object:Gem::Requirement
52
- requirements:
53
- - - ">="
54
- - !ruby/object:Gem::Version
55
- version: '3.0'
56
- - !ruby/object:Gem::Dependency
57
- name: rspec-rails
58
- requirement: !ruby/object:Gem::Requirement
59
- requirements:
60
- - - ">="
61
- - !ruby/object:Gem::Version
62
- version: '0'
63
- type: :development
64
- prerelease: false
65
- version_requirements: !ruby/object:Gem::Requirement
66
- requirements:
67
- - - ">="
68
- - !ruby/object:Gem::Version
69
- version: '0'
70
- - !ruby/object:Gem::Dependency
71
- name: sqlite3
72
- requirement: !ruby/object:Gem::Requirement
73
- requirements:
74
- - - ">="
75
- - !ruby/object:Gem::Version
76
- version: '0'
77
- type: :development
78
- prerelease: false
79
- version_requirements: !ruby/object:Gem::Requirement
80
- requirements:
81
- - - ">="
82
- - !ruby/object:Gem::Version
83
- version: '0'
84
- - !ruby/object:Gem::Dependency
85
- name: sidekiq
86
- requirement: !ruby/object:Gem::Requirement
87
- requirements:
88
- - - "~>"
89
- - !ruby/object:Gem::Version
90
- version: '6.1'
91
- - - ">="
92
- - !ruby/object:Gem::Version
93
- version: 6.1.2
94
- type: :development
95
- prerelease: false
96
- version_requirements: !ruby/object:Gem::Requirement
97
- requirements:
98
- - - "~>"
99
- - !ruby/object:Gem::Version
100
- version: '6.1'
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: 6.1.2
104
- - !ruby/object:Gem::Dependency
105
- name: standard
106
- requirement: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
- type: :development
112
- prerelease: false
113
- version_requirements: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- - !ruby/object:Gem::Dependency
119
- name: appraisal
120
- requirement: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
125
- type: :development
126
- prerelease: false
127
- version_requirements: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - ">="
130
- - !ruby/object:Gem::Version
131
- version: '0'
27
+ version: '6.0'
132
28
  description: Integrates multi-tenancy into a Rails application in a convenient and
133
29
  out-of-your way manner
134
30
  email:
@@ -142,6 +38,7 @@ files:
142
38
  - README.md
143
39
  - Rakefile
144
40
  - lib/acts_as_tenant.rb
41
+ - lib/acts_as_tenant/active_job_extensions.rb
145
42
  - lib/acts_as_tenant/configuration.rb
146
43
  - lib/acts_as_tenant/controller_extensions.rb
147
44
  - lib/acts_as_tenant/controller_extensions/filter.rb
@@ -171,7 +68,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
171
68
  - !ruby/object:Gem::Version
172
69
  version: '0'
173
70
  requirements: []
174
- rubygems_version: 3.4.4
71
+ rubygems_version: 3.4.22
175
72
  signing_key:
176
73
  specification_version: 4
177
74
  summary: Add multi-tenancy to Rails applications using a shared db strategy