acts_as_tenant 0.6.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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