activerecord-multi-tenant 1.2.0 → 2.4.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.
- checksums.yaml +4 -4
- data/.github/workflows/active-record-multi-tenant-tests.yml +83 -0
- data/.gitignore +6 -0
- data/.readthedocs.yaml +15 -0
- data/.rspec +0 -0
- data/.rubocop.yml +51 -0
- data/Appraisals +6 -22
- data/CHANGELOG.md +51 -0
- data/Gemfile +3 -1
- data/LICENSE +0 -0
- data/README.md +3 -2
- data/Rakefile +1 -1
- data/activerecord-multi-tenant.gemspec +28 -23
- 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 +87 -10
- data/lib/activerecord-multi-tenant/model_extensions.rb +98 -34
- data/lib/activerecord-multi-tenant/multi_tenant.rb +102 -29
- data/lib/activerecord-multi-tenant/query_monitor.rb +21 -5
- data/lib/activerecord-multi-tenant/query_rewriter.rb +122 -91
- data/lib/activerecord-multi-tenant/sidekiq.rb +46 -19
- data/lib/activerecord-multi-tenant/table_node.rb +13 -0
- data/lib/activerecord-multi-tenant/version.rb +1 -1
- data/lib/activerecord-multi-tenant.rb +3 -13
- data/lib/activerecord_multi_tenant.rb +13 -0
- data/spec/activerecord-multi-tenant/associations_spec.rb +42 -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 +347 -143
- data/spec/activerecord-multi-tenant/multi_tenant_spec.rb +69 -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 +23 -4
- data/spec/activerecord-multi-tenant/sidekiq_spec.rb +10 -10
- data/spec/database.yml +0 -0
- data/spec/schema.rb +43 -2
- data/spec/spec_helper.rb +52 -16
- data/spec/support/format_sql.rb +20 -0
- metadata +126 -36
- data/.github/workflows/CI.yml +0 -63
- data/gemfiles/.bundle/config +0 -2
- data/gemfiles/active_record_5.2.gemfile +0 -16
- 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_5.2.gemfile +0 -16
- 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/persistence_extension.rb +0 -13
- data/lib/activerecord-multi-tenant/with_lock.rb +0 -15
- 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 ||
|
6
|
+
@block = block || proc
|
5
7
|
super()
|
6
8
|
end
|
7
9
|
|
8
10
|
private
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
def visit(obj, _ = nil)
|
13
|
+
super
|
14
|
+
@block.call obj
|
15
|
+
end
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
60
|
+
def visit_Arel_Nodes_Count(obj)
|
61
|
+
visit obj.expressions
|
62
|
+
visit obj.alias
|
63
|
+
visit obj.distinct
|
64
|
+
end
|
61
65
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
66
|
+
def visit_Arel_Nodes_Case(obj)
|
67
|
+
visit obj.case
|
68
|
+
visit obj.conditions
|
69
|
+
visit obj.default
|
70
|
+
end
|
67
71
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
+
def nary(obj)
|
73
|
+
obj.children.each { |child| visit child }
|
74
|
+
end
|
75
|
+
alias visit_Arel_Nodes_And nary
|
72
76
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
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
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
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
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
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
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
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
|
-
|
182
|
-
|
183
|
-
|
184
|
+
def visit_Arel_Nodes_Comment(obj)
|
185
|
+
visit obj.values
|
186
|
+
end
|
184
187
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
188
|
+
def visit_Array(obj)
|
189
|
+
obj.each { |i| visit i }
|
190
|
+
end
|
191
|
+
alias visit_Set visit_Array
|
189
192
|
|
190
|
-
|
191
|
-
|
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
|
-
|
200
|
+
DISPATCH = dispatch_cache
|
195
201
|
|
196
|
-
|
197
|
-
|
198
|
-
|
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
|
-
|
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''
|
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| "'
|
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, scope = nil, **options, &extension)
|
12
|
+
# rubocop:enable Naming/PredicateName
|
13
|
+
has_and_belongs_to_many_without_tenant(name, scope, **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
|