activerecord-multi-tenant 2.2.0 → 2.3.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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/active-record-multi-tenant-tests.yml +80 -0
  3. data/.gitignore +6 -0
  4. data/.readthedocs.yaml +15 -0
  5. data/.rspec +0 -0
  6. data/.rubocop.yml +51 -0
  7. data/Appraisals +0 -0
  8. data/CHANGELOG.md +6 -0
  9. data/Gemfile +3 -1
  10. data/LICENSE +0 -0
  11. data/README.md +2 -1
  12. data/Rakefile +1 -1
  13. data/activerecord-multi-tenant.gemspec +28 -22
  14. data/docker-compose.yml +24 -18
  15. data/docs/.gitignore +3 -0
  16. data/docs/Makefile +28 -0
  17. data/docs/api-reference.sh +10 -0
  18. data/docs/requirements.in +4 -0
  19. data/docs/requirements.txt +62 -0
  20. data/docs/source/_static/api-reference/ActiveRecord/Associations/Association.html +285 -0
  21. data/docs/source/_static/api-reference/ActiveRecord/Associations/ClassMethods.html +255 -0
  22. data/docs/source/_static/api-reference/ActiveRecord/Associations.html +117 -0
  23. data/docs/source/_static/api-reference/ActiveRecord/ConnectionAdapters/SchemaStatements.html +232 -0
  24. data/docs/source/_static/api-reference/ActiveRecord/ConnectionAdapters.html +126 -0
  25. data/docs/source/_static/api-reference/ActiveRecord/QueryMethods.html +336 -0
  26. data/docs/source/_static/api-reference/ActiveRecord/SchemaDumper.html +121 -0
  27. data/docs/source/_static/api-reference/ActiveRecord.html +130 -0
  28. data/docs/source/_static/api-reference/MultiTenant/ArelTenantVisitor.html +755 -0
  29. data/docs/source/_static/api-reference/MultiTenant/ArelVisitorsDepthFirst.html +208 -0
  30. data/docs/source/_static/api-reference/MultiTenant/BaseTenantEnforcementClause.html +462 -0
  31. data/docs/source/_static/api-reference/MultiTenant/Context.html +659 -0
  32. data/docs/source/_static/api-reference/MultiTenant/ControllerExtensions.html +202 -0
  33. data/docs/source/_static/api-reference/MultiTenant/CopyFromClient.html +186 -0
  34. data/docs/source/_static/api-reference/MultiTenant/CopyFromClientHelper.html +362 -0
  35. data/docs/source/_static/api-reference/MultiTenant/Current.html +124 -0
  36. data/docs/source/_static/api-reference/MultiTenant/DatabaseStatements.html +366 -0
  37. data/docs/source/_static/api-reference/MultiTenant/FastTruncate.html +226 -0
  38. data/docs/source/_static/api-reference/MultiTenant/MigrationExtensions.html +554 -0
  39. data/docs/source/_static/api-reference/MultiTenant/MissingTenantError.html +124 -0
  40. data/docs/source/_static/api-reference/MultiTenant/ModelExtensionsClassMethods.html +492 -0
  41. data/docs/source/_static/api-reference/MultiTenant/QueryMonitor.html +257 -0
  42. data/docs/source/_static/api-reference/MultiTenant/Table.html +419 -0
  43. data/docs/source/_static/api-reference/MultiTenant/TenantEnforcementClause.html +148 -0
  44. data/docs/source/_static/api-reference/MultiTenant/TenantIsImmutable.html +135 -0
  45. data/docs/source/_static/api-reference/MultiTenant/TenantJoinEnforcementClause.html +310 -0
  46. data/docs/source/_static/api-reference/MultiTenant/TenantValueVisitor.html +239 -0
  47. data/docs/source/_static/api-reference/MultiTenant.html +1454 -0
  48. data/docs/source/_static/api-reference/MultiTenantFindBy.html +180 -0
  49. data/docs/source/_static/api-reference/Sidekiq/Client.html +302 -0
  50. data/docs/source/_static/api-reference/Sidekiq/Middleware/MultiTenant/Client.html +217 -0
  51. data/docs/source/_static/api-reference/Sidekiq/Middleware/MultiTenant/Server.html +219 -0
  52. data/docs/source/_static/api-reference/Sidekiq/Middleware/MultiTenant.html +126 -0
  53. data/docs/source/_static/api-reference/Sidekiq.html +126 -0
  54. data/docs/source/_static/api-reference/_index.html +399 -0
  55. data/docs/source/_static/api-reference/class_list.html +51 -0
  56. data/docs/source/_static/api-reference/css/common.css +1 -0
  57. data/docs/source/_static/api-reference/css/full_list.css +58 -0
  58. data/docs/source/_static/api-reference/css/style.css +497 -0
  59. data/docs/source/_static/api-reference/file.README.html +167 -0
  60. data/docs/source/_static/api-reference/file_list.html +56 -0
  61. data/docs/source/_static/api-reference/frames.html +17 -0
  62. data/docs/source/_static/api-reference/index.html +167 -0
  63. data/docs/source/_static/api-reference/js/app.js +314 -0
  64. data/docs/source/_static/api-reference/js/full_list.js +216 -0
  65. data/docs/source/_static/api-reference/js/jquery.js +4 -0
  66. data/docs/source/_static/api-reference/method_list.html +715 -0
  67. data/docs/source/_static/api-reference/top-level-namespace.html +126 -0
  68. data/docs/source/_templates/.gitignore +4 -0
  69. data/docs/source/api-reference.rst +8 -0
  70. data/docs/source/appendix.rst +26 -0
  71. data/docs/source/changelog.rst +8 -0
  72. data/docs/source/community-and-support.rst +26 -0
  73. data/docs/source/conf.py +30 -0
  74. data/docs/source/contributing.rst +70 -0
  75. data/docs/source/getting-started.rst +37 -0
  76. data/docs/source/guides-and-tutorials.rst +129 -0
  77. data/docs/source/index.rst +54 -0
  78. data/docs/source/introduction.rst +33 -0
  79. data/docs/source/license.rst +22 -0
  80. data/docs/source/troubleshooting.rst +41 -0
  81. data/docs/source/usage-guide.rst +59 -0
  82. data/lib/activerecord-multi-tenant/arel_visitors_depth_first.rb +183 -174
  83. data/lib/activerecord-multi-tenant/controller_extensions.rb +15 -4
  84. data/lib/activerecord-multi-tenant/copy_from_client.rb +4 -0
  85. data/lib/activerecord-multi-tenant/fast_truncate.rb +4 -2
  86. data/lib/activerecord-multi-tenant/habtm.rb +50 -0
  87. data/lib/activerecord-multi-tenant/migrations.rb +18 -8
  88. data/lib/activerecord-multi-tenant/model_extensions.rb +78 -37
  89. data/lib/activerecord-multi-tenant/multi_tenant.rb +40 -21
  90. data/lib/activerecord-multi-tenant/query_monitor.rb +21 -5
  91. data/lib/activerecord-multi-tenant/query_rewriter.rb +111 -80
  92. data/lib/activerecord-multi-tenant/sidekiq.rb +31 -20
  93. data/lib/activerecord-multi-tenant/version.rb +1 -1
  94. data/lib/activerecord-multi-tenant.rb +3 -12
  95. data/lib/activerecord_multi_tenant.rb +12 -0
  96. data/spec/activerecord-multi-tenant/associations_spec.rb +21 -0
  97. data/spec/activerecord-multi-tenant/controller_extensions_spec.rb +3 -2
  98. data/spec/activerecord-multi-tenant/fast_truncate_spec.rb +8 -6
  99. data/spec/activerecord-multi-tenant/model_extensions_spec.rb +233 -153
  100. data/spec/activerecord-multi-tenant/multi_tenant_spec.rb +15 -13
  101. data/spec/activerecord-multi-tenant/query_rewriter_spec.rb +60 -59
  102. data/spec/activerecord-multi-tenant/record_callback_spec.rb +0 -0
  103. data/spec/activerecord-multi-tenant/record_finding_spec.rb +11 -11
  104. data/spec/activerecord-multi-tenant/record_modifications_spec.rb +4 -4
  105. data/spec/activerecord-multi-tenant/sidekiq_spec.rb +10 -10
  106. data/spec/database.yml +0 -0
  107. data/spec/schema.rb +20 -2
  108. data/spec/spec_helper.rb +46 -17
  109. data/spec/support/format_sql.rb +20 -0
  110. metadata +130 -25
  111. data/.github/workflows/CI.yml +0 -47
  112. data/gemfiles/.bundle/config +0 -2
  113. data/gemfiles/active_record_6.0.gemfile +0 -8
  114. data/gemfiles/active_record_6.1.gemfile +0 -8
  115. data/gemfiles/active_record_7.0.gemfile +0 -8
  116. data/gemfiles/rails_6.0.gemfile +0 -8
  117. data/gemfiles/rails_6.1.gemfile +0 -8
  118. data/gemfiles/rails_7.0.gemfile +0 -8
  119. data/lib/activerecord-multi-tenant/with_lock.rb +0 -15
  120. data/spec/activerecord-multi-tenant/schema_dumper_tester.rb +0 -0
@@ -1,6 +1,7 @@
1
1
  require 'active_record'
2
- require_relative "./arel_visitors_depth_first.rb" unless Arel::Visitors.const_defined?(:DepthFirst)
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?(rhs)
13
- self.class == rhs.class &&
14
- equality_fields.eql?(rhs.equality_fields)
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) ? Arel::Visitors::DepthFirst : ::MultiTenant::ArelVisitorsDepthFirst
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(Proc.new {})
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(o, *args)
77
- if o.left.is_a?(Arel::Attributes::Attribute)
78
- table_name = o.left.relation.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
- @current_context.visited_handled_relation(o.left.relation) if model.present? && o.left.name.to_s == model.partition_key.to_s
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(o, *args)
92
+ super(obj, *args)
83
93
  end
84
94
 
85
- def visit_MultiTenant_TenantEnforcementClause(o, *)
86
- @current_context.visited_handled_relation(o.tenant_attribute.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(o, *)
90
- @current_context.visited_handled_relation(o.tenant_attribute.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(o, _collector = nil)
94
- @current_context.visited_relation(o) if tenant_relation?(o.table_name)
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
- def visit_Arel_Nodes_SelectCore(o, *args)
99
- nest_context(o) do
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 o.source
112
+ visit obj.source
102
113
  end
103
- visit o.wheres
104
- visit o.groups
105
- visit o.windows
106
- if defined?(o.having)
107
- visit o.having
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 o.havings
120
+ visit obj.havings
110
121
  end
111
122
  end
112
123
  end
113
124
 
114
- def visit_Arel_Nodes_OuterJoin(o, collector = nil)
115
- nest_context(o) do
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 o.left
118
- visit o.right
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
- alias :visit_ActiveModel_Attribute :terminal
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
- def nest_context(o)
163
+ # rubocop:enable Naming/AccessorMethodName
164
+
165
+ def nest_context(obj)
146
166
  old_context = @current_context
147
- @current_context = Context.new(o)
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; to_sql; end
164
- def to_str; to_sql; end
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
- def visit_MultiTenant_TenantEnforcementClause(o, collector)
204
- collector << o
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(o, collector)
208
- collector << o
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 :build_arel_orig :build_arel
286
+ alias build_arel_orig build_arel
287
+
258
288
  def build_arel(*args)
259
289
  arel = build_arel_orig(*args)
260
290
 
261
- if !MultiTenant.with_write_only_mode_enabled?
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
- if node.wheres[0].is_a?(Arel::Nodes::And)
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 "UnknownContext"
314
+ raise 'UnknownContext'
287
315
  end
288
316
  end
289
317
 
290
- if node.is_a?(Arel::Nodes::SelectCore) || node.is_a?(Arel::Nodes::Join)
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
- node_list.select{ |n| n.is_a? Arel::Nodes::Join }.each do |node_join|
298
- if !node_join.right
299
- next
300
- end
301
- relation_right, relation_left = relations_from_node_join(node_join)
320
+ node_list = if node.is_a? Arel::Nodes::Join
321
+ [node]
322
+ else
323
+ node.source.right
324
+ end
302
325
 
303
- next unless relation_right && relation_left
326
+ node_list.select { |n| n.is_a? Arel::Nodes::Join }.each do |node_join|
327
+ next unless node_join.right
304
328
 
305
- model_right = MultiTenant.multi_tenant_model_for_table(relation_left.table_name)
306
- model_left = MultiTenant.multi_tenant_model_for_table(relation_right.table_name)
307
- if model_right && model_left
308
- join_enforcement_clause = MultiTenant::TenantJoinEnforcementClause.new(relation_right[model_right.partition_key], relation_left)
309
- node_join.right.expr = node_join.right.expr.and(join_enforcement_clause)
310
- end
311
- end
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?{|c| c.is_a?(MultiTenant::TenantEnforcementClause) || c.is_a?(MultiTenant::TenantJoinEnforcementClause)}
329
- if tenant_applied || children.empty?
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
- return nil, nil
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
- ActiveRecord::Base.singleton_class.prepend(MultiTenantFindBy)
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(worker_class, msg, queue, redis_pool)
7
- msg['multi_tenant'] ||=
8
- {
9
- 'class' => MultiTenant.current_tenant_class,
10
- 'id' => MultiTenant.current_tenant_id
11
- } if MultiTenant.current_tenant.present?
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(worker_class, msg, queue)
20
- if msg.has_key?('multi_tenant')
22
+ def call(_worker_class, msg, _queue, &block)
23
+ if msg.key?('multi_tenant')
21
24
  tenant = begin
22
- msg['multi_tenant']['class'].constantize.find(msg['multi_tenant']['id'])
23
- rescue ActiveRecord::RecordNotFound
24
- msg['multi_tenant']['id']
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
- job = items['jobs'].first
56
- return [] unless job # no jobs to push
57
- raise ArgumentError, "Bulk arguments must be an Array of Hashes: [{ 'args' => [1], 'tenant_id' => 1 }, ...]" if !job.is_a?(Hash)
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 ? result : nil
74
+ result || nil
65
75
  end
66
76
  end.compact
67
77
 
68
- raw_push(payloads) if !payloads.empty?
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,3 +1,3 @@
1
1
  module MultiTenant
2
- VERSION = '2.2.0'
2
+ VERSION = '2.3.0'.freeze
3
3
  end
@@ -1,12 +1,3 @@
1
- if Object.const_defined?(:ActionController)
2
- require_relative 'activerecord-multi-tenant/controller_extensions'
3
- end
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 "Controller Extensions", type: :controller do
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 "truncates tables that have exactly one row inserted" do
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
- }.to change { Account.count }.from(1).to(0)
14
+ end.to change { Account.count }.from(1).to(0)
13
15
  end
14
16
 
15
- it "truncates tables that have more than one row inserted" do
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
- }.to change { Account.count }.from(2).to(0)
23
+ end.to change { Account.count }.from(2).to(0)
22
24
  end
23
25
  end