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.
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