activerecord-multi-tenant 0.5.0 → 0.6.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: fc1deedd61a8e692c880e666ccd16542079b3b5a
4
- data.tar.gz: e71ddea3d21d23c511e0d85d947b974f5ff40591
3
+ metadata.gz: 3923b91001e63778ed81b5d797c2204857ef31d8
4
+ data.tar.gz: 8b4c76c828bcfa5b29d80fb0c7e8b4bdbb0ff7c1
5
5
  SHA512:
6
- metadata.gz: c7354a9d0c30caa2264d35739edf4f181945bf035326c13354824fee2eb3d2717804236b412d5f5fa9352a498a62aba7c846b12fcd4b0566e907b0bbca3276a5
7
- data.tar.gz: 6d9aaf1c06cc24daf27423eec9fe1c032070f109c06d69c0c915c68ab549da89e37057388a65aed42e400a13abbc206285fa744216fbbed25ee35f2f2a106a05
6
+ metadata.gz: 259c8320b79b56b232a3d981609b3419757cac0315087350e27c5c9613852c899487d773fd3fb56f4a684cad7fc7633fa208b0b3b203e0331c3b78400f83decc
7
+ data.tar.gz: 7ec81cf673bf8eae9e53267c65222d104421b2cee3df0d12e72fe5d12f8723da069617ced3b74c75651a4fd5aa2d091d1bd6e0437492ec959b472a88d1bfeffd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0 2017-06-09
4
+
5
+ * Query rewriter
6
+ * Change hook from per-relation to be pre-SQL statement thats being output
7
+ - This should resolve issues where we added conditions in the wrong place
8
+ * Use table name to model klass registry
9
+ * Improve tests for activerecord-multi-tenant
10
+ * Use lower shard count to speed up tests
11
+ * Drop database cleaner dependency
12
+
13
+
3
14
  ## 0.5.0 2017-05-08
4
15
 
5
16
  * Write-only mode that enables step-by-step migrations
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- activerecord-multi-tenant (0.5.0)
4
+ activerecord-multi-tenant (0.6.0)
5
5
  rails (>= 3.1)
6
6
  request_store (>= 1.0.5)
7
7
 
@@ -52,7 +52,6 @@ GEM
52
52
  arel (7.1.4)
53
53
  builder (3.2.2)
54
54
  concurrent-ruby (1.0.4)
55
- database_cleaner (1.3.0)
56
55
  diff-lcs (1.2.5)
57
56
  erubis (2.7.0)
58
57
  globalid (0.4.0)
@@ -142,7 +141,6 @@ PLATFORMS
142
141
  DEPENDENCIES
143
142
  activerecord-multi-tenant!
144
143
  appraisal
145
- database_cleaner (~> 1.3.0)
146
144
  pg
147
145
  rake
148
146
  rspec (>= 3.0)
@@ -20,7 +20,6 @@ Gem::Specification.new do |s|
20
20
 
21
21
  s.add_development_dependency 'rspec', '>= 3.0'
22
22
  s.add_development_dependency 'rspec-rails'
23
- s.add_development_dependency 'database_cleaner', '~> 1.3.0'
24
23
  s.add_development_dependency 'pg'
25
24
  s.add_development_dependency 'rake'
26
25
  s.add_development_dependency 'thor'
data/docker-compose.yml CHANGED
@@ -2,15 +2,15 @@ version: '2'
2
2
 
3
3
  services:
4
4
  master:
5
- image: 'citusdata/citus:6.1.0'
5
+ image: 'citusdata/citus:6.2.1'
6
6
  ports: ['5600:5432']
7
7
  labels: ['com.citusdata.role=Master']
8
8
  volumes: ['/var/run/postgresql']
9
9
  worker1:
10
- image: 'citusdata/citus:6.1.0'
10
+ image: 'citusdata/citus:6.2.1'
11
11
  labels: ['com.citusdata.role=Worker']
12
12
  worker2:
13
- image: 'citusdata/citus:6.1.0'
13
+ image: 'citusdata/citus:6.2.1'
14
14
  labels: ['com.citusdata.role=Worker']
15
15
  config:
16
16
  image: 'citusdata/workerlist-gen:2.0.0'
@@ -52,7 +52,6 @@ GEM
52
52
  arel (8.0.0)
53
53
  builder (3.2.3)
54
54
  concurrent-ruby (1.0.5)
55
- database_cleaner (1.3.0)
56
55
  diff-lcs (1.3)
57
56
  erubi (1.6.0)
58
57
  globalid (0.4.0)
@@ -143,7 +142,6 @@ DEPENDENCIES
143
142
  activerecord (= 5.1.0)
144
143
  activerecord-multi-tenant!
145
144
  appraisal
146
- database_cleaner (~> 1.3.0)
147
145
  pg
148
146
  rake
149
147
  rspec (>= 3.0)
@@ -41,7 +41,6 @@ GEM
41
41
  thor (>= 0.14.0)
42
42
  arel (3.0.3)
43
43
  builder (3.0.4)
44
- database_cleaner (1.3.0)
45
44
  diff-lcs (1.3)
46
45
  erubis (2.7.0)
47
46
  hike (1.2.3)
@@ -123,7 +122,6 @@ PLATFORMS
123
122
  DEPENDENCIES
124
123
  activerecord-multi-tenant!
125
124
  appraisal
126
- database_cleaner (~> 1.3.0)
127
125
  pg
128
126
  rails (= 3.2.22.5)
129
127
  rake
@@ -39,7 +39,6 @@ GEM
39
39
  arel (4.0.2)
40
40
  builder (3.1.4)
41
41
  concurrent-ruby (1.0.5)
42
- database_cleaner (1.3.0)
43
42
  diff-lcs (1.3)
44
43
  erubis (2.7.0)
45
44
  i18n (0.8.1)
@@ -107,7 +106,6 @@ PLATFORMS
107
106
  DEPENDENCIES
108
107
  activerecord-multi-tenant!
109
108
  appraisal
110
- database_cleaner (~> 1.3.0)
111
109
  pg
112
110
  rails (= 4.0.13)
113
111
  rake
@@ -41,7 +41,6 @@ GEM
41
41
  arel (5.0.1.20140414130214)
42
42
  builder (3.2.3)
43
43
  concurrent-ruby (1.0.5)
44
- database_cleaner (1.3.0)
45
44
  diff-lcs (1.3)
46
45
  erubis (2.7.0)
47
46
  i18n (0.8.1)
@@ -112,7 +111,6 @@ PLATFORMS
112
111
  DEPENDENCIES
113
112
  activerecord-multi-tenant!
114
113
  appraisal
115
- database_cleaner (~> 1.3.0)
116
114
  pg
117
115
  rails (= 4.1.16)
118
116
  rake
@@ -49,7 +49,6 @@ GEM
49
49
  arel (6.0.4)
50
50
  builder (3.2.3)
51
51
  concurrent-ruby (1.0.5)
52
- database_cleaner (1.3.0)
53
52
  diff-lcs (1.3)
54
53
  erubis (2.7.0)
55
54
  globalid (0.3.7)
@@ -135,7 +134,6 @@ PLATFORMS
135
134
  DEPENDENCIES
136
135
  activerecord-multi-tenant!
137
136
  appraisal
138
- database_cleaner (~> 1.3.0)
139
137
  pg
140
138
  rails (= 4.2.8)
141
139
  rake
@@ -52,7 +52,6 @@ GEM
52
52
  arel (7.1.4)
53
53
  builder (3.2.3)
54
54
  concurrent-ruby (1.0.5)
55
- database_cleaner (1.3.0)
56
55
  diff-lcs (1.3)
57
56
  erubis (2.7.0)
58
57
  globalid (0.3.7)
@@ -142,7 +141,6 @@ PLATFORMS
142
141
  DEPENDENCIES
143
142
  activerecord-multi-tenant!
144
143
  appraisal
145
- database_cleaner (~> 1.3.0)
146
144
  pg
147
145
  rails (= 5.0.1)
148
146
  rake
@@ -52,7 +52,6 @@ GEM
52
52
  arel (8.0.0)
53
53
  builder (3.2.3)
54
54
  concurrent-ruby (1.0.5)
55
- database_cleaner (1.3.0)
56
55
  diff-lcs (1.3)
57
56
  erubi (1.6.0)
58
57
  globalid (0.4.0)
@@ -142,7 +141,6 @@ PLATFORMS
142
141
  DEPENDENCIES
143
142
  activerecord-multi-tenant!
144
143
  appraisal
145
- database_cleaner (~> 1.3.0)
146
144
  pg
147
145
  rails (= 5.1.0)
148
146
  rake
@@ -25,7 +25,9 @@ module MultiTenant
25
25
  END IF;
26
26
  END LOOP;
27
27
 
28
- EXECUTE 'TRUNCATE TABLE ' || array_to_string(tables, ', ') || ' RESTART IDENTITY CASCADE';
28
+ IF array_length(tables, 1) > 0 THEN
29
+ EXECUTE 'TRUNCATE TABLE ' || array_to_string(tables, ', ') || ' RESTART IDENTITY CASCADE';
30
+ END IF;
29
31
  END$$;), exclude.map { |t| "'" + t + "'" }.join('\n'))
30
32
  end
31
33
  end
@@ -34,6 +34,8 @@ module MultiTenant
34
34
  end
35
35
  end
36
36
 
37
+ MultiTenant.register_multi_tenant_model(table_name, self)
38
+
37
39
  @partition_key = options[:partition_key] || MultiTenant.partition_key(tenant_name)
38
40
  partition_key = @partition_key
39
41
 
@@ -11,7 +11,7 @@ module MultiTenant
11
11
 
12
12
  # In some cases we only have an ID - if defined we'll return the default tenant class in such cases
13
13
  def self.default_tenant_class=(tenant_class); @@default_tenant_class = tenant_class; end
14
- def self.default_tenant_class; @@default_tenant_class; end
14
+ def self.default_tenant_class; @@default_tenant_class ||= nil; end
15
15
 
16
16
  # Write-only Mode - this only adds the tenant_id to new records, but doesn't
17
17
  # require its presence for SELECTs/UPDATEs/DELETEs
@@ -23,6 +23,15 @@ module MultiTenant
23
23
  def self.enable_with_lock_workaround; @@enable_with_lock_workaround = true; end
24
24
  def self.with_lock_workaround_enabled?; @@enable_with_lock_workaround; end
25
25
 
26
+ # Registry that maps table names to models (used by the query rewriter)
27
+ def self.register_multi_tenant_model(table_name, model_klass)
28
+ @@multi_tenant_models ||= {}
29
+ @@multi_tenant_models[table_name.to_s] = model_klass
30
+ end
31
+ def self.multi_tenant_model_for_table(table_name)
32
+ @@multi_tenant_models[table_name.to_s]
33
+ end
34
+
26
35
  def self.current_tenant=(tenant)
27
36
  RequestStore.store[:current_tenant] = tenant
28
37
  end
@@ -1,47 +1,51 @@
1
1
  require 'active_record'
2
2
 
3
- class TTTenantVisitor < Arel::Visitors::DepthFirst
4
- def initialize(arel)
5
- super(Proc.new {})
6
- @tenant_relations = []
3
+ module MultiTenant
4
+ class ArelTenantVisitor < Arel::Visitors::DepthFirst
5
+ def initialize(arel)
6
+ super(Proc.new {})
7
+ @tenant_relations = []
7
8
 
8
- accept(arel.ast)
9
- end
9
+ accept(arel.ast)
10
+ end
10
11
 
11
- def tenant_relations
12
- @tenant_relations.uniq
13
- end
12
+ def tenant_relations
13
+ @tenant_relations.uniq
14
+ end
14
15
 
15
- def visit_Arel_Table(table, _collector = nil)
16
- @tenant_relations << table if tenant_relation?(table)
17
- end
16
+ def visit_Arel_Table(table, _collector = nil)
17
+ @tenant_relations << table if tenant_relation?(table)
18
+ end
18
19
 
19
- def visit_Arel_Nodes_TableAlias(table_alias, _collector = nil)
20
- @tenant_relations << table_alias if tenant_relation?(table_alias.left)
21
- end
20
+ def visit_Arel_Nodes_TableAlias(table_alias, _collector = nil)
21
+ @tenant_relations << table_alias if tenant_relation?(table_alias.left)
22
+ end
22
23
 
23
- private
24
+ private
24
25
 
25
- def tenant_relation?(table)
26
- model = table.name.classify.constantize
27
- model && model.respond_to?(:scoped_by_tenant?) && model.scoped_by_tenant?
26
+ def tenant_relation?(table)
27
+ MultiTenant.multi_tenant_model_for_table(table.name).present?
28
+ end
28
29
  end
29
30
  end
30
31
 
31
32
  module ActiveRecord
32
- module QueryMethods
33
- alias :build_arel_orig :build_arel
34
- def build_arel
35
- arel = build_arel_orig
36
-
37
- if MultiTenant.current_tenant_id && !MultiTenant.with_write_only_mode_enabled?
38
- relations_needing_tenant_id = TTTenantVisitor.new(arel).tenant_relations
39
- arel = relations_needing_tenant_id.reduce(arel) do |arel, relation|
40
- arel.where(relation[self.partition_key].eq(MultiTenant.current_tenant_id))
33
+ module ConnectionAdapters # :nodoc:
34
+ module DatabaseStatements
35
+ alias :to_sql_orig :to_sql
36
+ # Converts an arel AST to SQL
37
+ def to_sql(arel, binds = [])
38
+ if MultiTenant.current_tenant_id && !MultiTenant.with_write_only_mode_enabled? &&
39
+ [Arel::SelectManager, Arel::UpdateManager, Arel::DeleteManager, ActiveRecord::Relation].include?(arel.class)
40
+ relations_needing_tenant_id = MultiTenant::ArelTenantVisitor.new(arel).tenant_relations
41
+ arel = relations_needing_tenant_id.reduce(arel) do |arel, relation|
42
+ model = MultiTenant.multi_tenant_model_for_table(relation.table_name)
43
+ next arel unless model.present?
44
+ arel.where(relation[model.partition_key].eq(MultiTenant.current_tenant_id))
45
+ end
41
46
  end
47
+ to_sql_orig(arel, binds)
42
48
  end
43
-
44
- arel
45
49
  end
46
50
  end
47
51
  end
@@ -23,7 +23,6 @@ module Sidekiq::Middleware::MultiTenant
23
23
  yield
24
24
  end
25
25
  else
26
- Rails.logger.warn("Running #{worker_class} without tenant.")
27
26
  yield
28
27
  end
29
28
  end
@@ -1,3 +1,3 @@
1
1
  module MultiTenant
2
- VERSION = '0.5.0'
2
+ VERSION = '0.6.0'
3
3
  end
@@ -146,6 +146,22 @@ describe MultiTenant do
146
146
  end
147
147
  end
148
148
 
149
+ describe 'eager loading' do
150
+ let(:account) { Account.create!(name: 'foo') }
151
+ let(:project) { Project.create!(name: 'project', account: account) }
152
+ let(:manager) { Manager.create!(name: 'manager', account: account, project: project) }
153
+ let(:task) { project.tasks.create!(name: 'task') }
154
+ let(:sub_task) { task.sub_tasks.create!(name: 'sub task') }
155
+
156
+ it 'handles table aliases through joins' do
157
+ MultiTenant.with(account) do
158
+ sub_task
159
+ manager
160
+ expect(Project.eager_load([{manager: :project}, {tasks: :project}]).first).to eq project
161
+ end
162
+ end
163
+ end
164
+
149
165
  describe 'Subclass of Multi Tenant Model' do
150
166
  let(:account) { Account.create!(name: 'foo') }
151
167
  let(:project) { Project.create!(name: 'project', account: account) }
@@ -4,18 +4,18 @@ describe MultiTenant, 'Record modifications' do
4
4
  let(:account) { Account.create! name: 'test' }
5
5
  let(:project) { account.projects.create! name: 'something' }
6
6
 
7
- it 'includes the tenant_id in UPDATEs' do
8
- project.name = 'something else'
9
- project.save!
7
+ it 'includes the tenant_id in DELETEs' do
8
+ project.destroy
10
9
  MultiTenant.with(account) do
11
- expect(Project.find(project.id).name).to eq 'something else'
10
+ expect(Project.where(id: project.id).first).not_to be_present
12
11
  end
13
12
  end
14
13
 
15
- it 'includes the tenant_id in DELETEs' do
16
- project.destroy
14
+ it 'includes the tenant_id in UPDATEs' do
15
+ project.name = 'something else'
16
+ project.save!
17
17
  MultiTenant.with(account) do
18
- expect(Project.where(id: project.id).first).not_to be_present
18
+ expect(Project.find(project.id).name).to eq 'something else'
19
19
  end
20
20
  end
21
21
  end
data/spec/database.yml CHANGED
@@ -7,3 +7,5 @@ test:
7
7
  pool: 5
8
8
  timeout: 5000
9
9
  prepared_statements: false
10
+ variables:
11
+ citus.shard_count: 5
data/spec/spec_helper.rb CHANGED
@@ -4,7 +4,6 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
4
  require 'active_record/railtie'
5
5
  require 'action_controller/railtie'
6
6
  require 'rspec/rails'
7
- require 'database_cleaner'
8
7
 
9
8
  require 'activerecord-multi-tenant'
10
9
 
@@ -22,19 +21,14 @@ RSpec.configure do |config|
22
21
  end
23
22
 
24
23
  config.before(:suite) do
25
- DatabaseCleaner[:active_record].strategy = :truncation
26
- DatabaseCleaner[:active_record].clean
24
+ MultiTenant::FastTruncate.run
27
25
 
28
26
  # Keep this here until https://github.com/citusdata/citus/issues/1236 is fixed
29
27
  MultiTenant.enable_with_lock_workaround
30
28
  end
31
29
 
32
- config.before(:each) do
33
- DatabaseCleaner[:active_record].start
34
- end
35
-
36
30
  config.after(:each) do
37
- DatabaseCleaner[:active_record].clean
31
+ MultiTenant::FastTruncate.run
38
32
  end
39
33
  end
40
34
 
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.5.0
4
+ version: 0.6.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: 2017-05-08 00:00:00.000000000 Z
11
+ date: 2017-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: request_store
@@ -66,20 +66,6 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: database_cleaner
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: 1.3.0
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: 1.3.0
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: pg
85
71
  requirement: !ruby/object:Gem::Requirement
@@ -193,7 +179,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
193
179
  version: '0'
194
180
  requirements: []
195
181
  rubyforge_project:
196
- rubygems_version: 2.4.5.1
182
+ rubygems_version: 2.5.1
197
183
  signing_key:
198
184
  specification_version: 4
199
185
  summary: ActiveRecord/Rails integration for multi-tenant databases, in particular