activerecord-multi-tenant 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|