rails_multitenant 0.11.0 → 0.15.0

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