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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 016f85503236d02b8e0c0c91d48d1b1ea1be8090
4
- data.tar.gz: 98d861ee98e2453899c43ca479d40e0d3c072ad9
3
+ metadata.gz: 366debe159c5693b73fbca961fae7889febd5e00
4
+ data.tar.gz: 3670f0bd43e9074463c44e397ccbc1f25246bd4d
5
5
  SHA512:
6
- metadata.gz: c543e92bfaa83071358e43ee2fef050c0cd4692ef5a64b4e7a6fe5011a697e103e7e5b82b54314a937ea7f47a6fa6fda2516fac34419a4b42d609ce429642454
7
- data.tar.gz: cb09591190ce05fc3737f71716b6d9850d4613b23baaa9f1967905413be8d32941de64be30c23b466f41f8c2bb9c033d5e1e3ff91c2571068de4aa9cabc97a5a
6
+ metadata.gz: 9274691cbc15fcba204a5b9fcd01e550aefddcf3b0b37ec1aa329a66637dffb8e22d08b6fd9e55f63d42b2582146d544552921f8558722b047f31fb8f66fb9f1
7
+ data.tar.gz: 8e162537b956585c28734850d8f189433d8337854df10c9a1bf16637000c9c448eed6739763c8e20a488edf7d43743ea84254a137f0d77c6b7c84318a10d3c4b
@@ -18,6 +18,10 @@ gemfile:
18
18
  - gemfiles/active_record_5.1.gemfile
19
19
  - gemfiles/active_record_5.2.gemfile
20
20
 
21
+ env:
22
+ - PREPARED_STATEMENTS=0
23
+ - PREPARED_STATEMENTS=1
24
+
21
25
  matrix:
22
26
  fast_finish: true
23
27
  exclude:
@@ -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)
@@ -1,75 +1,85 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- activerecord-multi-tenant (0.9.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.0.1)
12
- actionpack (= 5.0.1)
13
- nio4r (~> 1.2)
14
- websocket-driver (~> 0.6.1)
15
- actionmailer (5.0.1)
16
- actionpack (= 5.0.1)
17
- actionview (= 5.0.1)
18
- activejob (= 5.0.1)
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.0.1)
22
- actionview (= 5.0.1)
23
- activesupport (= 5.0.1)
21
+ actionpack (5.2.2)
22
+ actionview (= 5.2.2)
23
+ activesupport (= 5.2.2)
24
24
  rack (~> 2.0)
25
- rack-test (~> 0.6.3)
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.0.1)
29
- activesupport (= 5.0.1)
28
+ actionview (5.2.2)
29
+ activesupport (= 5.2.2)
30
30
  builder (~> 3.1)
31
- erubis (~> 2.7.0)
31
+ erubi (~> 1.4)
32
32
  rails-dom-testing (~> 2.0)
33
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
34
- activejob (5.0.1)
35
- activesupport (= 5.0.1)
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.0.1)
38
- activesupport (= 5.0.1)
39
- activerecord (5.0.1)
40
- activemodel (= 5.0.1)
41
- activesupport (= 5.0.1)
42
- arel (~> 7.0)
43
- activesupport (5.0.1)
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 (~> 0.7)
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 (7.1.4)
53
- builder (3.2.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.0.4)
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
- erubis (2.7.0)
60
- globalid (0.4.1)
64
+ erubi (1.7.1)
65
+ globalid (0.4.2)
61
66
  activesupport (>= 4.2.0)
62
- i18n (0.7.0)
63
- loofah (2.0.3)
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.0)
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
- mini_mime (1.0.0)
77
+ mimemagic (0.3.3)
78
+ mini_mime (1.0.1)
69
79
  mini_portile2 (2.3.0)
70
- minitest (5.10.1)
71
- nio4r (1.2.1)
72
- nokogiri (1.8.2)
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.4)
92
+ rack (2.0.6)
83
93
  rack-protection (2.0.1)
84
94
  rack
85
- rack-test (0.6.3)
86
- rack (>= 1.0)
87
- rails (5.0.1)
88
- actioncable (= 5.0.1)
89
- actionmailer (= 5.0.1)
90
- actionpack (= 5.0.1)
91
- actionview (= 5.0.1)
92
- activejob (= 5.0.1)
93
- activemodel (= 5.0.1)
94
- activerecord (= 5.0.1)
95
- activesupport (= 5.0.1)
96
- bundler (>= 1.3.0, < 2.0)
97
- railties (= 5.0.1)
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.2)
100
- activesupport (>= 4.2.0, < 6.0)
101
- nokogiri (~> 1.6)
102
- rails-html-sanitizer (1.0.3)
103
- loofah (~> 2.0)
104
- railties (5.0.1)
105
- actionpack (= 5.0.1)
106
- activesupport (= 5.0.1)
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.18.1, < 2.0)
110
- rake (12.0.0)
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.1)
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.19.4)
159
+ thor (0.20.3)
149
160
  thread_safe (0.3.6)
150
- tzinfo (1.2.3)
161
+ tzinfo (1.2.5)
151
162
  thread_safe (~> 0.1)
152
- websocket-driver (0.6.5)
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
- 1.16.1
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
- Licensed under the MIT license<br>
103
- Copyright (c) 2018, Citus Data Inc.
102
+ Copyright (c) 2018, Citus Data Inc.<br>
103
+ Licensed under the MIT license, see LICENSE file for details.
@@ -2,20 +2,25 @@ version: '2.1'
2
2
 
3
3
  services:
4
4
  master:
5
- container_name: "${COMPOSE_PROJECT_NAME:-citus}_master"
6
- image: 'citusdata/citus:7.4.1'
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.4.1'
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.4.1'
19
+ image: 'citusdata/citus:7.5.1'
15
20
  labels: ['com.citusdata.role=Worker']
16
21
  depends_on: { manager: { condition: service_healthy } }
17
- manager:
18
- container_name: "${COMPOSE_PROJECT_NAME:-citus}_manager"
19
- image: 'citusdata/membership-manager:0.2.0'
20
- volumes: ['/var/run/docker.sock:/var/run/docker.sock']
21
- depends_on: { master: { condition: service_healthy } }
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
- if MultiTenant.current_tenant_id
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)
@@ -1,3 +1,3 @@
1
1
  module MultiTenant
2
- VERSION = '0.9.0'
2
+ VERSION = '0.10.0'
3
3
  end
@@ -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 = <<-sql
313
- 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
314
- sql
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 = <<-sql.strip
337
- SELECT "projects".* FROM "projects" WHERE "projects"."account_id" = #{account.id} AND "projects"."id" = #{project.id} LIMIT 1
338
- sql
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.with(nil) do
344
- expected_sql = <<-sql.strip
345
- SELECT "projects".* FROM "projects" WHERE 1=1 AND "projects"."id" = #{project2.id} LIMIT 1
346
- sql
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
@@ -6,6 +6,6 @@ test:
6
6
  port: 5600
7
7
  pool: 5
8
8
  timeout: 5000
9
- prepared_statements: false
9
+ prepared_statements: <%= ENV.fetch('PREPARED_STATEMENTS', '1') == '1' %>
10
10
  variables:
11
11
  citus.shard_count: 5
@@ -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.9.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: 2018-06-23 00:00:00.000000000 Z
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