rails_multitenant 0.11.0 → 0.15.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.
Files changed (39) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.txt +21 -0
  3. data/lib/rails_multitenant.rb +7 -4
  4. data/lib/rails_multitenant/global_context_registry.rb +43 -198
  5. data/lib/rails_multitenant/global_context_registry/current.rb +98 -0
  6. data/lib/rails_multitenant/global_context_registry/current_instance.rb +103 -0
  7. data/lib/rails_multitenant/global_context_registry/registry_dependent_on.rb +13 -0
  8. data/lib/rails_multitenant/middleware/extensions.rb +3 -3
  9. data/lib/rails_multitenant/middleware/isolated_context_registry.rb +2 -0
  10. data/lib/rails_multitenant/middleware/railtie.rb +3 -1
  11. data/lib/rails_multitenant/multitenant_model.rb +6 -2
  12. data/lib/rails_multitenant/rspec.rb +2 -0
  13. data/lib/rails_multitenant/version.rb +3 -1
  14. metadata +69 -76
  15. data/.gitignore +0 -14
  16. data/.rspec +0 -3
  17. data/.ruby-version +0 -1
  18. data/.travis.yml +0 -29
  19. data/CHANGELOG.md +0 -57
  20. data/CODE_OF_CONDUCT.md +0 -13
  21. data/Gemfile +0 -4
  22. data/README.md +0 -120
  23. data/Rakefile +0 -8
  24. data/bin/console +0 -14
  25. data/bin/setup +0 -7
  26. data/rails_multitenant.gemspec +0 -33
  27. data/spec/be_multitenant_on_matcher_spec.rb +0 -15
  28. data/spec/current_spec.rb +0 -118
  29. data/spec/db/database.yml +0 -3
  30. data/spec/db/schema.rb +0 -60
  31. data/spec/external_item_spec.rb +0 -36
  32. data/spec/external_item_with_optional_org_spec.rb +0 -25
  33. data/spec/global_context_registry_spec.rb +0 -113
  34. data/spec/item_spec.rb +0 -78
  35. data/spec/item_subtype_spec.rb +0 -37
  36. data/spec/item_with_optional_org_spec.rb +0 -26
  37. data/spec/middleware_isolated_context_registry_spec.rb +0 -15
  38. data/spec/rails_multitenant_spec.rb +0 -51
  39. data/spec/spec_helper.rb +0 -54
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 7c9936e0904797c7af7fb67e3a08102b5d4388be
4
- data.tar.gz: 5ba8bb69723b5d3b83007d6b5ad2148aa9769a50
2
+ SHA256:
3
+ metadata.gz: ab1547ea14417be1bbde8797d064b3c16e108ad698907e5ee5632ebd5d22468e
4
+ data.tar.gz: 6d03ee228b67715d3cd648e2ad9bf4396b68a2741f23941e560eac2000f1677e
5
5
  SHA512:
6
- metadata.gz: c1affba9e932ba4ffd61a034382a05fcc7027701137bafd215345451173ec5d92ef087366acf101db4e8fa48220f33c1b4649b33cb1b57c77c05b99db2de5bda
7
- data.tar.gz: d7e99b00fe2ef9bd7070b7074a0ea09e6a7a35a70554f62b095843318086da92a231543530132c95f316b913d01a3f71d062aa22b2d9cb2f3ee116188480e6be
6
+ metadata.gz: 30b2913470e4964030da8ce6c3a5d66a9ac9146aa9779f99ec65cf6c3a4fd2762b463e5ba21c43ab50a77e6b1f55eb90130638a3ba173ff052bee50f5cce92a2
7
+ data.tar.gz: ba249595b87ae32db7d8493277a69c54c2670dbbb10c2d5f2107176fdc9e54a09b177eb65c1b6eee28f48de7e5232aefafedcb8ae58b312c6b25857c94490490
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Salsify, Inc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -1,15 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/all'
2
4
  require 'active_record'
3
5
 
4
- require "rails_multitenant/global_context_registry"
5
- require "rails_multitenant/multitenant_model"
6
+ require 'rails_multitenant/global_context_registry'
7
+ require 'rails_multitenant/multitenant_model'
6
8
 
7
- require "rails_multitenant/middleware/extensions"
9
+ require 'rails_multitenant/middleware/extensions'
8
10
 
9
11
  module RailsMultitenant
10
12
  extend self
11
13
 
12
- delegate :get, :[], :fetch, :set, :[]=, :delete, :with_isolated_registry, to: :GlobalContextRegistry
14
+ delegate :get, :[], :fetch, :set, :[]=, :delete, :with_isolated_registry, :merge!, :with_merged_registry,
15
+ to: :GlobalContextRegistry
13
16
  end
14
17
 
15
18
  # rails_multitenant/rspec has to be explicitly included by clients who want to use it
@@ -1,3 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'global_context_registry/current'
4
+ require_relative 'global_context_registry/current_instance'
5
+ require_relative 'global_context_registry/registry_dependent_on'
6
+
1
7
  # Handles storing of global state that may be swapped out or unset at
2
8
  # various points. We use this in dandelion to store the current user,
3
9
  # org, catalog, etc.
@@ -6,204 +12,8 @@
6
12
  #
7
13
  module RailsMultitenant
8
14
  module GlobalContextRegistry
9
-
10
15
  extend self
11
16
 
12
- module RegistryDependentOn
13
-
14
- # Is this class dependent on changes in another GlobalContextRegistry-
15
- # stored object? Register that dependency here.
16
- def global_context_dependent_on(*klasses)
17
- klasses.each { |klass| GlobalContextRegistry.send(:add_dependency, klass, self) }
18
- end
19
- end
20
-
21
- # This module allows your to have a current, thread-local instance
22
- # of this class. It currently assumes your class has a zero-arg
23
- # constructor.
24
- module Current
25
- extend ActiveSupport::Concern
26
-
27
- included do
28
- class_attribute :default_provider, instance_writer: false
29
- end
30
-
31
- module ClassMethods
32
- def current
33
- GlobalContextRegistry.fetch(current_registry_obj) { __current_default }
34
- end
35
-
36
- def current=(object)
37
- raise "#{object} is not a #{self}" if object.present? && !object.is_a?(self)
38
- GlobalContextRegistry.set(current_registry_obj, object)
39
- __clear_dependents!
40
- end
41
-
42
- def current?
43
- GlobalContextRegistry.get(current_registry_obj).present?
44
- end
45
-
46
- def current!
47
- current || raise("No current #{name} set")
48
- end
49
-
50
- def clear_current!
51
- GlobalContextRegistry.delete(current_registry_obj)
52
- end
53
-
54
- def as_current(object)
55
- old_object = current if current?
56
- self.current = object
57
- yield
58
- ensure
59
- self.current = old_object
60
- end
61
-
62
- include RegistryDependentOn
63
-
64
- def provide_default(provider = nil, &block)
65
- self.default_provider = provider ? provider.to_proc : block
66
- end
67
-
68
- private
69
-
70
- def current_registry_obj
71
- return @current_registry_obj if @current_registry_obj
72
-
73
- @current_registry_obj = "#{__key_class.name.underscore}_obj".to_sym
74
- end
75
-
76
- def current_registry_default_provider
77
- "#{__key_class.name.underscore}_default_provider".to_sym
78
- end
79
-
80
- def __current_default
81
- if self.default_provider
82
- default = self.default_provider.call(self)
83
- raise "#{default} is not a #{self}" if default.present? && !default.is_a?(self)
84
- default
85
- end
86
- end
87
-
88
- def __clear_dependents!
89
- GlobalContextRegistry.send(:dependencies_for, __key_class).each(&:clear_current!)
90
- end
91
-
92
- def __key_class
93
- respond_to?(:base_class) ? base_class : self
94
- end
95
- end
96
-
97
- def as_current
98
- old_object = self.class.current if self.class.current?
99
- self.class.current = self
100
- yield
101
- ensure
102
- self.class.current = old_object
103
- end
104
-
105
- def current?
106
- self.class.current? && self.equal?(self.class.current)
107
- end
108
-
109
- end
110
-
111
- # This module allows you to have a current, thread-local instance
112
- # of a class. This module assumes that you are mixing into a Rails
113
- # model, and separately stores and id in thread local storage for
114
- # lazy loading.
115
- module CurrentInstance
116
- extend ActiveSupport::Concern
117
-
118
- module ClassMethods
119
- def current_id=(id)
120
- GlobalContextRegistry.delete(current_instance_registry_obj)
121
- GlobalContextRegistry.set(current_instance_registry_id, id)
122
- __clear_dependents!
123
- end
124
-
125
- def current=(object)
126
- raise "#{object} is not a #{self}" if object.present? && !object.is_a?(self)
127
- GlobalContextRegistry.set(current_instance_registry_obj, object)
128
- GlobalContextRegistry.set(current_instance_registry_id, object.try(:id))
129
- __clear_dependents!
130
- end
131
-
132
- def current_id
133
- GlobalContextRegistry.get(current_instance_registry_id)
134
- end
135
-
136
- def current
137
- GlobalContextRegistry.fetch(current_instance_registry_obj) do
138
- (current_id ? find(current_id) : nil)
139
- end
140
- end
141
-
142
- def current?
143
- !!current
144
- end
145
-
146
- def current!
147
- current || raise("No current #{name} set")
148
- end
149
-
150
- def as_current_id(id)
151
- old_id = current_id
152
- self.current_id = id
153
- yield
154
- ensure
155
- self.current_id = old_id
156
- end
157
-
158
- def as_current(model)
159
- old_model = current
160
- self.current = model
161
- yield
162
- ensure
163
- self.current = old_model
164
- end
165
-
166
- def clear_current!
167
- GlobalContextRegistry.delete(current_instance_registry_obj)
168
- end
169
-
170
- private
171
-
172
- def __clear_dependents!
173
- key_class = respond_to?(:base_class) ? base_class : self
174
- GlobalContextRegistry.send(:dependencies_for, key_class).each(&:clear_current!)
175
- end
176
-
177
- def current_instance_registry_id
178
- return @current_instance_registry_id if @current_instance_registry_id
179
-
180
- key_class = respond_to?(:base_class) ? base_class : self
181
- @current_instance_registry_id = "#{key_class.name.underscore}_id".to_sym
182
- end
183
-
184
- def current_instance_registry_obj
185
- return @current_instance_registry_obj if @current_instance_registry_obj
186
-
187
- key_class = respond_to?(:base_class) ? base_class : self
188
- @current_instance_registry_obj = "#{key_class.name.underscore}_obj".to_sym
189
- end
190
- include RegistryDependentOn
191
- end
192
-
193
- def as_current
194
- old_id = self.class.current_id
195
- self.class.current = self
196
- yield
197
- ensure
198
- self.class.current_id = old_id
199
- end
200
-
201
- def current?
202
- id == self.class.current_id
203
- end
204
-
205
- end
206
-
207
17
  # Set this global
208
18
  def set(symbol, value)
209
19
  globals[symbol] = value
@@ -231,10 +41,15 @@ module RailsMultitenant
231
41
  end
232
42
  alias_method :[], :get
233
43
 
44
+ # merge the given values into the registry
45
+ def merge!(values)
46
+ globals.merge!(values)
47
+ end
48
+
234
49
  # Duplicate the registry
235
50
  def duplicate_registry
236
51
  globals.each_with_object({}) do |(key, value), result|
237
- result[key] = (value.nil? || value.is_a?(Integer)) ? value : value.dup
52
+ result[key] = value.nil? || value.is_a?(Integer) ? value : value.dup
238
53
  end
239
54
  end
240
55
 
@@ -246,6 +61,14 @@ module RailsMultitenant
246
61
  self.globals = prior_globals
247
62
  end
248
63
 
64
+ # Run a block of code with the given values merged into the current registry
65
+ def with_merged_registry(values = {})
66
+ prior_globals = new_registry(globals.merge(values))
67
+ yield
68
+ ensure
69
+ self.globals = prior_globals
70
+ end
71
+
249
72
  # Prefer .with_isolated_registry to the following two methods.
250
73
  # Note: these methods are intended for use in a manner like .with_isolated_registry,
251
74
  # but in contexts where around semantics are not allowed.
@@ -262,6 +85,29 @@ module RailsMultitenant
262
85
  self.globals = registry
263
86
  end
264
87
 
88
+ # Run a block of code that disregards scoping during read queries
89
+ def with_unscoped_queries
90
+ with_merged_registry(__use_unscoped_queries: true) do
91
+ yield
92
+ end
93
+ end
94
+
95
+ def use_unscoped_queries?
96
+ self[:__use_unscoped_queries] == true
97
+ end
98
+
99
+ # Prefer .with_unscoped_queries to the following two methods.
100
+ # Note: these methods are intended for use in a manner like .with_admin_registry,
101
+ # but in contexts where around semantics are not allowed.
102
+
103
+ def disable_scoped_queries
104
+ self[:__use_unscoped_queries] = true
105
+ end
106
+
107
+ def enable_scoped_queries
108
+ self[:__use_unscoped_queries] = nil
109
+ end
110
+
265
111
  private
266
112
 
267
113
  @dependencies = {}
@@ -285,7 +131,6 @@ module RailsMultitenant
285
131
 
286
132
  def globals=(value)
287
133
  Thread.current[:global_context_registry] = value
288
- value
289
134
  end
290
135
  end
291
136
  end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'registry_dependent_on'
4
+
5
+ module RailsMultitenant
6
+ module GlobalContextRegistry
7
+ # This module allows your to have a current, thread-local instance
8
+ # of this class. It currently assumes your class has a zero-arg
9
+ # constructor.
10
+ module Current
11
+ extend ActiveSupport::Concern
12
+
13
+ included do
14
+ class_attribute :default_provider, instance_writer: false
15
+ end
16
+
17
+ module ClassMethods
18
+ def current
19
+ GlobalContextRegistry.fetch(current_registry_obj) { __current_default }
20
+ end
21
+
22
+ def current=(object)
23
+ raise "#{object} is not a #{self}" if object.present? && !object.is_a?(self)
24
+
25
+ GlobalContextRegistry.set(current_registry_obj, object)
26
+ __clear_dependents!
27
+ end
28
+
29
+ def current?
30
+ GlobalContextRegistry.get(current_registry_obj).present?
31
+ end
32
+
33
+ def current!
34
+ current || raise("No current #{name} set")
35
+ end
36
+
37
+ def clear_current!
38
+ GlobalContextRegistry.delete(current_registry_obj)
39
+ end
40
+
41
+ def as_current(object)
42
+ old_object = current if current?
43
+ self.current = object
44
+ yield
45
+ ensure
46
+ self.current = old_object
47
+ end
48
+
49
+ include RegistryDependentOn
50
+
51
+ def provide_default(provider = nil, &block)
52
+ self.default_provider = provider ? provider.to_proc : block
53
+ end
54
+
55
+ private
56
+
57
+ def current_registry_obj
58
+ return @current_registry_obj if @current_registry_obj
59
+
60
+ @current_registry_obj = "#{__key_class.name.underscore}_obj".to_sym
61
+ end
62
+
63
+ def current_registry_default_provider
64
+ "#{__key_class.name.underscore}_default_provider".to_sym
65
+ end
66
+
67
+ def __current_default
68
+ if default_provider
69
+ default = default_provider.call(self)
70
+ raise "#{default} is not a #{self}" if default.present? && !default.is_a?(self)
71
+
72
+ default
73
+ end
74
+ end
75
+
76
+ def __clear_dependents!
77
+ GlobalContextRegistry.send(:dependencies_for, __key_class).each(&:clear_current!)
78
+ end
79
+
80
+ def __key_class
81
+ respond_to?(:base_class) ? base_class : self
82
+ end
83
+ end
84
+
85
+ def as_current
86
+ old_object = self.class.current if self.class.current?
87
+ self.class.current = self
88
+ yield
89
+ ensure
90
+ self.class.current = old_object
91
+ end
92
+
93
+ def current?
94
+ self.class.current? && equal?(self.class.current)
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'registry_dependent_on'
4
+
5
+ module RailsMultitenant
6
+ module GlobalContextRegistry
7
+ # This module allows you to have a current, thread-local instance
8
+ # of a class. This module assumes that you are mixing into a Rails
9
+ # model, and separately stores and id in thread local storage for
10
+ # lazy loading.
11
+ module CurrentInstance
12
+ extend ActiveSupport::Concern
13
+
14
+ module ClassMethods
15
+ def current_id=(id)
16
+ GlobalContextRegistry.delete(current_instance_registry_obj)
17
+ GlobalContextRegistry.set(current_instance_registry_id, id)
18
+ __clear_dependents!
19
+ end
20
+
21
+ def current=(object)
22
+ raise "#{object} is not a #{self}" if object.present? && !object.is_a?(self)
23
+
24
+ GlobalContextRegistry.set(current_instance_registry_obj, object)
25
+ GlobalContextRegistry.set(current_instance_registry_id, object.try(:id))
26
+ __clear_dependents!
27
+ end
28
+
29
+ def current_id
30
+ GlobalContextRegistry.get(current_instance_registry_id)
31
+ end
32
+
33
+ def current
34
+ GlobalContextRegistry.fetch(current_instance_registry_obj) do
35
+ (current_id ? find(current_id) : nil)
36
+ end
37
+ end
38
+
39
+ def current?
40
+ !!current
41
+ end
42
+
43
+ def current!
44
+ current || raise("No current #{name} set")
45
+ end
46
+
47
+ def as_current_id(id)
48
+ old_id = current_id
49
+ self.current_id = id
50
+ yield
51
+ ensure
52
+ self.current_id = old_id
53
+ end
54
+
55
+ def as_current(model)
56
+ old_model = current
57
+ self.current = model
58
+ yield
59
+ ensure
60
+ self.current = old_model
61
+ end
62
+
63
+ def clear_current!
64
+ GlobalContextRegistry.delete(current_instance_registry_obj)
65
+ end
66
+
67
+ private
68
+
69
+ def __clear_dependents!
70
+ key_class = respond_to?(:base_class) ? base_class : self
71
+ GlobalContextRegistry.send(:dependencies_for, key_class).each(&:clear_current!)
72
+ end
73
+
74
+ def current_instance_registry_id
75
+ return @current_instance_registry_id if @current_instance_registry_id
76
+
77
+ key_class = respond_to?(:base_class) ? base_class : self
78
+ @current_instance_registry_id = "#{key_class.name.underscore}_id".to_sym
79
+ end
80
+
81
+ def current_instance_registry_obj
82
+ return @current_instance_registry_obj if @current_instance_registry_obj
83
+
84
+ key_class = respond_to?(:base_class) ? base_class : self
85
+ @current_instance_registry_obj = "#{key_class.name.underscore}_obj".to_sym
86
+ end
87
+ include RegistryDependentOn
88
+ end
89
+
90
+ def as_current
91
+ old_id = self.class.current_id
92
+ self.class.current = self
93
+ yield
94
+ ensure
95
+ self.class.current_id = old_id
96
+ end
97
+
98
+ def current?
99
+ id == self.class.current_id
100
+ end
101
+ end
102
+ end
103
+ end