activerecord-multi-tenant-next 2.5.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 +7 -0
- data/CHANGELOG.md +264 -0
- data/LICENSE +18 -0
- data/README.md +121 -0
- data/lib/activerecord-multi-tenant/arel_visitors_depth_first.rb +209 -0
- data/lib/activerecord-multi-tenant/controller_extensions.rb +36 -0
- data/lib/activerecord-multi-tenant/copy_from_client.rb +39 -0
- data/lib/activerecord-multi-tenant/fast_truncate.rb +38 -0
- data/lib/activerecord-multi-tenant/habtm.rb +49 -0
- data/lib/activerecord-multi-tenant/migrations.rb +134 -0
- data/lib/activerecord-multi-tenant/model_extensions.rb +207 -0
- data/lib/activerecord-multi-tenant/multi_tenant.rb +160 -0
- data/lib/activerecord-multi-tenant/query_monitor.rb +36 -0
- data/lib/activerecord-multi-tenant/query_rewriter.rb +431 -0
- data/lib/activerecord-multi-tenant/relation_extension.rb +90 -0
- data/lib/activerecord-multi-tenant/sidekiq.rb +91 -0
- data/lib/activerecord-multi-tenant/table_node.rb +15 -0
- data/lib/activerecord-multi-tenant/version.rb +5 -0
- data/lib/activerecord-multi-tenant-next.rb +4 -0
- data/lib/activerecord-multi-tenant.rb +3 -0
- data/lib/activerecord_multi_tenant.rb +14 -0
- metadata +238 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 9c71faa57caf1f2b1168a7fb90edd30b1394f6e24812de44cf3df4ea9cf48b48
|
|
4
|
+
data.tar.gz: 878717495ee2392449f346ace840518fec7d963d914cc61f2663c704d6fbf64b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: bc08983b8b2645b256241d5b6f43e60756338cf78c53ec79268644d2828a9a3fc3e0977a7e65589e14ce14c965293cc48efcc961fb569919696043af20f78b59
|
|
7
|
+
data.tar.gz: d017e6f20df585cefea35ffe8ea2b10e9a2db47634bd56f6c259e5f7b69586b5bedf1fb9d9ae98c2200287c0a6cb860ba658ca4a2a3074c32f917a7a82fdc37a
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2.5.0 2026-10-02
|
|
4
|
+
First release of the maintained fork, published as `activerecord-multi-tenant-next`.
|
|
5
|
+
|
|
6
|
+
* Adds support for Rails 7.2 (#239), 8.0 (#260) and 8.1
|
|
7
|
+
* Adds Ruby 3.4 to the test matrix, and makes CI actually install each appraisal gemfile (#264)
|
|
8
|
+
* Fixes `update_all` writing `Arel.sql(...)` values as literal strings inside `MultiTenant.with` (#278)
|
|
9
|
+
* Fixes `increment_counter`, `update_counters`, `increment!` and `decrement!` setting the column to `NULL` inside `MultiTenant.with` (#279, #282)
|
|
10
|
+
* Bumps the optimistic locking column in tenant-scoped `update_all`, as Active Record does (#282)
|
|
11
|
+
* Fixes `update_all` / `delete_all` inside `MultiTenant.with` raising on Active Record 6.0 and 8.1
|
|
12
|
+
* Fixes joined `update_all` on Rails 8.1 failing on Citus with "complex joins are only supported when all distributed tables are co-located"
|
|
13
|
+
* Fixes `NoMethodError: undefined method 'alias'` for Arel functions in conditions on Rails 8.1 (#284)
|
|
14
|
+
* Fixes `TypeError: Cannot visit Arel::SelectManager` for Arel subqueries in conditions (#236)
|
|
15
|
+
* Fixes `SystemStackError` in the schema dumper when another gem (e.g. strong_migrations) prepends to `ActiveRecord::SchemaDumper` (#271, also proposed in #245)
|
|
16
|
+
* Fixes `Model.limit(n).delete_all` and `Model.limit(n).update_all` generating an incorrect query (#200)
|
|
17
|
+
* Fixes tenant scoping for `update_all` (#223)
|
|
18
|
+
* Fixes a `SyntaxError` in `undistribute_table` (#226)
|
|
19
|
+
* Fixes errors when `table_name` is nil (#218)
|
|
20
|
+
* Uses `prepend` instead of `alias` to patch Active Record (#216)
|
|
21
|
+
|
|
22
|
+
## 2.4.0 2023-09-22
|
|
23
|
+
* Adds citus 12 to test matrix (#210)
|
|
24
|
+
* Adds Support for rails 7.1 (#208)
|
|
25
|
+
* Fix missing scope in habtm.rb (#207)
|
|
26
|
+
* Update logic inside the tenant_klass_defined? method (#202)
|
|
27
|
+
|
|
28
|
+
## 2.3.0 2023-06-05
|
|
29
|
+
* Adds has_and_belongs_to_many feature with tenant (#193)
|
|
30
|
+
* Removes eol ruby versions
|
|
31
|
+
* Adds documentation in ReadTheDocs platform (#196)
|
|
32
|
+
* Organizes badges in documentation and README.md (#197)
|
|
33
|
+
* Wrap ActiveRecord::Base with ActiveSupport.on_load (#199)
|
|
34
|
+
|
|
35
|
+
## 2.2.0 2022-12-06
|
|
36
|
+
* Handle changing tenant from `nil` to a value [#173](https://github.com/citusdata/activerecord-multi-tenant/pull/173)
|
|
37
|
+
* Allow Partitioned tables to be created without a primary key [#172](https://github.com/citusdata/activerecord-multi-tenant/pull/172)
|
|
38
|
+
* Only attempt to reload with MultiTenant when parition_key is present [#175](https://github.com/citusdata/activerecord-multi-tenant/pull/175)
|
|
39
|
+
* Remove support for Ruby 2.5 & ActiveRecord 5.2
|
|
40
|
+
|
|
41
|
+
## 2.1.6 2022-11-23
|
|
42
|
+
* Fix undefined wrap_methods error & wrap_methods version check [#170](https://github.com/citusdata/activerecord-multi-tenant/pull/170)
|
|
43
|
+
|
|
44
|
+
## 2.1.5 2022-11-20
|
|
45
|
+
* Fix `MultiTenant.without` codegen bug in Rails 6.1+ [#168](https://github.com/citusdata/activerecord-multi-tenant/pull/168)
|
|
46
|
+
|
|
47
|
+
## 2.1.4 2022-11-03
|
|
48
|
+
* Fixes #166 where db:schema:dump is broken when using this gem with MySQL [#167](https://github.com/citusdata/activerecord-multi-tenant/pull/167)
|
|
49
|
+
|
|
50
|
+
## 2.1.3 2022-10-27
|
|
51
|
+
* Error when calling a method that takes keyword arguments with MultiTenant.wrap_methods [#164](https://github.com/citusdata/activerecord-multi-tenant/pull/164)
|
|
52
|
+
|
|
53
|
+
## 2.1.2 2022-10-26
|
|
54
|
+
* Fixes issue when wraping methods that require a block [#162](https://github.com/citusdata/activerecord-multi-tenant/pull/162)
|
|
55
|
+
|
|
56
|
+
## 2.1.1 2022-10-20
|
|
57
|
+
* Fix query building for models with mismatched partition_keys [#150](https://github.com/citusdata/activerecord-multi-tenant/pull/150)
|
|
58
|
+
* Identify tenant even if class name is nonstandard [#152](https://github.com/citusdata/activerecord-multi-tenant/pull/152)
|
|
59
|
+
* Add current_tenant_id to WHERE clauses when calling methods on activerecord instance or its associations [#154](https://github.com/citusdata/activerecord-multi-tenant/pull/154)
|
|
60
|
+
* Make create_distributed_table, create_reference_table reversible & add ruby wrapper for rebalance_table_shards [#155](https://github.com/citusdata/activerecord-multi-tenant/pull/155)
|
|
61
|
+
* Support create_distributed_table, create_reference_table in schema.rb [#156](https://github.com/citusdata/activerecord-multi-tenant/pull/156)
|
|
62
|
+
* Add client and server sidekiq middleware to sidekiq middleware chain [#158](https://github.com/citusdata/activerecord-multi-tenant/pull/158)
|
|
63
|
+
|
|
64
|
+
## 2.0.0 2022-05-19
|
|
65
|
+
|
|
66
|
+
* Replace RequestStore with CurrentAttributes [#139](https://github.com/citusdata/activerecord-multi-tenant/pull/139)
|
|
67
|
+
* Support changing table_name after calling multi_tenant [#128](https://github.com/citusdata/activerecord-multi-tenant/pull/128)
|
|
68
|
+
* Allow to use uuid as primary key on partition table [#112](https://github.com/citusdata/activerecord-multi-tenant/pull/112)
|
|
69
|
+
* Support latest Rails 5.2 [#145](https://github.com/citusdata/activerecord-multi-tenant/pull/145)
|
|
70
|
+
* Support optional: true for belongs_to [#147](https://github.com/citusdata/activerecord-multi-tenant/pull/147)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
## 1.2.0 2022-03-29
|
|
74
|
+
|
|
75
|
+
* Test Rails 7 & Ruby 3
|
|
76
|
+
* Fix regression in 1.1.1 involving deleted tenants [#123](https://github.com/citusdata/activerecord-multi-tenant/pull/123)
|
|
77
|
+
* Fix incorrect SQL generated when joining two models and one has a default scope [#132](https://github.com/citusdata/activerecord-multi-tenant/pull/132)
|
|
78
|
+
* Update for Rails 5+ removal of type_cast_for_database [#135](https://github.com/citusdata/activerecord-multi-tenant/pull/135)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
## 1.1.1 2021-01-15
|
|
82
|
+
|
|
83
|
+
* Add support for Rails 6.1 [#108](https://github.com/citusdata/activerecord-multi-tenant/pull/108)
|
|
84
|
+
* Fix statement cache for has_many through relations [#103](https://github.com/citusdata/activerecord-multi-tenant/pull/103)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
## 1.1.0 2020-08-06
|
|
88
|
+
|
|
89
|
+
* See commits for changes:
|
|
90
|
+
https://github.com/citusdata/activerecord-multi-tenant/commits/v1.1.0
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
## 1.0.4 2019-10-30
|
|
94
|
+
|
|
95
|
+
* Fix bug introduced in 1.0.3 for delete when table is reference or not distributed
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
## 1.0.3 2019-10-28
|
|
99
|
+
|
|
100
|
+
* Ensure that when using object.delete, we set the tenant
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
## 1.0.2 2019-09-20
|
|
104
|
+
|
|
105
|
+
* Compatibility with rails 6
|
|
106
|
+
* Remove support for rails 4.0 and 4.1
|
|
107
|
+
* Fix bug when multiple databases are used
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
## 1.0.1 2019-08-27
|
|
111
|
+
|
|
112
|
+
* Ensure current tenant is present before adding tenant id filter in DatabaseStatements
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
## 1.0.0 2019-07-05
|
|
116
|
+
|
|
117
|
+
* Fix `RETURNING id` for distributed tables with no primary key
|
|
118
|
+
* Include fix for partial select described in issue [#34](https://github.com/citusdata/activerecord-multi-tenant/issues/34).
|
|
119
|
+
- When doing a partial select without the tenant like `Project.select(:name).find(project.id)` it would raise `ActiveModel::MissingAttributeError (missing attribute: tenant_id)`
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
## 0.11.0 2019-06-12
|
|
123
|
+
|
|
124
|
+
* Fix queries with joins by including the tenant column when current tenant isn't set
|
|
125
|
+
- A common use case is having a filter on the tenant, but `MultiTenant.with` isn't used like `Project.where(account_id: 1).eager_load(:categories)`. This version fixes the ORM call to include in the `join`: `"project_categories"."account_id" = "projects"."account_id"`
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
## 0.10.0 2019-05-31
|
|
129
|
+
|
|
130
|
+
* Add `MultiTenant.without` to remove already set tenant context in a block [#45](https://github.com/citusdata/activerecord-multi-tenant/pull/45) [Jackson Miller](https://github.com/jaxn)
|
|
131
|
+
* Fix uninitialized constant X::ActiveRecord::VERSION [#42](https://github.com/citusdata/activerecord-multi-tenant/pull/42) [vollnhals](https://github.com/vollnhals)
|
|
132
|
+
* Fix find and find_by caching issues
|
|
133
|
+
- This builds on work by multiple contributors, and fixes issues where the
|
|
134
|
+
tenant_id would be cached across different tenant contexts for find
|
|
135
|
+
and find_by methods. This issue was only present with prepared
|
|
136
|
+
statements turned on
|
|
137
|
+
- Note that the mechanism to solve this is slightly different for Rails 4
|
|
138
|
+
and 5:
|
|
139
|
+
- Rails 4: Disable any caching for find and find_by methods
|
|
140
|
+
- Rails 5: Explicitly add the current_tenant_id into the cache key
|
|
141
|
+
- This also ensures that we test both prepared statements on and off
|
|
142
|
+
on Travis
|
|
143
|
+
* Added method to ensure that the current tenant is loaded [#49](https://github.com/citusdata/activerecord-multi-tenant/pull/49) [Stephen Bussey](https://github.com/sb8244)
|
|
144
|
+
- This is ideal for fully utilizing the ActiveRecord extensions, as they only take effect when the
|
|
145
|
+
current tenant is not an ID
|
|
146
|
+
* Update loofah and rack to fix security warnings
|
|
147
|
+
- Note that loofah is not a direct dependency of the libary, so this only
|
|
148
|
+
applies when running the test suite
|
|
149
|
+
* Remove monkey patch that previously disabled referential integrity (DISABLE/ENABLE TRIGGER ALL) [#53](https://github.com/citusdata/activerecord-multi-tenant/pull/53) [Rémi Piotaix](https://github.com/piotaixr)
|
|
150
|
+
- This was required for Citus compatibility, but the issue has been fixed in
|
|
151
|
+
Citus for over a year (https://github.com/citusdata/citus/issues/1080)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
## 0.9.0 2018-06-22
|
|
155
|
+
|
|
156
|
+
* ActiveRecord 5.2 support [Nathan Stitt](https://github.com/nathanstitt) & [osyo-manga](https://github.com/osyo-manga)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
## 0.8.1 2017-10-06
|
|
160
|
+
|
|
161
|
+
* Cast attribute name to a string to avoid double applying tenant clause [Ben Olive](https://github.com/sionide21)
|
|
162
|
+
* Allow bulk delete/update with subqueries on joins [Kyle Bock](https://github.com/kwbock)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
## 0.8.0 2017-08-16
|
|
166
|
+
|
|
167
|
+
* Significant improvements and simplifications of query rewriting
|
|
168
|
+
* Big thanks to [Kyle Bock](https://github.com/kwbock) and [Ben Olive](https://github.com/sionide21)
|
|
169
|
+
for (re-)writing this code and verifying it works well
|
|
170
|
+
* This fixes caching issues across multiple MultiTenant.with { } blocks when
|
|
171
|
+
interacting with the Rails statement cache
|
|
172
|
+
* Drop support for Rails 3.2
|
|
173
|
+
* The arel version used in Rails 3.2 has caused more trouble than its worth -
|
|
174
|
+
it seems less troublesome to ask any users of this library to upgrade to at
|
|
175
|
+
least Rails 4.0
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
## 0.7.0 2017-07-18
|
|
179
|
+
|
|
180
|
+
* Switch back to Relation-based mechanism of hooking into ActiveRecord (this resolves issues for simple queries that didn't get rewritten)
|
|
181
|
+
* Query rewriter improvements
|
|
182
|
+
* Handle OUTER JOIN correctly
|
|
183
|
+
* Correctly rewrite sub-selects
|
|
184
|
+
* Model tenant method: Only return cached object if not loaded
|
|
185
|
+
* Fix support for inherited model classes that only have `multi-tenant` on a higher level object [Aaron Severs](https://github.com/webandtech) [#13](https://github.com/citusdata/activerecord-multi-tenant/pull/13)
|
|
186
|
+
* Sidekiq middleware: Don't automatically perform a find for the tenant object for every job [Scott Mitchell](https://github.com/smitchelus) [#14](https://github.com/citusdata/activerecord-multi-tenant/pull/14)
|
|
187
|
+
* Fix automatic inverse of on singular associations [Kyle Bock](https://github.com/kwbock) [#15](https://github.com/citusdata/activerecord-multi-tenant/pull/15)
|
|
188
|
+
* Fix bug that prevents fast truncate from running [Kyle Bock](https://github.com/kwbock) [#17](https://github.com/citusdata/activerecord-multi-tenant/pull/17)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
## 0.6.0 2017-06-09
|
|
192
|
+
|
|
193
|
+
* Query rewriter
|
|
194
|
+
* Change hook from per-relation to be pre-SQL statement thats being output
|
|
195
|
+
- This should resolve issues where we added conditions in the wrong place
|
|
196
|
+
* Use table name to model klass registry
|
|
197
|
+
* Improve tests for activerecord-multi-tenant
|
|
198
|
+
* Use lower shard count to speed up tests
|
|
199
|
+
* Drop database cleaner dependency
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
## 0.5.0 2017-05-08
|
|
203
|
+
|
|
204
|
+
* Write-only mode that enables step-by-step migrations
|
|
205
|
+
* Add tenant_id to queries using rewrite instead of default_scope/unscoped [Ben Olive](https://github.com/sionide21)
|
|
206
|
+
* Query monitor that warns you about missing tenant_id
|
|
207
|
+
* Helper for fast truncation
|
|
208
|
+
* Sidekiq middleware
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
## 0.4.1 2017-03-23
|
|
212
|
+
|
|
213
|
+
* Allow use outside of Rails, e.g. when using Sinatra with ActiveRecord [@nathanstitt](https://github.com/nathanstitt) [#5](https://github.com/citusdata/activerecord-multi-tenant/pull/5)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
## 0.4.0 2017-03-22
|
|
217
|
+
|
|
218
|
+
* Infer multi_tenant setting from parent classes [@webandtech](https://github.com/webandtech) [#6](https://github.com/citusdata/activerecord-multi-tenant/pull/6)
|
|
219
|
+
* Remove use of global tenant klass variable [@webandtech](https://github.com/webandtech) [#6](https://github.com/citusdata/activerecord-multi-tenant/pull/6)
|
|
220
|
+
* Support passing ID values to MultiTenant.with directly [@webandtech](https://github.com/webandtech) [#6](https://github.com/citusdata/activerecord-multi-tenant/pull/6)
|
|
221
|
+
* This effectively deprecates with_id, but we'll keep it around for now
|
|
222
|
+
* Remove unnecessary validation for invalid belongs_to association
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
## 0.3.4 2017-02-22
|
|
226
|
+
|
|
227
|
+
* Expand with_lock workaround to cover lock! as well
|
|
228
|
+
* Enable trigger workaround on Rails 5 as well, otherwise it fails silently
|
|
229
|
+
* Tests: Switch to database cleaner truncation strategy to avoid multi-shard transactions
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
## 0.3.3 2017-02-21
|
|
233
|
+
|
|
234
|
+
* Avoid warning about multi-column primary keys with Rails 5 [#2](https://github.com/citusdata/activerecord-multi-tenant/issues/2)
|
|
235
|
+
* Fix odd bind errors for has_one/has_many through
|
|
236
|
+
* Add MultiTenant.current_tenant_id= helper method
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
## 0.3.2 2017-02-16
|
|
240
|
+
|
|
241
|
+
* Support blocks passed into the unscoped method (this fixes reload, amongst other issues)
|
|
242
|
+
* Make with_lock work by adding workaround for Citus #1236 (SELECT ... FOR UPDATE is not router-plannable)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
## 0.3.1 2017-02-13
|
|
246
|
+
|
|
247
|
+
* Rails 5 API controller support [@mstahl](https://github.com/mstahl) [#4](https://github.com/citusdata/activerecord-multi-tenant/pull/4)
|
|
248
|
+
* Citus 6.1 compatibility
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
## 0.3.0 2016-12-30
|
|
252
|
+
|
|
253
|
+
* Remove dependency on acts_as_tenant - instead copy the code thats necessary
|
|
254
|
+
* Fix issue with callbacks having TenantIdWrapper instead of the actual object
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
## 0.2.1 2016-12-29
|
|
258
|
+
|
|
259
|
+
* Fix bug in CopyFromClient helper
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
## 0.2.0 2016-12-27
|
|
263
|
+
|
|
264
|
+
* Initial release
|
data/LICENSE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Copyright (c) 2018, Citus Data Inc.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
5
|
+
the Software without restriction, including without limitation the rights to use,
|
|
6
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
7
|
+
Software, and to permit persons to whom the Software is furnished to do so,
|
|
8
|
+
subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
15
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
16
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# activerecord-multi-tenant-next
|
|
2
|
+
[](https://github.com/tachyurgy/activerecord-multi-tenant/actions/workflows/active-record-multi-tenant-tests.yml) [](https://rubygems.org/gems/activerecord-multi-tenant-next)
|
|
3
|
+
|
|
4
|
+
A maintained fork of [citusdata/activerecord-multi-tenant](https://github.com/citusdata/activerecord-multi-tenant).
|
|
5
|
+
The upstream gem has had no release since 2.4.0 (September 2023) and no active maintainer
|
|
6
|
+
([#268](https://github.com/citusdata/activerecord-multi-tenant/issues/268)), so no released
|
|
7
|
+
version works on Rails 7.2, 8.0 or 8.1. This fork collects the fixes that were sitting in
|
|
8
|
+
upstream pull requests (authors kept on their commits), adds the missing ones, and runs CI
|
|
9
|
+
against every supported Rails/Ruby/Citus combination.
|
|
10
|
+
|
|
11
|
+
It is a drop-in replacement: same `MultiTenant` API, same `require` path. Everything is
|
|
12
|
+
also offered back upstream; if upstream starts releasing again, switching back is a one-line
|
|
13
|
+
Gemfile change.
|
|
14
|
+
|
|
15
|
+
Introduction Post: https://www.citusdata.com/blog/2017/01/05/easily-scale-out-multi-tenant-apps/
|
|
16
|
+
|
|
17
|
+
ActiveRecord/Rails integration for multi-tenant databases, in particular the open-source [Citus](https://github.com/citusdata/citus) extension for PostgreSQL.
|
|
18
|
+
|
|
19
|
+
Enables easy scale-out by adding the tenant context to your queries, enabling the database (e.g. Citus) to efficiently route queries to the right database node.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
Add the following to your Gemfile:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
gem 'activerecord-multi-tenant-next'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
If you are coming from `activerecord-multi-tenant`, remove that line from your Gemfile; the two
|
|
30
|
+
gems ship the same files and should not be installed together.
|
|
31
|
+
|
|
32
|
+
## Supported Rails versions
|
|
33
|
+
|
|
34
|
+
All Ruby on Rails versions starting with 6.0 or newer (up to 8.1) are supported, on
|
|
35
|
+
Ruby 3.0 or newer. CI runs every combination that Rails itself supports
|
|
36
|
+
(Rails 8.x needs Ruby 3.2+, Rails 7.2 needs Ruby 3.1+, Rails 6.x does not run on
|
|
37
|
+
Ruby 3.4) against Citus 10, 11 and 12.
|
|
38
|
+
|
|
39
|
+
This gem only supports ActiveRecord (the Rails default ORM), and not alternative ORMs like Sequel.
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
It is required that you add `multi_tenant` definitions to your model in order to have full support for Citus, in particular when updating records.
|
|
44
|
+
|
|
45
|
+
In the example of an analytics application, sharding on `customer_id`, annotate your models like this:
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
class PageView < ActiveRecord::Base
|
|
49
|
+
multi_tenant :customer
|
|
50
|
+
belongs_to :site
|
|
51
|
+
|
|
52
|
+
# ...
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class Site < ActiveRecord::Base
|
|
56
|
+
multi_tenant :customer
|
|
57
|
+
has_many :page_views
|
|
58
|
+
|
|
59
|
+
# ...
|
|
60
|
+
end
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
and then wrap all code that runs queries/modifications in blocks like this:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
customer = Customer.find(session[:current_customer_id])
|
|
67
|
+
# ...
|
|
68
|
+
MultiTenant.with(customer) do
|
|
69
|
+
site = Site.find(params[:site_id])
|
|
70
|
+
site.update! last_accessed_at: Time.now
|
|
71
|
+
site.page_views.count
|
|
72
|
+
end
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Inside controllers you can use a before_action together with set_current_tenant, to set the tenant for the current request:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
class ApplicationController < ActionController::Base
|
|
79
|
+
set_current_tenant_through_filter # Required to opt into this behavior
|
|
80
|
+
before_action :set_customer_as_tenant
|
|
81
|
+
|
|
82
|
+
def set_customer_as_tenant
|
|
83
|
+
customer = Customer.find(session[:current_customer_id])
|
|
84
|
+
set_current_tenant(customer)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Rolling out activerecord-multi-tenant for your application (write-only mode)
|
|
90
|
+
|
|
91
|
+
The library relies on tenant_id to be present and NOT NULL for all rows. However,
|
|
92
|
+
its often useful to have the library set the tenant_id for new records, and then backfilling
|
|
93
|
+
tenant_id for existing records as a background task.
|
|
94
|
+
|
|
95
|
+
To support this, there is a write-only mode, in which tenant_id is not included in queries,
|
|
96
|
+
but only set for new records. Include the following in an initializer to enable it:
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
MultiTenant.enable_write_only_mode
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Once you are ready to enforce tenancy, make your tenant_id column NOT NULL and simply remove that line.
|
|
103
|
+
|
|
104
|
+
## Frequently Asked Questions
|
|
105
|
+
|
|
106
|
+
* **What if I have a table that doesn't relate to my tenant?** (e.g. templates that are the same in every account)
|
|
107
|
+
|
|
108
|
+
We recommend not using activerecord-multi-tenant on these tables. In case only some records in a table are not associated to a tenant (i.e. your templates are in the same table as actual objects), we recommend setting the tenant_id to 0, and then using MultiTenant.with(0) to access these objects.
|
|
109
|
+
|
|
110
|
+
* **What if my tenant model is not defined in my application?**
|
|
111
|
+
|
|
112
|
+
The tenant model does not have to be defined. Use the gem as if the model was present. `MultiTenant.with` accepts either a tenant id or model instance.
|
|
113
|
+
|
|
114
|
+
## Credits
|
|
115
|
+
|
|
116
|
+
This gem was initially based on [acts_as_tenant](https://github.com/ErwinM/acts_as_tenant), and still shares some code. We thank the authors for their efforts.
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
Copyright (c) 2018, Citus Data Inc.<br>
|
|
121
|
+
Licensed under the MIT license, see LICENSE file for details.
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MultiTenant
|
|
4
|
+
class ArelVisitorsDepthFirst < Arel::Visitors::Visitor
|
|
5
|
+
def initialize(block = nil)
|
|
6
|
+
@block = block || proc
|
|
7
|
+
super()
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def visit(obj, _ = nil)
|
|
13
|
+
super
|
|
14
|
+
@block.call obj
|
|
15
|
+
end
|
|
16
|
+
|
|
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 if obj.respond_to?(: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 if obj.respond_to?(:alias)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def visit_Arel_Nodes_Count(obj)
|
|
61
|
+
visit obj.expressions
|
|
62
|
+
visit obj.alias if obj.respond_to?(:alias)
|
|
63
|
+
visit obj.distinct
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def visit_Arel_Nodes_Case(obj)
|
|
67
|
+
visit obj.case
|
|
68
|
+
visit obj.conditions
|
|
69
|
+
visit obj.default
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def nary(obj)
|
|
73
|
+
obj.children.each { |child| visit child }
|
|
74
|
+
end
|
|
75
|
+
alias visit_Arel_Nodes_And nary
|
|
76
|
+
|
|
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
|
|
113
|
+
|
|
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
|
|
129
|
+
|
|
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
|
|
158
|
+
|
|
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
|
|
167
|
+
|
|
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
|
|
175
|
+
|
|
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
|
|
183
|
+
|
|
184
|
+
def visit_Arel_Nodes_Comment(obj)
|
|
185
|
+
visit obj.values
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def visit_Array(obj)
|
|
189
|
+
obj.each { |i| visit i }
|
|
190
|
+
end
|
|
191
|
+
alias visit_Set visit_Array
|
|
192
|
+
|
|
193
|
+
def visit_Hash(obj)
|
|
194
|
+
obj.each do |k, v|
|
|
195
|
+
visit(k)
|
|
196
|
+
visit(v)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
DISPATCH = dispatch_cache
|
|
201
|
+
|
|
202
|
+
# rubocop:disable Naming/AccessorMethodName
|
|
203
|
+
def get_dispatch_cache
|
|
204
|
+
DISPATCH
|
|
205
|
+
end
|
|
206
|
+
# rubocop:enable Naming/AccessorMethodName
|
|
207
|
+
# rubocop:enable Naming/MethodName
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
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.
|
|
7
|
+
module MultiTenant
|
|
8
|
+
module ControllerExtensions
|
|
9
|
+
def set_current_tenant_through_filter
|
|
10
|
+
class_eval do
|
|
11
|
+
helper_method :current_tenant if respond_to?(:helper_method)
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
# rubocop:disable Naming/AccessorMethodName
|
|
16
|
+
def set_current_tenant(current_tenant_object)
|
|
17
|
+
MultiTenant.current_tenant = current_tenant_object
|
|
18
|
+
end
|
|
19
|
+
# rubocop:enable Naming/AccessorMethodName
|
|
20
|
+
|
|
21
|
+
def current_tenant
|
|
22
|
+
MultiTenant.current_tenant
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
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
|
|
34
|
+
ActiveSupport.on_load(:action_controller) do |base|
|
|
35
|
+
base.extend MultiTenant::ControllerExtensions
|
|
36
|
+
end
|