activerecord-multi-tenant 0.9.0 → 0.10.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/.travis.yml +4 -0
- data/CHANGELOG.md +26 -0
- data/Gemfile.lock +79 -68
- data/LICENSE +18 -0
- data/README.md +2 -2
- data/docker-compose.yml +15 -10
- data/lib/activerecord-multi-tenant.rb +0 -1
- data/lib/activerecord-multi-tenant/model_extensions.rb +11 -1
- data/lib/activerecord-multi-tenant/multi_tenant.rb +18 -0
- data/lib/activerecord-multi-tenant/query_rewriter.rb +66 -5
- data/lib/activerecord-multi-tenant/version.rb +1 -1
- data/spec/activerecord-multi-tenant/model_extensions_spec.rb +70 -10
- data/spec/activerecord-multi-tenant/multi_tenant_spec.rb +65 -0
- data/spec/activerecord-multi-tenant/record_finding_spec.rb +43 -0
- data/spec/database.yml +1 -1
- data/spec/spec_helper.rb +9 -0
- metadata +4 -3
- data/lib/activerecord-multi-tenant/referential_integrity.rb +0 -14
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 366debe159c5693b73fbca961fae7889febd5e00
|
|
4
|
+
data.tar.gz: 3670f0bd43e9074463c44e397ccbc1f25246bd4d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9274691cbc15fcba204a5b9fcd01e550aefddcf3b0b37ec1aa329a66637dffb8e22d08b6fd9e55f63d42b2582146d544552921f8558722b047f31fb8f66fb9f1
|
|
7
|
+
data.tar.gz: 8e162537b956585c28734850d8f189433d8337854df10c9a1bf16637000c9c448eed6739763c8e20a488edf7d43743ea84254a137f0d77c6b7c84318a10d3c4b
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.0 2019-05-31
|
|
4
|
+
|
|
5
|
+
* 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)
|
|
6
|
+
* Fix uninitialized constant X::ActiveRecord::VERSION [#42](https://github.com/citusdata/activerecord-multi-tenant/pull/42) [vollnhals](https://github.com/vollnhals)
|
|
7
|
+
* Fix find and find_by caching issues
|
|
8
|
+
- This builds on work by multiple contributors, and fixes issues where the
|
|
9
|
+
tenant_id would be cached across different tenant contexts for find
|
|
10
|
+
and find_by methods. This issue was only present with prepared
|
|
11
|
+
statements turned on
|
|
12
|
+
- Note that the mechanism to solve this is slightly different for Rails 4
|
|
13
|
+
and 5:
|
|
14
|
+
- Rails 4: Disable any caching for find and find_by methods
|
|
15
|
+
- Rails 5: Explicitly add the current_tenant_id into the cache key
|
|
16
|
+
- This also ensures that we test both prepared statements on and off
|
|
17
|
+
on Travis
|
|
18
|
+
* 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)
|
|
19
|
+
- This is ideal for fully utilizing the ActiveRecord extensions, as they only take effect when the
|
|
20
|
+
current tenant is not an ID
|
|
21
|
+
* Update loofah and rack to fix security warnings
|
|
22
|
+
- Note that loofah is not a direct dependency of the libary, so this only
|
|
23
|
+
applies when running the test suite
|
|
24
|
+
* 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)
|
|
25
|
+
- This was required for Citus compatibility, but the issue has been fixed in
|
|
26
|
+
Citus for over a year (https://github.com/citusdata/citus/issues/1080)
|
|
27
|
+
|
|
28
|
+
|
|
3
29
|
## 0.9.0 2018-06-22
|
|
4
30
|
|
|
5
31
|
* ActiveRecord 5.2 support [Nathan Stitt](https://github.com/nathanstitt) & [osyo-manga](https://github.com/osyo-manga)
|
data/Gemfile.lock
CHANGED
|
@@ -1,75 +1,85 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
activerecord-multi-tenant (0.
|
|
4
|
+
activerecord-multi-tenant (0.10.0)
|
|
5
5
|
rails (>= 4.0)
|
|
6
6
|
request_store (>= 1.0.5)
|
|
7
7
|
|
|
8
8
|
GEM
|
|
9
9
|
remote: https://rubygems.org/
|
|
10
10
|
specs:
|
|
11
|
-
actioncable (5.
|
|
12
|
-
actionpack (= 5.
|
|
13
|
-
nio4r (~>
|
|
14
|
-
websocket-driver (
|
|
15
|
-
actionmailer (5.
|
|
16
|
-
actionpack (= 5.
|
|
17
|
-
actionview (= 5.
|
|
18
|
-
activejob (= 5.
|
|
11
|
+
actioncable (5.2.2)
|
|
12
|
+
actionpack (= 5.2.2)
|
|
13
|
+
nio4r (~> 2.0)
|
|
14
|
+
websocket-driver (>= 0.6.1)
|
|
15
|
+
actionmailer (5.2.2)
|
|
16
|
+
actionpack (= 5.2.2)
|
|
17
|
+
actionview (= 5.2.2)
|
|
18
|
+
activejob (= 5.2.2)
|
|
19
19
|
mail (~> 2.5, >= 2.5.4)
|
|
20
20
|
rails-dom-testing (~> 2.0)
|
|
21
|
-
actionpack (5.
|
|
22
|
-
actionview (= 5.
|
|
23
|
-
activesupport (= 5.
|
|
21
|
+
actionpack (5.2.2)
|
|
22
|
+
actionview (= 5.2.2)
|
|
23
|
+
activesupport (= 5.2.2)
|
|
24
24
|
rack (~> 2.0)
|
|
25
|
-
rack-test (
|
|
25
|
+
rack-test (>= 0.6.3)
|
|
26
26
|
rails-dom-testing (~> 2.0)
|
|
27
27
|
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
|
28
|
-
actionview (5.
|
|
29
|
-
activesupport (= 5.
|
|
28
|
+
actionview (5.2.2)
|
|
29
|
+
activesupport (= 5.2.2)
|
|
30
30
|
builder (~> 3.1)
|
|
31
|
-
|
|
31
|
+
erubi (~> 1.4)
|
|
32
32
|
rails-dom-testing (~> 2.0)
|
|
33
|
-
rails-html-sanitizer (~> 1.0, >= 1.0.
|
|
34
|
-
activejob (5.
|
|
35
|
-
activesupport (= 5.
|
|
33
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
|
34
|
+
activejob (5.2.2)
|
|
35
|
+
activesupport (= 5.2.2)
|
|
36
36
|
globalid (>= 0.3.6)
|
|
37
|
-
activemodel (5.
|
|
38
|
-
activesupport (= 5.
|
|
39
|
-
activerecord (5.
|
|
40
|
-
activemodel (= 5.
|
|
41
|
-
activesupport (= 5.
|
|
42
|
-
arel (
|
|
43
|
-
|
|
37
|
+
activemodel (5.2.2)
|
|
38
|
+
activesupport (= 5.2.2)
|
|
39
|
+
activerecord (5.2.2)
|
|
40
|
+
activemodel (= 5.2.2)
|
|
41
|
+
activesupport (= 5.2.2)
|
|
42
|
+
arel (>= 9.0)
|
|
43
|
+
activestorage (5.2.2)
|
|
44
|
+
actionpack (= 5.2.2)
|
|
45
|
+
activerecord (= 5.2.2)
|
|
46
|
+
marcel (~> 0.3.1)
|
|
47
|
+
activesupport (5.2.2)
|
|
44
48
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
45
|
-
i18n (
|
|
49
|
+
i18n (>= 0.7, < 2)
|
|
46
50
|
minitest (~> 5.1)
|
|
47
51
|
tzinfo (~> 1.1)
|
|
48
52
|
appraisal (2.1.0)
|
|
49
53
|
bundler
|
|
50
54
|
rake
|
|
51
55
|
thor (>= 0.14.0)
|
|
52
|
-
arel (
|
|
53
|
-
builder (3.2.
|
|
56
|
+
arel (9.0.0)
|
|
57
|
+
builder (3.2.3)
|
|
54
58
|
byebug (9.0.6)
|
|
55
59
|
coderay (1.1.1)
|
|
56
|
-
concurrent-ruby (1.
|
|
60
|
+
concurrent-ruby (1.1.3)
|
|
57
61
|
connection_pool (2.2.1)
|
|
62
|
+
crass (1.0.4)
|
|
58
63
|
diff-lcs (1.2.5)
|
|
59
|
-
|
|
60
|
-
globalid (0.4.
|
|
64
|
+
erubi (1.7.1)
|
|
65
|
+
globalid (0.4.2)
|
|
61
66
|
activesupport (>= 4.2.0)
|
|
62
|
-
i18n (
|
|
63
|
-
|
|
67
|
+
i18n (1.1.1)
|
|
68
|
+
concurrent-ruby (~> 1.0)
|
|
69
|
+
loofah (2.2.3)
|
|
70
|
+
crass (~> 1.0.2)
|
|
64
71
|
nokogiri (>= 1.5.9)
|
|
65
|
-
mail (2.7.
|
|
72
|
+
mail (2.7.1)
|
|
66
73
|
mini_mime (>= 0.1.1)
|
|
74
|
+
marcel (0.3.3)
|
|
75
|
+
mimemagic (~> 0.3.2)
|
|
67
76
|
method_source (0.8.2)
|
|
68
|
-
|
|
77
|
+
mimemagic (0.3.3)
|
|
78
|
+
mini_mime (1.0.1)
|
|
69
79
|
mini_portile2 (2.3.0)
|
|
70
|
-
minitest (5.
|
|
71
|
-
nio4r (
|
|
72
|
-
nokogiri (1.8.
|
|
80
|
+
minitest (5.11.3)
|
|
81
|
+
nio4r (2.3.1)
|
|
82
|
+
nokogiri (1.8.5)
|
|
73
83
|
mini_portile2 (~> 2.3.0)
|
|
74
84
|
pg (0.19.0)
|
|
75
85
|
pry (0.10.4)
|
|
@@ -79,35 +89,36 @@ GEM
|
|
|
79
89
|
pry-byebug (3.4.2)
|
|
80
90
|
byebug (~> 9.0)
|
|
81
91
|
pry (~> 0.10)
|
|
82
|
-
rack (2.0.
|
|
92
|
+
rack (2.0.6)
|
|
83
93
|
rack-protection (2.0.1)
|
|
84
94
|
rack
|
|
85
|
-
rack-test (
|
|
86
|
-
rack (>= 1.0)
|
|
87
|
-
rails (5.
|
|
88
|
-
actioncable (= 5.
|
|
89
|
-
actionmailer (= 5.
|
|
90
|
-
actionpack (= 5.
|
|
91
|
-
actionview (= 5.
|
|
92
|
-
activejob (= 5.
|
|
93
|
-
activemodel (= 5.
|
|
94
|
-
activerecord (= 5.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
rack-test (1.1.0)
|
|
96
|
+
rack (>= 1.0, < 3)
|
|
97
|
+
rails (5.2.2)
|
|
98
|
+
actioncable (= 5.2.2)
|
|
99
|
+
actionmailer (= 5.2.2)
|
|
100
|
+
actionpack (= 5.2.2)
|
|
101
|
+
actionview (= 5.2.2)
|
|
102
|
+
activejob (= 5.2.2)
|
|
103
|
+
activemodel (= 5.2.2)
|
|
104
|
+
activerecord (= 5.2.2)
|
|
105
|
+
activestorage (= 5.2.2)
|
|
106
|
+
activesupport (= 5.2.2)
|
|
107
|
+
bundler (>= 1.3.0)
|
|
108
|
+
railties (= 5.2.2)
|
|
98
109
|
sprockets-rails (>= 2.0.0)
|
|
99
|
-
rails-dom-testing (2.0.
|
|
100
|
-
activesupport (>= 4.2.0
|
|
101
|
-
nokogiri (
|
|
102
|
-
rails-html-sanitizer (1.0.
|
|
103
|
-
loofah (~> 2.
|
|
104
|
-
railties (5.
|
|
105
|
-
actionpack (= 5.
|
|
106
|
-
activesupport (= 5.
|
|
110
|
+
rails-dom-testing (2.0.3)
|
|
111
|
+
activesupport (>= 4.2.0)
|
|
112
|
+
nokogiri (>= 1.6)
|
|
113
|
+
rails-html-sanitizer (1.0.4)
|
|
114
|
+
loofah (~> 2.2, >= 2.2.2)
|
|
115
|
+
railties (5.2.2)
|
|
116
|
+
actionpack (= 5.2.2)
|
|
117
|
+
activesupport (= 5.2.2)
|
|
107
118
|
method_source
|
|
108
119
|
rake (>= 0.8.7)
|
|
109
|
-
thor (>= 0.
|
|
110
|
-
rake (12.
|
|
120
|
+
thor (>= 0.19.0, < 2.0)
|
|
121
|
+
rake (12.3.1)
|
|
111
122
|
redis (3.3.3)
|
|
112
123
|
request_store (1.4.1)
|
|
113
124
|
rack (>= 1.4)
|
|
@@ -138,18 +149,18 @@ GEM
|
|
|
138
149
|
rack-protection (>= 1.5.0)
|
|
139
150
|
redis (~> 3.2, >= 3.2.1)
|
|
140
151
|
slop (3.6.0)
|
|
141
|
-
sprockets (3.7.
|
|
152
|
+
sprockets (3.7.2)
|
|
142
153
|
concurrent-ruby (~> 1.0)
|
|
143
154
|
rack (> 1, < 3)
|
|
144
155
|
sprockets-rails (3.2.1)
|
|
145
156
|
actionpack (>= 4.0)
|
|
146
157
|
activesupport (>= 4.0)
|
|
147
158
|
sprockets (>= 3.0.0)
|
|
148
|
-
thor (0.
|
|
159
|
+
thor (0.20.3)
|
|
149
160
|
thread_safe (0.3.6)
|
|
150
|
-
tzinfo (1.2.
|
|
161
|
+
tzinfo (1.2.5)
|
|
151
162
|
thread_safe (~> 0.1)
|
|
152
|
-
websocket-driver (0.
|
|
163
|
+
websocket-driver (0.7.0)
|
|
153
164
|
websocket-extensions (>= 0.1.0)
|
|
154
165
|
websocket-extensions (0.1.3)
|
|
155
166
|
|
|
@@ -169,4 +180,4 @@ DEPENDENCIES
|
|
|
169
180
|
thor
|
|
170
181
|
|
|
171
182
|
BUNDLED WITH
|
|
172
|
-
|
|
183
|
+
2.0.1
|
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
CHANGED
|
@@ -99,5 +99,5 @@ This gem was initially based on [acts_as_tenant](https://github.com/ErwinM/acts_
|
|
|
99
99
|
|
|
100
100
|
## License
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
Copyright (c) 2018, Citus Data Inc.<br>
|
|
103
|
+
Licensed under the MIT license, see LICENSE file for details.
|
data/docker-compose.yml
CHANGED
|
@@ -2,20 +2,25 @@ version: '2.1'
|
|
|
2
2
|
|
|
3
3
|
services:
|
|
4
4
|
master:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
ports: ["5600:5432"]
|
|
5
|
+
image: 'citusdata/citus:7.5.1'
|
|
6
|
+
ports: ['5600:5432']
|
|
8
7
|
labels: ['com.citusdata.role=Master']
|
|
8
|
+
volumes: ['/var/run/postgresql']
|
|
9
|
+
manager:
|
|
10
|
+
container_name: "${COMPOSE_PROJECT_NAME:-citus}_manager"
|
|
11
|
+
image: 'citusdata/membership-manager:0.1.0'
|
|
12
|
+
volumes: ['/var/run/docker.sock:/var/run/docker.sock']
|
|
13
|
+
depends_on: { master: { condition: service_healthy } }
|
|
9
14
|
worker1:
|
|
10
|
-
image: 'citusdata/citus:7.
|
|
15
|
+
image: 'citusdata/citus:7.5.1'
|
|
11
16
|
labels: ['com.citusdata.role=Worker']
|
|
12
17
|
depends_on: { manager: { condition: service_healthy } }
|
|
13
18
|
worker2:
|
|
14
|
-
image: 'citusdata/citus:7.
|
|
19
|
+
image: 'citusdata/citus:7.5.1'
|
|
15
20
|
labels: ['com.citusdata.role=Worker']
|
|
16
21
|
depends_on: { manager: { condition: service_healthy } }
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
healthcheck:
|
|
23
|
+
image: busybox
|
|
24
|
+
depends_on:
|
|
25
|
+
worker1: { condition: service_healthy }
|
|
26
|
+
worker2: { condition: service_healthy }
|
|
@@ -8,6 +8,5 @@ require_relative 'activerecord-multi-tenant/model_extensions'
|
|
|
8
8
|
require_relative 'activerecord-multi-tenant/multi_tenant'
|
|
9
9
|
require_relative 'activerecord-multi-tenant/query_rewriter'
|
|
10
10
|
require_relative 'activerecord-multi-tenant/query_monitor'
|
|
11
|
-
require_relative 'activerecord-multi-tenant/referential_integrity'
|
|
12
11
|
require_relative 'activerecord-multi-tenant/version'
|
|
13
12
|
require_relative 'activerecord-multi-tenant/with_lock'
|
|
@@ -23,7 +23,7 @@ module MultiTenant
|
|
|
23
23
|
# Avoid primary_key errors when using composite primary keys (e.g. id, tenant_id)
|
|
24
24
|
def primary_key
|
|
25
25
|
return @primary_key if @primary_key
|
|
26
|
-
return @primary_key = super || DEFAULT_ID_FIELD if ActiveRecord::VERSION::MAJOR < 5
|
|
26
|
+
return @primary_key = super || DEFAULT_ID_FIELD if ::ActiveRecord::VERSION::MAJOR < 5
|
|
27
27
|
|
|
28
28
|
primary_object_keys = Array.wrap(connection.schema_cache.primary_keys(table_name)) - [partition_key]
|
|
29
29
|
if primary_object_keys.size == 1
|
|
@@ -112,3 +112,13 @@ end
|
|
|
112
112
|
if defined?(ActiveRecord::Base)
|
|
113
113
|
ActiveRecord::Base.extend(MultiTenant::ModelExtensionsClassMethods)
|
|
114
114
|
end
|
|
115
|
+
|
|
116
|
+
if ActiveRecord::VERSION::MAJOR > 4 || (ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR >= 2)
|
|
117
|
+
class ActiveRecord::Associations::Association
|
|
118
|
+
alias skip_statement_cache_orig skip_statement_cache?
|
|
119
|
+
def skip_statement_cache?(*scope)
|
|
120
|
+
return true if klass.respond_to?(:scoped_by_tenant?) && klass.scoped_by_tenant?
|
|
121
|
+
skip_statement_cache_orig(*scope)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -57,6 +57,13 @@ module MultiTenant
|
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
+
def self.load_current_tenant!
|
|
61
|
+
return MultiTenant.current_tenant if MultiTenant.current_tenant && !current_tenant_is_id?
|
|
62
|
+
raise 'MultiTenant.current_tenant must be set to load' if MultiTenant.current_tenant.nil?
|
|
63
|
+
klass = MultiTenant.default_tenant_class || fail('Only have tenant id, and no default tenant class set')
|
|
64
|
+
self.current_tenant = klass.find(MultiTenant.current_tenant_id)
|
|
65
|
+
end
|
|
66
|
+
|
|
60
67
|
def self.with(tenant, &block)
|
|
61
68
|
return block.call if self.current_tenant == tenant
|
|
62
69
|
old_tenant = self.current_tenant
|
|
@@ -68,6 +75,17 @@ module MultiTenant
|
|
|
68
75
|
end
|
|
69
76
|
end
|
|
70
77
|
|
|
78
|
+
def self.without(&block)
|
|
79
|
+
return block.call if self.current_tenant.nil?
|
|
80
|
+
old_tenant = self.current_tenant
|
|
81
|
+
begin
|
|
82
|
+
self.current_tenant = nil
|
|
83
|
+
return block.call
|
|
84
|
+
ensure
|
|
85
|
+
self.current_tenant = old_tenant
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
71
89
|
# Preserve backward compatibility for people using .with_id
|
|
72
90
|
singleton_class.send(:alias_method, :with_id, :with)
|
|
73
91
|
|
|
@@ -155,11 +155,7 @@ module MultiTenant
|
|
|
155
155
|
def to_str; to_sql; end
|
|
156
156
|
|
|
157
157
|
def to_sql(*)
|
|
158
|
-
|
|
159
|
-
tenant_arel.to_sql
|
|
160
|
-
else
|
|
161
|
-
'1=1'
|
|
162
|
-
end
|
|
158
|
+
tenant_arel.to_sql
|
|
163
159
|
end
|
|
164
160
|
|
|
165
161
|
private
|
|
@@ -264,3 +260,68 @@ module ActiveRecord
|
|
|
264
260
|
end
|
|
265
261
|
end
|
|
266
262
|
end
|
|
263
|
+
|
|
264
|
+
require 'active_record/base'
|
|
265
|
+
module MultiTenantFindBy
|
|
266
|
+
if ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR >= 2
|
|
267
|
+
# Disable caching for find and find_by in Rails 4.2 - we don't have a good
|
|
268
|
+
# way to prevent caching problems here when prepared statements are enabled
|
|
269
|
+
def find_by(*args)
|
|
270
|
+
return super unless respond_to?(:scoped_by_tenant?) && scoped_by_tenant?
|
|
271
|
+
|
|
272
|
+
# This duplicates a bunch of code from AR's find() method
|
|
273
|
+
return super if current_scope || !(Hash === args.first) || reflect_on_all_aggregations.any?
|
|
274
|
+
return super if default_scopes.any?
|
|
275
|
+
|
|
276
|
+
hash = args.first
|
|
277
|
+
|
|
278
|
+
return super if hash.values.any? { |v| v.nil? || Array === v || Hash === v }
|
|
279
|
+
return super unless hash.keys.all? { |k| columns_hash.has_key?(k.to_s) }
|
|
280
|
+
|
|
281
|
+
key = hash.keys
|
|
282
|
+
|
|
283
|
+
# Ensure we never use the cached version
|
|
284
|
+
find_by_statement_cache.synchronize { find_by_statement_cache[key] = nil }
|
|
285
|
+
|
|
286
|
+
super
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def find(*ids)
|
|
290
|
+
return super unless respond_to?(:scoped_by_tenant?) && scoped_by_tenant?
|
|
291
|
+
|
|
292
|
+
# This duplicates a bunch of code from AR's find() method
|
|
293
|
+
return super unless ids.length == 1
|
|
294
|
+
return super if ids.first.kind_of?(Symbol)
|
|
295
|
+
return super if block_given? ||
|
|
296
|
+
primary_key.nil? ||
|
|
297
|
+
default_scopes.any? ||
|
|
298
|
+
current_scope ||
|
|
299
|
+
columns_hash.include?(inheritance_column) ||
|
|
300
|
+
ids.first.kind_of?(Array)
|
|
301
|
+
|
|
302
|
+
id = ids.first
|
|
303
|
+
if ActiveRecord::Base === id
|
|
304
|
+
id = id.id
|
|
305
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
|
306
|
+
You are passing an instance of ActiveRecord::Base to `find`.
|
|
307
|
+
Please pass the id of the object by calling `.id`
|
|
308
|
+
MSG
|
|
309
|
+
end
|
|
310
|
+
key = primary_key
|
|
311
|
+
|
|
312
|
+
# Ensure we never use the cached version
|
|
313
|
+
find_by_statement_cache.synchronize { find_by_statement_cache[key] = nil }
|
|
314
|
+
|
|
315
|
+
super
|
|
316
|
+
end
|
|
317
|
+
elsif ActiveRecord::VERSION::MAJOR > 4
|
|
318
|
+
def cached_find_by_statement(key, &block)
|
|
319
|
+
return super unless respond_to?(:scoped_by_tenant?) && scoped_by_tenant?
|
|
320
|
+
|
|
321
|
+
key = Array.wrap(key) + [MultiTenant.current_tenant_id.to_s]
|
|
322
|
+
super(key, &block)
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
ActiveRecord::Base.singleton_class.prepend(MultiTenantFindBy)
|
|
@@ -274,6 +274,40 @@ describe MultiTenant do
|
|
|
274
274
|
end
|
|
275
275
|
end
|
|
276
276
|
|
|
277
|
+
# ::without
|
|
278
|
+
describe "::without" do
|
|
279
|
+
it "should unset current_tenant inside the block" do
|
|
280
|
+
@account = Account.create!(:name => 'baz')
|
|
281
|
+
|
|
282
|
+
MultiTenant.current_tenant = @account
|
|
283
|
+
MultiTenant.without do
|
|
284
|
+
expect(MultiTenant.current_tenant).to eq(nil)
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
it "should reset current_tenant to the previous tenant once exiting the block" do
|
|
289
|
+
@account1 = Account.create!(:name => 'foo')
|
|
290
|
+
|
|
291
|
+
MultiTenant.current_tenant = @account1
|
|
292
|
+
MultiTenant.without do
|
|
293
|
+
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
expect(MultiTenant.current_tenant).to eq(@account1)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
it "should return the value of the block" do
|
|
300
|
+
@account1 = Account.create!(:name => 'foo')
|
|
301
|
+
|
|
302
|
+
MultiTenant.current_tenant = @account1
|
|
303
|
+
value = MultiTenant.without do
|
|
304
|
+
"something"
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
expect(value).to eq "something"
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
277
311
|
describe '.with_lock' do
|
|
278
312
|
it 'supports with_lock blocks inside the block' do
|
|
279
313
|
@account = Account.create!(name: 'foo')
|
|
@@ -309,9 +343,15 @@ describe MultiTenant do
|
|
|
309
343
|
end
|
|
310
344
|
|
|
311
345
|
it "applies the team_id conditions in the where clause" do
|
|
312
|
-
expected_sql =
|
|
313
|
-
|
|
314
|
-
|
|
346
|
+
expected_sql = if uses_prepared_statements? && ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR < 2
|
|
347
|
+
<<-sql
|
|
348
|
+
SELECT "sub_tasks".* FROM "sub_tasks" INNER JOIN "tasks" ON "sub_tasks"."task_id" = "tasks"."id" WHERE "tasks"."account_id" = 1 AND "sub_tasks"."account_id" = 1 AND "tasks"."project_id" = $1
|
|
349
|
+
sql
|
|
350
|
+
else
|
|
351
|
+
<<-sql
|
|
352
|
+
SELECT "sub_tasks".* FROM "sub_tasks" INNER JOIN "tasks" ON "sub_tasks"."task_id" = "tasks"."id" WHERE "tasks"."account_id" = 1 AND "sub_tasks"."account_id" = 1 AND "tasks"."project_id" = 1
|
|
353
|
+
sql
|
|
354
|
+
end
|
|
315
355
|
account1 = Account.create! name: 'Account 1'
|
|
316
356
|
|
|
317
357
|
MultiTenant.with(account1) do
|
|
@@ -333,17 +373,37 @@ describe MultiTenant do
|
|
|
333
373
|
project2 = Project.create! name: 'Project 2', account: Account.create!(name: 'Account2')
|
|
334
374
|
|
|
335
375
|
MultiTenant.with(account) do
|
|
336
|
-
expected_sql =
|
|
337
|
-
|
|
338
|
-
|
|
376
|
+
expected_sql = if uses_prepared_statements? && ActiveRecord::VERSION::MAJOR > 4
|
|
377
|
+
<<-sql.strip
|
|
378
|
+
SELECT "projects".* FROM "projects" WHERE "projects"."account_id" = #{account.id} AND "projects"."id" = $1 LIMIT $2
|
|
379
|
+
sql
|
|
380
|
+
elsif uses_prepared_statements? && ActiveRecord::VERSION::MAJOR == 4
|
|
381
|
+
<<-sql.strip
|
|
382
|
+
SELECT "projects".* FROM "projects" WHERE "projects"."account_id" = #{account.id} AND "projects"."id" = $1 LIMIT 1
|
|
383
|
+
sql
|
|
384
|
+
else
|
|
385
|
+
<<-sql.strip
|
|
386
|
+
SELECT "projects".* FROM "projects" WHERE "projects"."account_id" = #{account.id} AND "projects"."id" = #{project.id} LIMIT 1
|
|
387
|
+
sql
|
|
388
|
+
end
|
|
339
389
|
expect(Project).to receive(:find_by_sql).with(expected_sql, any_args).and_call_original
|
|
340
390
|
expect(Project.find(project.id)).to eq(project)
|
|
341
391
|
end
|
|
342
392
|
|
|
343
|
-
MultiTenant.
|
|
344
|
-
expected_sql =
|
|
345
|
-
|
|
346
|
-
|
|
393
|
+
MultiTenant.without do
|
|
394
|
+
expected_sql = if uses_prepared_statements? && ActiveRecord::VERSION::MAJOR > 4
|
|
395
|
+
<<-sql.strip
|
|
396
|
+
SELECT "projects".* FROM "projects" WHERE "projects"."id" = $1 LIMIT $2
|
|
397
|
+
sql
|
|
398
|
+
elsif uses_prepared_statements? && ActiveRecord::VERSION::MAJOR == 4
|
|
399
|
+
<<-sql.strip
|
|
400
|
+
SELECT "projects".* FROM "projects" WHERE "projects"."id" = $1 LIMIT 1
|
|
401
|
+
sql
|
|
402
|
+
else
|
|
403
|
+
<<-sql.strip
|
|
404
|
+
SELECT "projects".* FROM "projects" WHERE "projects"."id" = #{project2.id} LIMIT 1
|
|
405
|
+
sql
|
|
406
|
+
end
|
|
347
407
|
expect(Project).to receive(:find_by_sql).with(expected_sql, any_args).and_call_original
|
|
348
408
|
expect(Project.find(project2.id)).to eq(project2)
|
|
349
409
|
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe MultiTenant do
|
|
4
|
+
describe ".load_current_tenant!" do
|
|
5
|
+
let(:fake_tenant) { OpenStruct.new(id: 1) }
|
|
6
|
+
let(:mock_klass) { double(find: fake_tenant) }
|
|
7
|
+
|
|
8
|
+
before do
|
|
9
|
+
@original_default_class = MultiTenant.default_tenant_class
|
|
10
|
+
MultiTenant.default_tenant_class = mock_klass
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
after do
|
|
14
|
+
MultiTenant.default_tenant_class = @original_default_class
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "sets and returns the loaded current_tenant" do
|
|
18
|
+
expect(mock_klass).to receive(:find).once.with(1)
|
|
19
|
+
MultiTenant.current_tenant = 1
|
|
20
|
+
expect(MultiTenant.load_current_tenant!).to eq(fake_tenant)
|
|
21
|
+
expect(MultiTenant.current_tenant).to eq(fake_tenant)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "respects `.with` lifecycle" do
|
|
25
|
+
expect(mock_klass).to receive(:find).once.with(2)
|
|
26
|
+
expect(MultiTenant.current_tenant).to eq(nil)
|
|
27
|
+
MultiTenant.with(2) do
|
|
28
|
+
expect(MultiTenant.load_current_tenant!).to eq(fake_tenant)
|
|
29
|
+
expect(MultiTenant.current_tenant).to eq(fake_tenant)
|
|
30
|
+
end
|
|
31
|
+
expect(MultiTenant.current_tenant).to eq(nil)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context "with a loaded current_tenant" do
|
|
35
|
+
it "returns the tenant without fetching it" do
|
|
36
|
+
expect(mock_klass).not_to receive(:find)
|
|
37
|
+
MultiTenant.current_tenant = fake_tenant
|
|
38
|
+
expect(MultiTenant.load_current_tenant!).to eq(fake_tenant)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
context "with a nil current_tenant" do
|
|
43
|
+
it "raises an error, as there is not enough information to load the tenant" do
|
|
44
|
+
expect(mock_klass).not_to receive(:find)
|
|
45
|
+
expect {
|
|
46
|
+
MultiTenant.load_current_tenant!
|
|
47
|
+
}.to raise_error(RuntimeError, 'MultiTenant.current_tenant must be set to load')
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context "without a default class set" do
|
|
52
|
+
before do
|
|
53
|
+
MultiTenant.default_tenant_class = nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "raises an error, as there is not enough information to load the tenant" do
|
|
57
|
+
expect(mock_klass).not_to receive(:find)
|
|
58
|
+
MultiTenant.current_tenant = 1
|
|
59
|
+
expect {
|
|
60
|
+
MultiTenant.load_current_tenant!
|
|
61
|
+
}.to raise_error(RuntimeError, 'Only have tenant id, and no default tenant class set')
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -16,4 +16,47 @@ describe MultiTenant, 'Record finding' do
|
|
|
16
16
|
expect(UuidRecord.find(uuid_record.id)).to be_present
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
|
+
|
|
20
|
+
it 'can use find_bys accurately' do
|
|
21
|
+
first_tenant = Account.create! name: 'First Tenant'
|
|
22
|
+
second_tenant = Account.create! name: 'Second Tenant'
|
|
23
|
+
first_record = first_tenant.projects.create! name: 'identical name'
|
|
24
|
+
second_record = second_tenant.projects.create! name: 'identical name'
|
|
25
|
+
|
|
26
|
+
MultiTenant.with(first_tenant) do
|
|
27
|
+
found_record = Project.find_by(name: 'identical name')
|
|
28
|
+
expect(found_record).to eq(first_record)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
MultiTenant.with(second_tenant) do
|
|
32
|
+
found_record = Project.find_by(name: 'identical name')
|
|
33
|
+
expect(found_record).to eq(second_record)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'can use find accurately' do
|
|
38
|
+
first_tenant = Account.create! name: 'First Tenant'
|
|
39
|
+
second_tenant = Account.create! name: 'Second Tenant'
|
|
40
|
+
first_record = first_tenant.projects.create!
|
|
41
|
+
second_record = second_tenant.projects.create!
|
|
42
|
+
|
|
43
|
+
MultiTenant.with(first_tenant) do
|
|
44
|
+
found_record = Project.find(first_record.id)
|
|
45
|
+
expect(found_record).to eq(first_record)
|
|
46
|
+
expect { Project.find(second_record.id) }.to raise_error(ActiveRecord::RecordNotFound)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
MultiTenant.with(second_tenant) do
|
|
50
|
+
found_record = Project.find(second_record.id)
|
|
51
|
+
expect(found_record).to eq(second_record)
|
|
52
|
+
expect { Project.find(first_record.id) }.to raise_error(ActiveRecord::RecordNotFound)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
MultiTenant.without do
|
|
56
|
+
first_found = Project.find(first_record.id)
|
|
57
|
+
expect(first_found).to eq(first_record)
|
|
58
|
+
second_found = Project.find(second_record.id)
|
|
59
|
+
expect(second_found).to eq(second_record)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
19
62
|
end
|
data/spec/database.yml
CHANGED
data/spec/spec_helper.rb
CHANGED
|
@@ -42,4 +42,13 @@ end
|
|
|
42
42
|
MultiTenantTest::Application.config.secret_token = 'x' * 40
|
|
43
43
|
MultiTenantTest::Application.config.secret_key_base = 'y' * 40
|
|
44
44
|
|
|
45
|
+
def uses_prepared_statements?
|
|
46
|
+
if ActiveRecord::VERSION::MAJOR == 4
|
|
47
|
+
config = ActiveRecord::Base.connection.instance_variable_get(:@config)
|
|
48
|
+
config.fetch(:prepared_statements, true)
|
|
49
|
+
else
|
|
50
|
+
ActiveRecord::Base.connection.prepared_statements
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
45
54
|
require 'schema'
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activerecord-multi-tenant
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Citus Data
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2019-05-31 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: request_store
|
|
@@ -162,6 +162,7 @@ files:
|
|
|
162
162
|
- CHANGELOG.md
|
|
163
163
|
- Gemfile
|
|
164
164
|
- Gemfile.lock
|
|
165
|
+
- LICENSE
|
|
165
166
|
- README.md
|
|
166
167
|
- Rakefile
|
|
167
168
|
- activerecord-multi-tenant.gemspec
|
|
@@ -191,13 +192,13 @@ files:
|
|
|
191
192
|
- lib/activerecord-multi-tenant/multi_tenant.rb
|
|
192
193
|
- lib/activerecord-multi-tenant/query_monitor.rb
|
|
193
194
|
- lib/activerecord-multi-tenant/query_rewriter.rb
|
|
194
|
-
- lib/activerecord-multi-tenant/referential_integrity.rb
|
|
195
195
|
- lib/activerecord-multi-tenant/sidekiq.rb
|
|
196
196
|
- lib/activerecord-multi-tenant/version.rb
|
|
197
197
|
- lib/activerecord-multi-tenant/with_lock.rb
|
|
198
198
|
- spec/activerecord-multi-tenant/controller_extensions_spec.rb
|
|
199
199
|
- spec/activerecord-multi-tenant/fast_truncate_spec.rb
|
|
200
200
|
- spec/activerecord-multi-tenant/model_extensions_spec.rb
|
|
201
|
+
- spec/activerecord-multi-tenant/multi_tenant_spec.rb
|
|
201
202
|
- spec/activerecord-multi-tenant/query_rewriter_spec.rb
|
|
202
203
|
- spec/activerecord-multi-tenant/record_callback_spec.rb
|
|
203
204
|
- spec/activerecord-multi-tenant/record_finding_spec.rb
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# Workaround for https://github.com/citusdata/citus/issues/1080
|
|
2
|
-
# "Support DISABLE/ENABLE TRIGGER ALL on distributed tables"
|
|
3
|
-
|
|
4
|
-
require 'active_record/connection_adapters/postgresql_adapter'
|
|
5
|
-
|
|
6
|
-
module ActiveRecord
|
|
7
|
-
module ConnectionAdapters
|
|
8
|
-
class PostgreSQLAdapter < AbstractAdapter
|
|
9
|
-
def supports_disable_referential_integrity?
|
|
10
|
-
false
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|