activerecord-multi-tenant 2.2.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/active-record-multi-tenant-tests.yml +80 -0
- data/.gitignore +6 -0
- data/.readthedocs.yaml +15 -0
- data/.rspec +0 -0
- data/.rubocop.yml +51 -0
- data/Appraisals +0 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +3 -1
- data/LICENSE +0 -0
- data/README.md +2 -1
- data/Rakefile +1 -1
- data/activerecord-multi-tenant.gemspec +28 -22
- data/docker-compose.yml +24 -18
- data/docs/.gitignore +3 -0
- data/docs/Makefile +28 -0
- data/docs/api-reference.sh +10 -0
- data/docs/requirements.in +4 -0
- data/docs/requirements.txt +62 -0
- data/docs/source/_static/api-reference/ActiveRecord/Associations/Association.html +285 -0
- data/docs/source/_static/api-reference/ActiveRecord/Associations/ClassMethods.html +255 -0
- data/docs/source/_static/api-reference/ActiveRecord/Associations.html +117 -0
- data/docs/source/_static/api-reference/ActiveRecord/ConnectionAdapters/SchemaStatements.html +232 -0
- data/docs/source/_static/api-reference/ActiveRecord/ConnectionAdapters.html +126 -0
- data/docs/source/_static/api-reference/ActiveRecord/QueryMethods.html +336 -0
- data/docs/source/_static/api-reference/ActiveRecord/SchemaDumper.html +121 -0
- data/docs/source/_static/api-reference/ActiveRecord.html +130 -0
- data/docs/source/_static/api-reference/MultiTenant/ArelTenantVisitor.html +755 -0
- data/docs/source/_static/api-reference/MultiTenant/ArelVisitorsDepthFirst.html +208 -0
- data/docs/source/_static/api-reference/MultiTenant/BaseTenantEnforcementClause.html +462 -0
- data/docs/source/_static/api-reference/MultiTenant/Context.html +659 -0
- data/docs/source/_static/api-reference/MultiTenant/ControllerExtensions.html +202 -0
- data/docs/source/_static/api-reference/MultiTenant/CopyFromClient.html +186 -0
- data/docs/source/_static/api-reference/MultiTenant/CopyFromClientHelper.html +362 -0
- data/docs/source/_static/api-reference/MultiTenant/Current.html +124 -0
- data/docs/source/_static/api-reference/MultiTenant/DatabaseStatements.html +366 -0
- data/docs/source/_static/api-reference/MultiTenant/FastTruncate.html +226 -0
- data/docs/source/_static/api-reference/MultiTenant/MigrationExtensions.html +554 -0
- data/docs/source/_static/api-reference/MultiTenant/MissingTenantError.html +124 -0
- data/docs/source/_static/api-reference/MultiTenant/ModelExtensionsClassMethods.html +492 -0
- data/docs/source/_static/api-reference/MultiTenant/QueryMonitor.html +257 -0
- data/docs/source/_static/api-reference/MultiTenant/Table.html +419 -0
- data/docs/source/_static/api-reference/MultiTenant/TenantEnforcementClause.html +148 -0
- data/docs/source/_static/api-reference/MultiTenant/TenantIsImmutable.html +135 -0
- data/docs/source/_static/api-reference/MultiTenant/TenantJoinEnforcementClause.html +310 -0
- data/docs/source/_static/api-reference/MultiTenant/TenantValueVisitor.html +239 -0
- data/docs/source/_static/api-reference/MultiTenant.html +1454 -0
- data/docs/source/_static/api-reference/MultiTenantFindBy.html +180 -0
- data/docs/source/_static/api-reference/Sidekiq/Client.html +302 -0
- data/docs/source/_static/api-reference/Sidekiq/Middleware/MultiTenant/Client.html +217 -0
- data/docs/source/_static/api-reference/Sidekiq/Middleware/MultiTenant/Server.html +219 -0
- data/docs/source/_static/api-reference/Sidekiq/Middleware/MultiTenant.html +126 -0
- data/docs/source/_static/api-reference/Sidekiq.html +126 -0
- data/docs/source/_static/api-reference/_index.html +399 -0
- data/docs/source/_static/api-reference/class_list.html +51 -0
- data/docs/source/_static/api-reference/css/common.css +1 -0
- data/docs/source/_static/api-reference/css/full_list.css +58 -0
- data/docs/source/_static/api-reference/css/style.css +497 -0
- data/docs/source/_static/api-reference/file.README.html +167 -0
- data/docs/source/_static/api-reference/file_list.html +56 -0
- data/docs/source/_static/api-reference/frames.html +17 -0
- data/docs/source/_static/api-reference/index.html +167 -0
- data/docs/source/_static/api-reference/js/app.js +314 -0
- data/docs/source/_static/api-reference/js/full_list.js +216 -0
- data/docs/source/_static/api-reference/js/jquery.js +4 -0
- data/docs/source/_static/api-reference/method_list.html +715 -0
- data/docs/source/_static/api-reference/top-level-namespace.html +126 -0
- data/docs/source/_templates/.gitignore +4 -0
- data/docs/source/api-reference.rst +8 -0
- data/docs/source/appendix.rst +26 -0
- data/docs/source/changelog.rst +8 -0
- data/docs/source/community-and-support.rst +26 -0
- data/docs/source/conf.py +30 -0
- data/docs/source/contributing.rst +70 -0
- data/docs/source/getting-started.rst +37 -0
- data/docs/source/guides-and-tutorials.rst +129 -0
- data/docs/source/index.rst +54 -0
- data/docs/source/introduction.rst +33 -0
- data/docs/source/license.rst +22 -0
- data/docs/source/troubleshooting.rst +41 -0
- data/docs/source/usage-guide.rst +59 -0
- data/lib/activerecord-multi-tenant/arel_visitors_depth_first.rb +183 -174
- data/lib/activerecord-multi-tenant/controller_extensions.rb +15 -4
- data/lib/activerecord-multi-tenant/copy_from_client.rb +4 -0
- data/lib/activerecord-multi-tenant/fast_truncate.rb +4 -2
- data/lib/activerecord-multi-tenant/habtm.rb +50 -0
- data/lib/activerecord-multi-tenant/migrations.rb +18 -8
- data/lib/activerecord-multi-tenant/model_extensions.rb +78 -37
- data/lib/activerecord-multi-tenant/multi_tenant.rb +40 -21
- data/lib/activerecord-multi-tenant/query_monitor.rb +21 -5
- data/lib/activerecord-multi-tenant/query_rewriter.rb +111 -80
- data/lib/activerecord-multi-tenant/sidekiq.rb +31 -20
- data/lib/activerecord-multi-tenant/version.rb +1 -1
- data/lib/activerecord-multi-tenant.rb +3 -12
- data/lib/activerecord_multi_tenant.rb +12 -0
- data/spec/activerecord-multi-tenant/associations_spec.rb +21 -0
- data/spec/activerecord-multi-tenant/controller_extensions_spec.rb +3 -2
- data/spec/activerecord-multi-tenant/fast_truncate_spec.rb +8 -6
- data/spec/activerecord-multi-tenant/model_extensions_spec.rb +233 -153
- data/spec/activerecord-multi-tenant/multi_tenant_spec.rb +15 -13
- data/spec/activerecord-multi-tenant/query_rewriter_spec.rb +60 -59
- data/spec/activerecord-multi-tenant/record_callback_spec.rb +0 -0
- data/spec/activerecord-multi-tenant/record_finding_spec.rb +11 -11
- data/spec/activerecord-multi-tenant/record_modifications_spec.rb +4 -4
- data/spec/activerecord-multi-tenant/sidekiq_spec.rb +10 -10
- data/spec/database.yml +0 -0
- data/spec/schema.rb +20 -2
- data/spec/spec_helper.rb +46 -17
- data/spec/support/format_sql.rb +20 -0
- metadata +130 -25
- data/.github/workflows/CI.yml +0 -47
- data/gemfiles/.bundle/config +0 -2
- data/gemfiles/active_record_6.0.gemfile +0 -8
- data/gemfiles/active_record_6.1.gemfile +0 -8
- data/gemfiles/active_record_7.0.gemfile +0 -8
- data/gemfiles/rails_6.0.gemfile +0 -8
- data/gemfiles/rails_6.1.gemfile +0 -8
- data/gemfiles/rails_7.0.gemfile +0 -8
- data/lib/activerecord-multi-tenant/with_lock.rb +0 -15
- data/spec/activerecord-multi-tenant/schema_dumper_tester.rb +0 -0
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'active_record'
|
2
|
-
require_relative
|
2
|
+
require_relative './arel_visitors_depth_first' unless Arel::Visitors.const_defined?(:DepthFirst)
|
3
3
|
|
4
|
+
# Iterates AST and adds tenant enforcement clauses to all relations
|
4
5
|
module MultiTenant
|
5
6
|
class Table
|
6
7
|
attr_reader :arel_table
|
@@ -9,9 +10,9 @@ module MultiTenant
|
|
9
10
|
@arel_table = arel_table
|
10
11
|
end
|
11
12
|
|
12
|
-
def eql?(
|
13
|
-
self.class ==
|
14
|
-
equality_fields.eql?(
|
13
|
+
def eql?(other)
|
14
|
+
self.class == other.class &&
|
15
|
+
equality_fields.eql?(other.equality_fields)
|
15
16
|
end
|
16
17
|
|
17
18
|
def hash
|
@@ -44,6 +45,7 @@ module MultiTenant
|
|
44
45
|
|
45
46
|
def visited_relation(relation)
|
46
47
|
return unless @discovering
|
48
|
+
|
47
49
|
@known_relations << Table.new(relation)
|
48
50
|
end
|
49
51
|
|
@@ -56,9 +58,13 @@ module MultiTenant
|
|
56
58
|
end
|
57
59
|
end
|
58
60
|
|
59
|
-
class ArelTenantVisitor < Arel::Visitors.const_defined?(:DepthFirst)
|
61
|
+
class ArelTenantVisitor < if Arel::Visitors.const_defined?(:DepthFirst)
|
62
|
+
Arel::Visitors::DepthFirst
|
63
|
+
else
|
64
|
+
::MultiTenant::ArelVisitorsDepthFirst
|
65
|
+
end
|
60
66
|
def initialize(arel)
|
61
|
-
super(
|
67
|
+
super(proc {})
|
62
68
|
@statement_node_id = nil
|
63
69
|
|
64
70
|
@contexts = []
|
@@ -68,61 +74,72 @@ module MultiTenant
|
|
68
74
|
|
69
75
|
attr_reader :contexts
|
70
76
|
|
77
|
+
# rubocop:disable Naming/MethodName
|
71
78
|
def visit_Arel_Attributes_Attribute(*args)
|
72
79
|
return if @current_context.nil?
|
80
|
+
|
73
81
|
super(*args)
|
74
82
|
end
|
75
83
|
|
76
|
-
def visit_Arel_Nodes_Equality(
|
77
|
-
if
|
78
|
-
table_name =
|
84
|
+
def visit_Arel_Nodes_Equality(obj, *args)
|
85
|
+
if obj.left.is_a?(Arel::Attributes::Attribute)
|
86
|
+
table_name = obj.left.relation.table_name
|
79
87
|
model = MultiTenant.multi_tenant_model_for_table(table_name)
|
80
|
-
|
88
|
+
if model.present? && obj.left.name.to_s == model.partition_key.to_s
|
89
|
+
@current_context.visited_handled_relation(obj.left.relation)
|
90
|
+
end
|
81
91
|
end
|
82
|
-
super(
|
92
|
+
super(obj, *args)
|
83
93
|
end
|
84
94
|
|
85
|
-
def visit_MultiTenant_TenantEnforcementClause(
|
86
|
-
@current_context.visited_handled_relation(
|
95
|
+
def visit_MultiTenant_TenantEnforcementClause(obj, *)
|
96
|
+
@current_context.visited_handled_relation(obj.tenant_attribute.relation)
|
87
97
|
end
|
88
98
|
|
89
|
-
def visit_MultiTenant_TenantJoinEnforcementClause(
|
90
|
-
@current_context.visited_handled_relation(
|
99
|
+
def visit_MultiTenant_TenantJoinEnforcementClause(obj, *)
|
100
|
+
@current_context.visited_handled_relation(obj.tenant_attribute.relation)
|
91
101
|
end
|
92
102
|
|
93
|
-
def visit_Arel_Table(
|
94
|
-
@current_context.visited_relation(
|
103
|
+
def visit_Arel_Table(obj, _collector = nil)
|
104
|
+
@current_context.visited_relation(obj) if tenant_relation?(obj.table_name)
|
95
105
|
end
|
96
|
-
alias :visit_Arel_Nodes_TableAlias :visit_Arel_Table
|
97
106
|
|
98
|
-
|
99
|
-
|
107
|
+
alias visit_Arel_Nodes_TableAlias visit_Arel_Table
|
108
|
+
|
109
|
+
def visit_Arel_Nodes_SelectCore(obj, *_args)
|
110
|
+
nest_context(obj) do
|
100
111
|
@current_context.discover_relations do
|
101
|
-
visit
|
112
|
+
visit obj.source
|
102
113
|
end
|
103
|
-
visit
|
104
|
-
visit
|
105
|
-
visit
|
106
|
-
if defined?(
|
107
|
-
visit
|
114
|
+
visit obj.wheres
|
115
|
+
visit obj.groups
|
116
|
+
visit obj.windows
|
117
|
+
if defined?(obj.having)
|
118
|
+
visit obj.having
|
108
119
|
else
|
109
|
-
visit
|
120
|
+
visit obj.havings
|
110
121
|
end
|
111
122
|
end
|
112
123
|
end
|
113
124
|
|
114
|
-
|
115
|
-
|
125
|
+
# rubocop:enable Naming/MethodName
|
126
|
+
|
127
|
+
# rubocop:disable Naming/MethodName
|
128
|
+
def visit_Arel_Nodes_OuterJoin(obj, _collector = nil)
|
129
|
+
nest_context(obj) do
|
116
130
|
@current_context.discover_relations do
|
117
|
-
visit
|
118
|
-
visit
|
131
|
+
visit obj.left
|
132
|
+
visit obj.right
|
119
133
|
end
|
120
134
|
end
|
121
135
|
end
|
122
|
-
alias :visit_Arel_Nodes_FullOuterJoin :visit_Arel_Nodes_OuterJoin
|
123
|
-
alias :visit_Arel_Nodes_RightOuterJoin :visit_Arel_Nodes_OuterJoin
|
124
136
|
|
125
|
-
|
137
|
+
# rubocop:enable Naming/MethodName
|
138
|
+
|
139
|
+
alias visit_Arel_Nodes_FullOuterJoin visit_Arel_Nodes_OuterJoin
|
140
|
+
alias visit_Arel_Nodes_RightOuterJoin visit_Arel_Nodes_OuterJoin
|
141
|
+
|
142
|
+
alias visit_ActiveModel_Attribute terminal
|
126
143
|
|
127
144
|
private
|
128
145
|
|
@@ -138,13 +155,16 @@ module MultiTenant
|
|
138
155
|
DISPATCH
|
139
156
|
end
|
140
157
|
|
158
|
+
# rubocop:disable Naming/AccessorMethodName
|
141
159
|
def get_dispatch_cache
|
142
160
|
dispatch
|
143
161
|
end
|
144
162
|
|
145
|
-
|
163
|
+
# rubocop:enable Naming/AccessorMethodName
|
164
|
+
|
165
|
+
def nest_context(obj)
|
146
166
|
old_context = @current_context
|
147
|
-
@current_context = Context.new(
|
167
|
+
@current_context = Context.new(obj)
|
148
168
|
@contexts << @current_context
|
149
169
|
|
150
170
|
yield
|
@@ -155,25 +175,31 @@ module MultiTenant
|
|
155
175
|
|
156
176
|
class BaseTenantEnforcementClause < Arel::Nodes::Node
|
157
177
|
attr_reader :tenant_attribute
|
178
|
+
|
158
179
|
def initialize(tenant_attribute)
|
180
|
+
super()
|
159
181
|
@tenant_attribute = tenant_attribute
|
160
182
|
@tenant_model = MultiTenant.multi_tenant_model_for_table(tenant_attribute.relation.table_name)
|
161
183
|
end
|
162
184
|
|
163
|
-
def to_s
|
164
|
-
|
185
|
+
def to_s
|
186
|
+
to_sql
|
187
|
+
end
|
188
|
+
|
189
|
+
def to_str
|
190
|
+
to_sql
|
191
|
+
end
|
165
192
|
|
166
193
|
def to_sql(*)
|
167
194
|
collector = Arel::Collectors::SQLString.new
|
168
195
|
collector = @tenant_model.connection.visitor.accept tenant_arel, collector
|
169
196
|
collector.value
|
170
197
|
end
|
171
|
-
|
172
|
-
|
173
198
|
end
|
174
199
|
|
175
200
|
class TenantEnforcementClause < BaseTenantEnforcementClause
|
176
201
|
private
|
202
|
+
|
177
203
|
def tenant_arel
|
178
204
|
if defined?(Arel::Nodes::Quoted)
|
179
205
|
@tenant_attribute.eq(Arel::Nodes::Quoted.new(MultiTenant.current_tenant_id))
|
@@ -183,9 +209,9 @@ module MultiTenant
|
|
183
209
|
end
|
184
210
|
end
|
185
211
|
|
186
|
-
|
187
212
|
class TenantJoinEnforcementClause < BaseTenantEnforcementClause
|
188
213
|
attr_reader :table_left
|
214
|
+
|
189
215
|
def initialize(tenant_attribute, table_left)
|
190
216
|
super(tenant_attribute)
|
191
217
|
@table_left = table_left
|
@@ -193,20 +219,23 @@ module MultiTenant
|
|
193
219
|
end
|
194
220
|
|
195
221
|
private
|
222
|
+
|
196
223
|
def tenant_arel
|
197
224
|
@tenant_attribute.eq(@table_left[@model_left.partition_key])
|
198
225
|
end
|
199
226
|
end
|
200
227
|
|
201
|
-
|
202
228
|
module TenantValueVisitor
|
203
|
-
|
204
|
-
|
229
|
+
# rubocop:disable Naming/MethodName
|
230
|
+
def visit_MultiTenant_TenantEnforcementClause(obj, collector)
|
231
|
+
collector << obj
|
205
232
|
end
|
206
233
|
|
207
|
-
def visit_MultiTenant_TenantJoinEnforcementClause(
|
208
|
-
collector <<
|
234
|
+
def visit_MultiTenant_TenantJoinEnforcementClause(obj, collector)
|
235
|
+
collector << obj
|
209
236
|
end
|
237
|
+
|
238
|
+
# rubocop:enable Naming/MethodName
|
210
239
|
end
|
211
240
|
|
212
241
|
module DatabaseStatements
|
@@ -254,11 +283,12 @@ Arel::Visitors::ToSql.include(MultiTenant::TenantValueVisitor)
|
|
254
283
|
require 'active_record/relation'
|
255
284
|
module ActiveRecord
|
256
285
|
module QueryMethods
|
257
|
-
alias
|
286
|
+
alias build_arel_orig build_arel
|
287
|
+
|
258
288
|
def build_arel(*args)
|
259
289
|
arel = build_arel_orig(*args)
|
260
290
|
|
261
|
-
|
291
|
+
unless MultiTenant.with_write_only_mode_enabled?
|
262
292
|
visitor = MultiTenant::ArelTenantVisitor.new(arel)
|
263
293
|
|
264
294
|
visitor.contexts.each do |context|
|
@@ -270,45 +300,44 @@ module ActiveRecord
|
|
270
300
|
if MultiTenant.current_tenant_id
|
271
301
|
enforcement_clause = MultiTenant::TenantEnforcementClause.new(relation.arel_table[model.partition_key])
|
272
302
|
case node
|
273
|
-
when Arel::Nodes::Join #Arel::Nodes::OuterJoin, Arel::Nodes::RightOuterJoin, Arel::Nodes::FullOuterJoin
|
303
|
+
when Arel::Nodes::Join # Arel::Nodes::OuterJoin, Arel::Nodes::RightOuterJoin, Arel::Nodes::FullOuterJoin
|
274
304
|
node.right.expr = node.right.expr.and(enforcement_clause)
|
275
305
|
when Arel::Nodes::SelectCore
|
276
306
|
if node.wheres.empty?
|
277
307
|
node.wheres = [enforcement_clause]
|
308
|
+
elsif node.wheres[0].is_a?(Arel::Nodes::And)
|
309
|
+
node.wheres[0].children << enforcement_clause
|
278
310
|
else
|
279
|
-
|
280
|
-
node.wheres[0].children << enforcement_clause
|
281
|
-
else
|
282
|
-
node.wheres[0] = enforcement_clause.and(node.wheres[0])
|
283
|
-
end
|
311
|
+
node.wheres[0] = enforcement_clause.and(node.wheres[0])
|
284
312
|
end
|
285
313
|
else
|
286
|
-
raise
|
314
|
+
raise 'UnknownContext'
|
287
315
|
end
|
288
316
|
end
|
289
317
|
|
290
|
-
|
291
|
-
if node.is_a?Arel::Nodes::Join
|
292
|
-
node_list = [node]
|
293
|
-
else
|
294
|
-
node_list = node.source.right
|
295
|
-
end
|
318
|
+
next unless node.is_a?(Arel::Nodes::SelectCore) || node.is_a?(Arel::Nodes::Join)
|
296
319
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
320
|
+
node_list = if node.is_a? Arel::Nodes::Join
|
321
|
+
[node]
|
322
|
+
else
|
323
|
+
node.source.right
|
324
|
+
end
|
302
325
|
|
303
|
-
|
326
|
+
node_list.select { |n| n.is_a? Arel::Nodes::Join }.each do |node_join|
|
327
|
+
next unless node_join.right
|
304
328
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
329
|
+
relation_right, relation_left = relations_from_node_join(node_join)
|
330
|
+
|
331
|
+
next unless relation_right && relation_left
|
332
|
+
|
333
|
+
model_right = MultiTenant.multi_tenant_model_for_table(relation_left.table_name)
|
334
|
+
model_left = MultiTenant.multi_tenant_model_for_table(relation_right.table_name)
|
335
|
+
next unless model_right && model_left
|
336
|
+
|
337
|
+
join_enforcement_clause = MultiTenant::TenantJoinEnforcementClause.new(
|
338
|
+
relation_right[model_right.partition_key], relation_left
|
339
|
+
)
|
340
|
+
node_join.right.expr = node_join.right.expr.and(join_enforcement_clause)
|
312
341
|
end
|
313
342
|
end
|
314
343
|
end
|
@@ -318,6 +347,7 @@ module ActiveRecord
|
|
318
347
|
end
|
319
348
|
|
320
349
|
private
|
350
|
+
|
321
351
|
def relations_from_node_join(node_join)
|
322
352
|
if node_join.right.expr.is_a?(Arel::Nodes::Equality)
|
323
353
|
return node_join.right.expr.right.relation, node_join.right.expr.left.relation
|
@@ -325,22 +355,21 @@ module ActiveRecord
|
|
325
355
|
|
326
356
|
children = [node_join.right.expr.children].flatten
|
327
357
|
|
328
|
-
tenant_applied = children.any?
|
329
|
-
|
330
|
-
return nil, nil
|
358
|
+
tenant_applied = children.any? do |c|
|
359
|
+
c.is_a?(MultiTenant::TenantEnforcementClause) || c.is_a?(MultiTenant::TenantJoinEnforcementClause)
|
331
360
|
end
|
361
|
+
return nil, nil if tenant_applied || children.empty?
|
332
362
|
|
333
363
|
child = children.first.respond_to?(:children) ? children.first.children.first : children.first
|
334
364
|
if child.right.respond_to?(:relation) && child.left.respond_to?(:relation)
|
335
365
|
return child.right.relation, child.left.relation
|
336
366
|
end
|
337
367
|
|
338
|
-
|
368
|
+
[nil, nil]
|
339
369
|
end
|
340
370
|
end
|
341
371
|
end
|
342
372
|
|
343
|
-
require 'active_record/base'
|
344
373
|
module MultiTenantFindBy
|
345
374
|
def cached_find_by_statement(key, &block)
|
346
375
|
return super unless respond_to?(:scoped_by_tenant?) && scoped_by_tenant?
|
@@ -350,4 +379,6 @@ module MultiTenantFindBy
|
|
350
379
|
end
|
351
380
|
end
|
352
381
|
|
353
|
-
|
382
|
+
ActiveSupport.on_load(:active_record) do |base|
|
383
|
+
base.singleton_class.prepend(MultiTenantFindBy)
|
384
|
+
end
|
@@ -1,14 +1,17 @@
|
|
1
1
|
require 'sidekiq/client'
|
2
2
|
|
3
|
+
# Adds methods to handle tenant information both in the client and server.
|
3
4
|
module Sidekiq::Middleware::MultiTenant
|
4
5
|
# Get the current tenant and store in the message to be sent to Sidekiq.
|
5
6
|
class Client
|
6
|
-
def call(
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
def call(_worker_class, msg, _queue, _redis_pool)
|
8
|
+
if MultiTenant.current_tenant.present?
|
9
|
+
msg['multi_tenant'] ||=
|
10
|
+
{
|
11
|
+
'class' => MultiTenant.current_tenant_class,
|
12
|
+
'id' => MultiTenant.current_tenant_id
|
13
|
+
}
|
14
|
+
end
|
12
15
|
|
13
16
|
yield
|
14
17
|
end
|
@@ -16,16 +19,14 @@ module Sidekiq::Middleware::MultiTenant
|
|
16
19
|
|
17
20
|
# Pull the tenant out and run the current thread with it.
|
18
21
|
class Server
|
19
|
-
def call(
|
20
|
-
if msg.
|
22
|
+
def call(_worker_class, msg, _queue, &block)
|
23
|
+
if msg.key?('multi_tenant')
|
21
24
|
tenant = begin
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
26
|
-
MultiTenant.with(tenant) do
|
27
|
-
yield
|
25
|
+
msg['multi_tenant']['class'].constantize.find(msg['multi_tenant']['id'])
|
26
|
+
rescue ActiveRecord::RecordNotFound
|
27
|
+
msg['multi_tenant']['id']
|
28
28
|
end
|
29
|
+
MultiTenant.with(tenant, &block)
|
29
30
|
else
|
30
31
|
yield
|
31
32
|
end
|
@@ -33,6 +34,8 @@ module Sidekiq::Middleware::MultiTenant
|
|
33
34
|
end
|
34
35
|
end
|
35
36
|
|
37
|
+
# Configure Sidekiq to use the multi-tenant client and server middleware to add (client/server)/process(server)
|
38
|
+
# tenant information.
|
36
39
|
Sidekiq.configure_server do |config|
|
37
40
|
config.server_middleware do |chain|
|
38
41
|
chain.add Sidekiq::Middleware::MultiTenant::Server
|
@@ -48,27 +51,35 @@ Sidekiq.configure_client do |config|
|
|
48
51
|
end
|
49
52
|
end
|
50
53
|
|
51
|
-
|
54
|
+
# Bulk push support for Sidekiq while setting multi-tenant information.
|
55
|
+
# This is a copy of the Sidekiq::Client#push_bulk method with the addition of
|
56
|
+
# setting the multi-tenant information for each job.
|
52
57
|
module Sidekiq
|
53
58
|
class Client
|
59
|
+
# Allows the caller to enqueue multiple Sidekiq jobs with
|
60
|
+
# tenant information in a single call. It ensures that each job is processed
|
61
|
+
# within the correct tenant context and returns an array of job IDs for the enqueued jobs
|
54
62
|
def push_bulk_with_tenants(items)
|
55
|
-
|
56
|
-
return [] unless
|
57
|
-
|
63
|
+
first_job = items['jobs'].first
|
64
|
+
return [] unless first_job # no jobs to push
|
65
|
+
unless first_job.is_a?(Hash)
|
66
|
+
raise ArgumentError, "Bulk arguments must be an Array of Hashes: [{ 'args' => [1], 'tenant_id' => 1 }, ...]"
|
67
|
+
end
|
58
68
|
|
59
69
|
normed = normalize_item(items.except('jobs').merge('args' => []))
|
60
70
|
payloads = items['jobs'].map do |job|
|
61
71
|
MultiTenant.with(job['tenant_id']) do
|
62
72
|
copy = normed.merge('args' => job['args'], 'jid' => SecureRandom.hex(12), 'enqueued_at' => Time.now.to_f)
|
63
73
|
result = process_single(items['class'], copy)
|
64
|
-
result
|
74
|
+
result || nil
|
65
75
|
end
|
66
76
|
end.compact
|
67
77
|
|
68
|
-
raw_push(payloads)
|
78
|
+
raw_push(payloads) unless payloads.empty?
|
69
79
|
payloads.collect { |payload| payload['jid'] }
|
70
80
|
end
|
71
81
|
|
82
|
+
# Enabling the push_bulk_with_tenants method to be called directly on the Sidekiq::Client class
|
72
83
|
class << self
|
73
84
|
def push_bulk_with_tenants(items)
|
74
85
|
new.push_bulk_with_tenants(items)
|
@@ -1,12 +1,3 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
require_relative 'activerecord-multi-tenant/copy_from_client'
|
5
|
-
require_relative 'activerecord-multi-tenant/fast_truncate'
|
6
|
-
require_relative 'activerecord-multi-tenant/migrations'
|
7
|
-
require_relative 'activerecord-multi-tenant/model_extensions'
|
8
|
-
require_relative 'activerecord-multi-tenant/multi_tenant'
|
9
|
-
require_relative 'activerecord-multi-tenant/query_rewriter'
|
10
|
-
require_relative 'activerecord-multi-tenant/query_monitor'
|
11
|
-
require_relative 'activerecord-multi-tenant/version'
|
12
|
-
require_relative 'activerecord-multi-tenant/with_lock'
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'activerecord_multi_tenant'
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'activerecord-multi-tenant/controller_extensions' if Object.const_defined?(:ActionController)
|
4
|
+
require_relative 'activerecord-multi-tenant/copy_from_client'
|
5
|
+
require_relative 'activerecord-multi-tenant/fast_truncate'
|
6
|
+
require_relative 'activerecord-multi-tenant/migrations'
|
7
|
+
require_relative 'activerecord-multi-tenant/model_extensions'
|
8
|
+
require_relative 'activerecord-multi-tenant/multi_tenant'
|
9
|
+
require_relative 'activerecord-multi-tenant/query_rewriter'
|
10
|
+
require_relative 'activerecord-multi-tenant/query_monitor'
|
11
|
+
require_relative 'activerecord-multi-tenant/version'
|
12
|
+
require_relative 'activerecord-multi-tenant/habtm'
|
@@ -5,8 +5,11 @@ describe MultiTenant, 'Association methods' do
|
|
5
5
|
let(:account2) { Account.create! name: 'test2' }
|
6
6
|
let(:project1) { Project.create! name: 'something1', account: account1 }
|
7
7
|
let(:project2) { Project.create! name: 'something2', account: account2, id: project1.id }
|
8
|
+
|
8
9
|
let(:task1) { Task.create! name: 'task1', project: project1, account: account1 }
|
9
10
|
let(:task2) { Task.create! name: 'task2', project: project2, account: account2, id: task1.id }
|
11
|
+
let(:manager1) { Manager.create! name: 'manager1', account: account1, tasks: [task1] }
|
12
|
+
let(:project3) { Project.create! name: 'something3', account: account1, managers: [manager1] }
|
10
13
|
|
11
14
|
context 'include the tenant_id in queries and' do
|
12
15
|
it 'creates a task with correct account_id' do
|
@@ -17,5 +20,23 @@ describe MultiTenant, 'Association methods' do
|
|
17
20
|
expect(project2.tasks.count).to eq(1)
|
18
21
|
expect(project2.tasks.first.account_id).to eq(account2.id) # has_many
|
19
22
|
end
|
23
|
+
|
24
|
+
it 'check has_many_belongs_to' do
|
25
|
+
MultiTenant.with(account1) do
|
26
|
+
expect(manager1.tasks.first.account_id).to eq(task1.account_id) # has_many
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'check has_many_belongs_to without tenant in the intermediate table' do
|
31
|
+
MultiTenant.with(account1) do
|
32
|
+
expect(manager1.tasks.first.account_id).to eq(task1.account_id) # has_many
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'check has_many_belongs_to tenant_enabled false' do
|
37
|
+
MultiTenant.with(account1) do
|
38
|
+
expect(project3.managers.first.id).to eq(manager1.id) # has_many
|
39
|
+
end
|
40
|
+
end
|
20
41
|
end
|
21
42
|
end
|
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
|
-
describe
|
5
|
+
describe 'Controller Extensions', type: :controller do
|
4
6
|
class Account
|
5
7
|
attr_accessor :name
|
6
8
|
end
|
@@ -31,7 +33,6 @@ describe "Controller Extensions", type: :controller do
|
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
34
|
-
|
35
36
|
class APIApplicationController < ActionController::API
|
36
37
|
include Rails.application.routes.url_helpers
|
37
38
|
set_current_tenant_through_filter
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe MultiTenant::FastTruncate do
|
@@ -5,19 +7,19 @@ describe MultiTenant::FastTruncate do
|
|
5
7
|
MultiTenant::FastTruncate.run
|
6
8
|
end
|
7
9
|
|
8
|
-
it
|
10
|
+
it 'truncates tables that have exactly one row inserted' do
|
9
11
|
Account.create! name: 'foo'
|
10
|
-
expect
|
12
|
+
expect do
|
11
13
|
MultiTenant::FastTruncate.run
|
12
|
-
|
14
|
+
end.to change { Account.count }.from(1).to(0)
|
13
15
|
end
|
14
16
|
|
15
|
-
it
|
17
|
+
it 'truncates tables that have more than one row inserted' do
|
16
18
|
Account.create! name: 'foo'
|
17
19
|
Account.create! name: 'bar'
|
18
20
|
|
19
|
-
expect
|
21
|
+
expect do
|
20
22
|
MultiTenant::FastTruncate.run
|
21
|
-
|
23
|
+
end.to change { Account.count }.from(2).to(0)
|
22
24
|
end
|
23
25
|
end
|