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 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