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
@@ -0,0 +1,59 @@
1
+ .. _usage-guide:
2
+
3
+ Usage Guide
4
+ ===========
5
+
6
+ This section provides a comprehensive guide on how to use ``activerecord-multi-tenant`` in your Rails application.
7
+
8
+ Basic Usage
9
+ -----------
10
+
11
+ To use ``activerecord-multi-tenant``, you need to declare the tenant model in your ActiveRecord models. Here's an example:
12
+
13
+ .. code-block:: ruby
14
+
15
+ class PageView < ActiveRecord::Base
16
+ multi_tenant :customer
17
+ belongs_to :site
18
+
19
+ # ...
20
+ end
21
+
22
+ class Site < ActiveRecord::Base
23
+ multi_tenant :customer
24
+ has_many :page_views
25
+
26
+ # ...
27
+ end
28
+
29
+ In this example, the ``PageView`` and ``Site`` models are scoped to the ``Customer`` model. This means that each user belongs to a specific customer.
30
+
31
+
32
+ Then wrap all code that runs queries/modifications in blocks like this:
33
+
34
+ .. code-block:: ruby
35
+
36
+ customer = Customer.find(session[:current_customer_id])
37
+ # ...
38
+ MultiTenant.with(customer) do
39
+ site = Site.find(params[:site_id])
40
+ site.update! last_accessed_at: Time.now
41
+ site.page_views.count
42
+ end
43
+
44
+ Alternatively, if you don't want to use a block, you can set the current tenant explicitly:
45
+
46
+ .. code-block:: ruby
47
+
48
+ customer = Customer.find(session[:current_customer_id])
49
+ MultiTenant.current_tenant = customer
50
+
51
+
52
+ Multi-tenancy Concepts and Terminology
53
+ --------------------------------------
54
+
55
+ Multi-tenancy is a software architecture in which a single instance of software serves multiple tenants. A tenant is a group of users who share a common access with specific privileges to the software instance.
56
+
57
+ In the context of ``activerecord-multi-tenant``, a tenant is typically represented by a model in your Rails application (e.g., ``Customer``), and other models (e.g., ``PageView``) are scoped to this tenant model.
58
+
59
+
@@ -1,200 +1,209 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MultiTenant
2
4
  class ArelVisitorsDepthFirst < Arel::Visitors::Visitor
3
5
  def initialize(block = nil)
4
- @block = block || Proc.new
6
+ @block = block || proc
5
7
  super()
6
8
  end
7
9
 
8
10
  private
9
11
 
10
- def visit(o, _ = nil)
11
- super
12
- @block.call o
13
- end
12
+ def visit(obj, _ = nil)
13
+ super
14
+ @block.call obj
15
+ end
14
16
 
15
- def unary(o)
16
- visit o.expr
17
- end
18
- alias :visit_Arel_Nodes_Else :unary
19
- alias :visit_Arel_Nodes_Group :unary
20
- alias :visit_Arel_Nodes_Cube :unary
21
- alias :visit_Arel_Nodes_RollUp :unary
22
- alias :visit_Arel_Nodes_GroupingSet :unary
23
- alias :visit_Arel_Nodes_GroupingElement :unary
24
- alias :visit_Arel_Nodes_Grouping :unary
25
- alias :visit_Arel_Nodes_Having :unary
26
- alias :visit_Arel_Nodes_Lateral :unary
27
- alias :visit_Arel_Nodes_Limit :unary
28
- alias :visit_Arel_Nodes_Not :unary
29
- alias :visit_Arel_Nodes_Offset :unary
30
- alias :visit_Arel_Nodes_On :unary
31
- alias :visit_Arel_Nodes_Ordering :unary
32
- alias :visit_Arel_Nodes_Ascending :unary
33
- alias :visit_Arel_Nodes_Descending :unary
34
- alias :visit_Arel_Nodes_UnqualifiedColumn :unary
35
- alias :visit_Arel_Nodes_OptimizerHints :unary
36
- alias :visit_Arel_Nodes_ValuesList :unary
37
-
38
- def function(o)
39
- visit o.expressions
40
- visit o.alias
41
- visit o.distinct
42
- end
43
- alias :visit_Arel_Nodes_Avg :function
44
- alias :visit_Arel_Nodes_Exists :function
45
- alias :visit_Arel_Nodes_Max :function
46
- alias :visit_Arel_Nodes_Min :function
47
- alias :visit_Arel_Nodes_Sum :function
48
-
49
- def visit_Arel_Nodes_NamedFunction(o)
50
- visit o.name
51
- visit o.expressions
52
- visit o.distinct
53
- visit o.alias
54
- end
17
+ def unary(obj)
18
+ visit obj.expr
19
+ end
20
+ alias visit_Arel_Nodes_Else unary
21
+ alias visit_Arel_Nodes_Group unary
22
+ alias visit_Arel_Nodes_Cube unary
23
+ alias visit_Arel_Nodes_RollUp unary
24
+ alias visit_Arel_Nodes_GroupingSet unary
25
+ alias visit_Arel_Nodes_GroupingElement unary
26
+ alias visit_Arel_Nodes_Grouping unary
27
+ alias visit_Arel_Nodes_Having unary
28
+ alias visit_Arel_Nodes_Lateral unary
29
+ alias visit_Arel_Nodes_Limit unary
30
+ alias visit_Arel_Nodes_Not unary
31
+ alias visit_Arel_Nodes_Offset unary
32
+ alias visit_Arel_Nodes_On unary
33
+ alias visit_Arel_Nodes_Ordering unary
34
+ alias visit_Arel_Nodes_Ascending unary
35
+ alias visit_Arel_Nodes_Descending unary
36
+ alias visit_Arel_Nodes_UnqualifiedColumn unary
37
+ alias visit_Arel_Nodes_OptimizerHints unary
38
+ alias visit_Arel_Nodes_ValuesList unary
39
+
40
+ def function(obj)
41
+ visit obj.expressions
42
+ visit obj.alias
43
+ visit obj.distinct
44
+ end
45
+ alias visit_Arel_Nodes_Avg function
46
+ alias visit_Arel_Nodes_Exists function
47
+ alias visit_Arel_Nodes_Max function
48
+ alias visit_Arel_Nodes_Min function
49
+ alias visit_Arel_Nodes_Sum function
50
+
51
+ # rubocop:disable Naming/MethodName
52
+
53
+ def visit_Arel_Nodes_NamedFunction(obj)
54
+ visit obj.name
55
+ visit obj.expressions
56
+ visit obj.distinct
57
+ visit obj.alias
58
+ end
55
59
 
56
- def visit_Arel_Nodes_Count(o)
57
- visit o.expressions
58
- visit o.alias
59
- visit o.distinct
60
- end
60
+ def visit_Arel_Nodes_Count(obj)
61
+ visit obj.expressions
62
+ visit obj.alias
63
+ visit obj.distinct
64
+ end
61
65
 
62
- def visit_Arel_Nodes_Case(o)
63
- visit o.case
64
- visit o.conditions
65
- visit o.default
66
- end
66
+ def visit_Arel_Nodes_Case(obj)
67
+ visit obj.case
68
+ visit obj.conditions
69
+ visit obj.default
70
+ end
67
71
 
68
- def nary(o)
69
- o.children.each { |child| visit child }
70
- end
71
- alias :visit_Arel_Nodes_And :nary
72
+ def nary(obj)
73
+ obj.children.each { |child| visit child }
74
+ end
75
+ alias visit_Arel_Nodes_And nary
72
76
 
73
- def binary(o)
74
- visit o.left
75
- visit o.right
76
- end
77
- alias :visit_Arel_Nodes_As :binary
78
- alias :visit_Arel_Nodes_Assignment :binary
79
- alias :visit_Arel_Nodes_Between :binary
80
- alias :visit_Arel_Nodes_Concat :binary
81
- alias :visit_Arel_Nodes_DeleteStatement :binary
82
- alias :visit_Arel_Nodes_DoesNotMatch :binary
83
- alias :visit_Arel_Nodes_Equality :binary
84
- alias :visit_Arel_Nodes_FullOuterJoin :binary
85
- alias :visit_Arel_Nodes_GreaterThan :binary
86
- alias :visit_Arel_Nodes_GreaterThanOrEqual :binary
87
- alias :visit_Arel_Nodes_In :binary
88
- alias :visit_Arel_Nodes_InfixOperation :binary
89
- alias :visit_Arel_Nodes_JoinSource :binary
90
- alias :visit_Arel_Nodes_InnerJoin :binary
91
- alias :visit_Arel_Nodes_LessThan :binary
92
- alias :visit_Arel_Nodes_LessThanOrEqual :binary
93
- alias :visit_Arel_Nodes_Matches :binary
94
- alias :visit_Arel_Nodes_NotEqual :binary
95
- alias :visit_Arel_Nodes_NotIn :binary
96
- alias :visit_Arel_Nodes_NotRegexp :binary
97
- alias :visit_Arel_Nodes_IsNotDistinctFrom :binary
98
- alias :visit_Arel_Nodes_IsDistinctFrom :binary
99
- alias :visit_Arel_Nodes_Or :binary
100
- alias :visit_Arel_Nodes_OuterJoin :binary
101
- alias :visit_Arel_Nodes_Regexp :binary
102
- alias :visit_Arel_Nodes_RightOuterJoin :binary
103
- alias :visit_Arel_Nodes_TableAlias :binary
104
- alias :visit_Arel_Nodes_When :binary
105
-
106
- def visit_Arel_Nodes_StringJoin(o)
107
- visit o.left
108
- end
77
+ def binary(obj)
78
+ visit obj.left
79
+ visit obj.right
80
+ end
81
+ alias visit_Arel_Nodes_As binary
82
+ alias visit_Arel_Nodes_Assignment binary
83
+ alias visit_Arel_Nodes_Between binary
84
+ alias visit_Arel_Nodes_Concat binary
85
+ alias visit_Arel_Nodes_DeleteStatement binary
86
+ alias visit_Arel_Nodes_DoesNotMatch binary
87
+ alias visit_Arel_Nodes_Equality binary
88
+ alias visit_Arel_Nodes_FullOuterJoin binary
89
+ alias visit_Arel_Nodes_GreaterThan binary
90
+ alias visit_Arel_Nodes_GreaterThanOrEqual binary
91
+ alias visit_Arel_Nodes_In binary
92
+ alias visit_Arel_Nodes_InfixOperation binary
93
+ alias visit_Arel_Nodes_JoinSource binary
94
+ alias visit_Arel_Nodes_InnerJoin binary
95
+ alias visit_Arel_Nodes_LessThan binary
96
+ alias visit_Arel_Nodes_LessThanOrEqual binary
97
+ alias visit_Arel_Nodes_Matches binary
98
+ alias visit_Arel_Nodes_NotEqual binary
99
+ alias visit_Arel_Nodes_NotIn binary
100
+ alias visit_Arel_Nodes_NotRegexp binary
101
+ alias visit_Arel_Nodes_IsNotDistinctFrom binary
102
+ alias visit_Arel_Nodes_IsDistinctFrom binary
103
+ alias visit_Arel_Nodes_Or binary
104
+ alias visit_Arel_Nodes_OuterJoin binary
105
+ alias visit_Arel_Nodes_Regexp binary
106
+ alias visit_Arel_Nodes_RightOuterJoin binary
107
+ alias visit_Arel_Nodes_TableAlias binary
108
+ alias visit_Arel_Nodes_When binary
109
+
110
+ def visit_Arel_Nodes_StringJoin(obj)
111
+ visit obj.left
112
+ end
109
113
 
110
- def visit_Arel_Attribute(o)
111
- visit o.relation
112
- visit o.name
113
- end
114
- alias :visit_Arel_Attributes_Integer :visit_Arel_Attribute
115
- alias :visit_Arel_Attributes_Float :visit_Arel_Attribute
116
- alias :visit_Arel_Attributes_String :visit_Arel_Attribute
117
- alias :visit_Arel_Attributes_Time :visit_Arel_Attribute
118
- alias :visit_Arel_Attributes_Boolean :visit_Arel_Attribute
119
- alias :visit_Arel_Attributes_Attribute :visit_Arel_Attribute
120
- alias :visit_Arel_Attributes_Decimal :visit_Arel_Attribute
121
-
122
- def visit_Arel_Table(o)
123
- visit o.name
124
- end
114
+ def visit_Arel_Attribute(obj)
115
+ visit obj.relation
116
+ visit obj.name
117
+ end
118
+ alias visit_Arel_Attributes_Integer visit_Arel_Attribute
119
+ alias visit_Arel_Attributes_Float visit_Arel_Attribute
120
+ alias visit_Arel_Attributes_String visit_Arel_Attribute
121
+ alias visit_Arel_Attributes_Time visit_Arel_Attribute
122
+ alias visit_Arel_Attributes_Boolean visit_Arel_Attribute
123
+ alias visit_Arel_Attributes_Attribute visit_Arel_Attribute
124
+ alias visit_Arel_Attributes_Decimal visit_Arel_Attribute
125
+
126
+ def visit_Arel_Table(obj)
127
+ visit obj.name
128
+ end
125
129
 
126
- def terminal(o)
127
- end
128
- alias :visit_ActiveSupport_Multibyte_Chars :terminal
129
- alias :visit_ActiveSupport_StringInquirer :terminal
130
- alias :visit_Arel_Nodes_Lock :terminal
131
- alias :visit_Arel_Nodes_Node :terminal
132
- alias :visit_Arel_Nodes_SqlLiteral :terminal
133
- alias :visit_Arel_Nodes_BindParam :terminal
134
- alias :visit_Arel_Nodes_Window :terminal
135
- alias :visit_Arel_Nodes_True :terminal
136
- alias :visit_Arel_Nodes_False :terminal
137
- alias :visit_BigDecimal :terminal
138
- alias :visit_Class :terminal
139
- alias :visit_Date :terminal
140
- alias :visit_DateTime :terminal
141
- alias :visit_FalseClass :terminal
142
- alias :visit_Float :terminal
143
- alias :visit_Integer :terminal
144
- alias :visit_NilClass :terminal
145
- alias :visit_String :terminal
146
- alias :visit_Symbol :terminal
147
- alias :visit_Time :terminal
148
- alias :visit_TrueClass :terminal
149
-
150
- def visit_Arel_Nodes_InsertStatement(o)
151
- visit o.relation
152
- visit o.columns
153
- visit o.values
154
- end
130
+ def terminal(obj); end
131
+ alias visit_ActiveSupport_Multibyte_Chars terminal
132
+ alias visit_ActiveSupport_StringInquirer terminal
133
+ alias visit_Arel_Nodes_Lock terminal
134
+ alias visit_Arel_Nodes_Node terminal
135
+ alias visit_Arel_Nodes_SqlLiteral terminal
136
+ alias visit_Arel_Nodes_BindParam terminal
137
+ alias visit_Arel_Nodes_Window terminal
138
+ alias visit_Arel_Nodes_True terminal
139
+ alias visit_Arel_Nodes_False terminal
140
+ alias visit_BigDecimal terminal
141
+ alias visit_Class terminal
142
+ alias visit_Date terminal
143
+ alias visit_DateTime terminal
144
+ alias visit_FalseClass terminal
145
+ alias visit_Float terminal
146
+ alias visit_Integer terminal
147
+ alias visit_NilClass terminal
148
+ alias visit_String terminal
149
+ alias visit_Symbol terminal
150
+ alias visit_Time terminal
151
+ alias visit_TrueClass terminal
152
+
153
+ def visit_Arel_Nodes_InsertStatement(obj)
154
+ visit obj.relation
155
+ visit obj.columns
156
+ visit obj.values
157
+ end
155
158
 
156
- def visit_Arel_Nodes_SelectCore(o)
157
- visit o.projections
158
- visit o.source
159
- visit o.wheres
160
- visit o.groups
161
- visit o.windows
162
- visit o.havings
163
- end
159
+ def visit_Arel_Nodes_SelectCore(obj)
160
+ visit obj.projections
161
+ visit obj.source
162
+ visit obj.wheres
163
+ visit obj.groups
164
+ visit obj.windows
165
+ visit obj.havings
166
+ end
164
167
 
165
- def visit_Arel_Nodes_SelectStatement(o)
166
- visit o.cores
167
- visit o.orders
168
- visit o.limit
169
- visit o.lock
170
- visit o.offset
171
- end
168
+ def visit_Arel_Nodes_SelectStatement(obj)
169
+ visit obj.cores
170
+ visit obj.orders
171
+ visit obj.limit
172
+ visit obj.lock
173
+ visit obj.offset
174
+ end
172
175
 
173
- def visit_Arel_Nodes_UpdateStatement(o)
174
- visit o.relation
175
- visit o.values
176
- visit o.wheres
177
- visit o.orders
178
- visit o.limit
179
- end
176
+ def visit_Arel_Nodes_UpdateStatement(obj)
177
+ visit obj.relation
178
+ visit obj.values
179
+ visit obj.wheres
180
+ visit obj.orders
181
+ visit obj.limit
182
+ end
180
183
 
181
- def visit_Arel_Nodes_Comment(o)
182
- visit o.values
183
- end
184
+ def visit_Arel_Nodes_Comment(obj)
185
+ visit obj.values
186
+ end
184
187
 
185
- def visit_Array(o)
186
- o.each { |i| visit i }
187
- end
188
- alias :visit_Set :visit_Array
188
+ def visit_Array(obj)
189
+ obj.each { |i| visit i }
190
+ end
191
+ alias visit_Set visit_Array
189
192
 
190
- def visit_Hash(o)
191
- o.each { |k, v| visit(k); visit(v) }
193
+ def visit_Hash(obj)
194
+ obj.each do |k, v|
195
+ visit(k)
196
+ visit(v)
192
197
  end
198
+ end
193
199
 
194
- DISPATCH = dispatch_cache
200
+ DISPATCH = dispatch_cache
195
201
 
196
- def get_dispatch_cache
197
- DISPATCH
198
- end
202
+ # rubocop:disable Naming/AccessorMethodName
203
+ def get_dispatch_cache
204
+ DISPATCH
205
+ end
206
+ # rubocop:enable Naming/AccessorMethodName
207
+ # rubocop:enable Naming/MethodName
199
208
  end
200
209
  end
@@ -1,16 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Extension to the controller to allow setting the current tenant
4
+ # set_current_tenant and current_tenant methods are introduced
5
+ # to set and get the current tenant in the controllers that uses
6
+ # the MultiTenant module.
1
7
  module MultiTenant
2
8
  module ControllerExtensions
3
9
  def set_current_tenant_through_filter
4
- self.class_eval do
5
- if respond_to?(:helper_method)
6
- helper_method :current_tenant
7
- end
10
+ class_eval do
11
+ helper_method :current_tenant if respond_to?(:helper_method)
8
12
 
9
13
  private
10
14
 
15
+ # rubocop:disable Naming/AccessorMethodName
11
16
  def set_current_tenant(current_tenant_object)
12
17
  MultiTenant.current_tenant = current_tenant_object
13
18
  end
19
+ # rubocop:enable Naming/AccessorMethodName
14
20
 
15
21
  def current_tenant
16
22
  MultiTenant.current_tenant
@@ -20,6 +26,11 @@ module MultiTenant
20
26
  end
21
27
  end
22
28
 
29
+ # This block is executed when the file is loaded and
30
+ # makes the base class; ActionController::Base to
31
+ # extend the ControllerExtensions module.
32
+ # This will add the set_current_tenant and current_tenant
33
+ # in all the controllers that inherit from ActionController::Base
23
34
  ActiveSupport.on_load(:action_controller) do |base|
24
35
  base.extend MultiTenant::ControllerExtensions
25
36
  end
@@ -1,4 +1,7 @@
1
1
  module MultiTenant
2
+ # Designed to be mixed into an ActiveRecord model to provide
3
+ # a copy_from_client method that allows for efficient bulk insertion of
4
+ # data into a PostgreSQL database using the COPY command
2
5
  class CopyFromClientHelper
3
6
  attr_reader :count
4
7
 
@@ -28,6 +31,7 @@ module MultiTenant
28
31
  end
29
32
  end
30
33
 
34
+ # Add copy_from_client to ActiveRecord::Base
31
35
  ActiveSupport.on_load(:active_record) do |base|
32
36
  base.extend(MultiTenant::CopyFromClient)
33
37
  end
@@ -1,5 +1,6 @@
1
1
  # Truncates only the tables that have been modified, according to sequence
2
2
  # values
3
+ # Faster alternative to DatabaseCleaner.clean_with(:truncation, pre_count: true)
3
4
  module MultiTenant
4
5
  module FastTruncate
5
6
  def self.run(exclude: ['schema_migrations'])
@@ -13,7 +14,8 @@ module MultiTenant
13
14
  needs_truncate boolean;
14
15
  BEGIN
15
16
  FOR t IN SELECT schemaname, tablename FROM pg_tables WHERE schemaname = 'public' AND tablename NOT IN (%s) LOOP
16
- EXECUTE 'SELECT EXISTS (SELECT * from pg_class c WHERE c.relkind = ''S'' AND c.relname=''' || t.tablename || '_id_seq'')' into seq_exists;
17
+ EXECUTE 'SELECT EXISTS (SELECT * from pg_class c WHERE c.relkind = ''S''
18
+ AND c.relname=''' || t.tablename || '_id_seq'')' into seq_exists;
17
19
  IF seq_exists THEN
18
20
  EXECUTE 'SELECT is_called FROM ' || t.tablename || '_id_seq' INTO needs_truncate;
19
21
  ELSE
@@ -28,7 +30,7 @@ module MultiTenant
28
30
  IF array_length(tables, 1) > 0 THEN
29
31
  EXECUTE 'TRUNCATE TABLE ' || array_to_string(tables, ', ') || ' RESTART IDENTITY CASCADE';
30
32
  END IF;
31
- END$$;), exclude.map { |t| "'" + t + "'" }.join('\n'))
33
+ END$$;), exclude.map { |t| "'#{t}'" }.join('\n'))
32
34
  end
33
35
  end
34
36
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This module extension is a monkey patch to the ActiveRecord::Associations::ClassMethods module.
4
+ # It overrides the has_and_belongs_to_many method to add the tenant_id to the join table if the
5
+ # tenant_enabled option is set to true.
6
+
7
+ module ActiveRecord
8
+ module Associations
9
+ module ClassMethods
10
+ # rubocop:disable Naming/PredicateName
11
+ def has_and_belongs_to_many_with_tenant(name, options = {}, &extension)
12
+ # rubocop:enable Naming/PredicateName
13
+ has_and_belongs_to_many_without_tenant(name, **options, &extension)
14
+
15
+ middle_reflection = _reflections[name.to_s].through_reflection
16
+ join_model = middle_reflection.klass
17
+
18
+ # get tenant_enabled from options and if it is not set, set it to false
19
+ tenant_enabled = options[:tenant_enabled] || false
20
+
21
+ return unless tenant_enabled
22
+
23
+ tenant_class_name = options[:tenant_class_name]
24
+ tenant_column = options[:tenant_column]
25
+
26
+ match = tenant_column.match(/(\w+)_id/)
27
+ tenant_field_name = match ? match[1] : 'tenant'
28
+
29
+ join_model.class_eval do
30
+ belongs_to tenant_field_name.to_sym, class_name: tenant_class_name
31
+ before_create :tenant_set
32
+
33
+ private
34
+
35
+ # This method sets the tenant_id on the join table and executes before creation of the join table record.
36
+ define_method :tenant_set do
37
+ if tenant_enabled
38
+ raise MultiTenant::MissingTenantError, 'Tenant Id is not set' unless MultiTenant.current_tenant_id
39
+
40
+ send("#{tenant_column}=", MultiTenant.current_tenant_id)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ alias has_and_belongs_to_many_without_tenant has_and_belongs_to_many
47
+ alias has_and_belongs_to_many has_and_belongs_to_many_with_tenant
48
+ end
49
+ end
50
+ end
@@ -46,7 +46,8 @@ module MultiTenant
46
46
  execute "SELECT citus_run_on_all_workers($$#{sql}$$)" # initial citus_tools.sql with different names
47
47
  when nil
48
48
  # Do nothing, this is regular Postgres
49
- else # 6.1 and newer
49
+ else
50
+ # 6.1 and newer
50
51
  execute "SELECT run_command_on_workers($$#{sql}$$)"
51
52
  end
52
53
  end
@@ -69,6 +70,7 @@ module ActiveRecord
69
70
  module ConnectionAdapters # :nodoc:
70
71
  module SchemaStatements
71
72
  alias orig_create_table create_table
73
+
72
74
  def create_table(table_name, options = {}, &block)
73
75
  ret = orig_create_table(table_name, **options.except(:partition_key), &block)
74
76
  if options[:id] != false && options[:partition_key] && options[:partition_key].to_s != 'id'
@@ -86,18 +88,25 @@ module ActiveRecord
86
88
  private
87
89
 
88
90
  alias initialize_without_citus initialize
91
+
89
92
  def initialize(connection, options = {})
90
93
  initialize_without_citus(connection, options)
91
94
 
92
- citus_version = begin
93
- ActiveRecord::Migration.citus_version
94
- rescue StandardError
95
- # Handle the case where this gem is used with MySQL https://github.com/citusdata/activerecord-multi-tenant/issues/166
96
- nil
97
- end
95
+ citus_version =
96
+ begin
97
+ ActiveRecord::Migration.citus_version
98
+ rescue StandardError
99
+ # Handle the case where this gem is used with MySQL https://github.com/citusdata/activerecord-multi-tenant/issues/166
100
+ nil
101
+ end
98
102
  @distribution_columns =
99
103
  if citus_version.present?
100
- @connection.execute('SELECT logicalrelid::regclass AS table_name, column_to_column_name(logicalrelid, partkey) AS dist_col_name FROM pg_dist_partition').to_h do |v|
104
+ query_to_execute = <<-SQL.strip
105
+ SELECT logicalrelid::regclass AS table_name,
106
+ column_to_column_name(logicalrelid, partkey) AS dist_col_name
107
+ FROM pg_dist_partition
108
+ SQL
109
+ @connection.execute(query_to_execute).to_h do |v|
101
110
  [v['table_name'], v['dist_col_name']]
102
111
  end
103
112
  else
@@ -107,6 +116,7 @@ module ActiveRecord
107
116
 
108
117
  # Support for create_distributed_table & create_reference_table
109
118
  alias table_without_citus table
119
+
110
120
  def table(table, stream)
111
121
  table_without_citus(table, stream)
112
122
  table_name = remove_prefix_and_suffix(table)