activerecord-multi-tenant 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/CI.yml +10 -0
- data/CHANGELOG.md +9 -0
- data/activerecord-multi-tenant.gemspec +1 -2
- data/gemfiles/active_record_5.2.3.gemfile +16 -0
- data/gemfiles/active_record_5.2.gemfile +1 -1
- data/gemfiles/rails_5.2.3.gemfile +16 -0
- data/gemfiles/rails_5.2.gemfile +1 -1
- data/lib/activerecord-multi-tenant/model_extensions.rb +10 -4
- data/lib/activerecord-multi-tenant/multi_tenant.rb +22 -8
- data/lib/activerecord-multi-tenant/query_rewriter.rb +4 -7
- data/lib/activerecord-multi-tenant/version.rb +1 -1
- data/spec/activerecord-multi-tenant/model_extensions_spec.rb +51 -0
- data/spec/schema.rb +24 -0
- data/spec/spec_helper.rb +7 -0
- metadata +6 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 457cfac4996b93bb7b0b25cd99c2687f2742a8bc580888008baa4f903255cd74
|
4
|
+
data.tar.gz: 8c369e04a906752deda52d9b7b78a2377551e4b85f74e3b6d4b0742d30147783
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7715dadcbd9cd13d86135518d3fc62283d9ab85e802016ab1be15fe39d815ec344c1d3e4699f96870b6e50b6d3b66b4a38f780d07ad6d3d4b8e21668deacca18
|
7
|
+
data.tar.gz: d1d364ebde87012cc5b91a6cf812a1a8cc0f89bb5ee8859b3de01ab0392dc5a62b74a3f93ac721d3e583343b703fc872d73c1f301cff9716e686492f06922d36
|
data/.github/workflows/CI.yml
CHANGED
@@ -19,10 +19,12 @@ jobs:
|
|
19
19
|
- '3.0'
|
20
20
|
- '3.1'
|
21
21
|
gemfile:
|
22
|
+
- rails_5.2.3
|
22
23
|
- rails_5.2
|
23
24
|
- rails_6.0
|
24
25
|
- rails_6.1
|
25
26
|
- rails_7.0
|
27
|
+
- active_record_5.2.3
|
26
28
|
- active_record_5.2
|
27
29
|
- active_record_6.0
|
28
30
|
- active_record_6.1
|
@@ -47,6 +49,14 @@ jobs:
|
|
47
49
|
gemfile: 'rails_5.2'
|
48
50
|
- ruby: '3.1'
|
49
51
|
gemfile: 'active_record_5.2'
|
52
|
+
- ruby: '3.0'
|
53
|
+
gemfile: 'rails_5.2.3'
|
54
|
+
- ruby: '3.0'
|
55
|
+
gemfile: 'active_record_5.2.3'
|
56
|
+
- ruby: '3.1'
|
57
|
+
gemfile: 'rails_5.2.3'
|
58
|
+
- ruby: '3.1'
|
59
|
+
gemfile: 'active_record_5.2.3'
|
50
60
|
name: Ruby ${{ matrix.ruby }} / ${{ matrix.gemfile }} ${{ (matrix.prepared_statements && 'w/ prepared statements') || '' }}
|
51
61
|
env:
|
52
62
|
BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 2.0.0 2022-05-19
|
4
|
+
|
5
|
+
* Replace RequestStore with CurrentAttributes [#139](https://github.com/citusdata/activerecord-multi-tenant/pull/139)
|
6
|
+
* Support changing table_name after calling multi_tenant [#128](https://github.com/citusdata/activerecord-multi-tenant/pull/128)
|
7
|
+
* Allow to use uuid as primary key on partition table [#112](https://github.com/citusdata/activerecord-multi-tenant/pull/112)
|
8
|
+
* Support latest Rails 5.2 [#145](https://github.com/citusdata/activerecord-multi-tenant/pull/145)
|
9
|
+
* Support optional: true for belongs_to [#147](https://github.com/citusdata/activerecord-multi-tenant/pull/147)
|
10
|
+
|
11
|
+
|
3
12
|
## 1.2.0 2022-03-29
|
4
13
|
|
5
14
|
* Test Rails 7 & Ruby 3
|
@@ -15,8 +15,7 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.homepage = 'https://github.com/citusdata/activerecord-multi-tenant'
|
16
16
|
s.license = 'MIT'
|
17
17
|
|
18
|
-
s.
|
19
|
-
s.add_dependency('rails','>= 4.2')
|
18
|
+
s.add_dependency 'rails', '>= 5.2'
|
20
19
|
|
21
20
|
s.add_development_dependency 'rspec', '>= 3.0'
|
22
21
|
s.add_development_dependency 'rspec-rails'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "appraisal"
|
6
|
+
gem "activerecord", "~> 5.2.0", "< 5.2.4" # FIXME
|
7
|
+
gem "i18n", "~> 0.9.5"
|
8
|
+
gem "nokogiri", "~> 1.7.1"
|
9
|
+
gem "nio4r", "~> 2.3.1"
|
10
|
+
gem "sprockets", "~> 3.7.1"
|
11
|
+
gem "byebug", "~> 11.0"
|
12
|
+
gem "rake", "12.0.0"
|
13
|
+
gem "redis", "3.3.3"
|
14
|
+
gem "pry-byebug", "3.9.0"
|
15
|
+
|
16
|
+
gemspec path: "../"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "appraisal"
|
6
|
+
gem "rails", "~> 5.2.0", "< 5.2.4" # FIXME
|
7
|
+
gem "i18n", "~> 0.9.5"
|
8
|
+
gem "nokogiri", "~> 1.7.1"
|
9
|
+
gem "nio4r", "~> 2.3.1"
|
10
|
+
gem "sprockets", "~> 3.7.1"
|
11
|
+
gem "byebug", "~> 11.0"
|
12
|
+
gem "rake", "12.0.0"
|
13
|
+
gem "redis", "3.3.3"
|
14
|
+
gem "pry-byebug", "3.9.0"
|
15
|
+
|
16
|
+
gemspec path: "../"
|
data/gemfiles/rails_5.2.gemfile
CHANGED
@@ -6,7 +6,13 @@ module MultiTenant
|
|
6
6
|
if to_s.underscore.to_sym == tenant_name
|
7
7
|
unless MultiTenant.with_write_only_mode_enabled?
|
8
8
|
# This is the tenant model itself. Workaround for https://github.com/citusdata/citus/issues/687
|
9
|
-
before_create ->
|
9
|
+
before_create -> do
|
10
|
+
if self.class.columns_hash[self.class.primary_key].type == :uuid
|
11
|
+
self.id ||= SecureRandom.uuid
|
12
|
+
else
|
13
|
+
self.id ||= self.class.connection.select_value("SELECT nextval('#{self.class.table_name}_#{self.class.primary_key}_seq'::regclass)")
|
14
|
+
end
|
15
|
+
end
|
10
16
|
end
|
11
17
|
else
|
12
18
|
class << self
|
@@ -38,18 +44,18 @@ module MultiTenant
|
|
38
44
|
|
39
45
|
def inherited(subclass)
|
40
46
|
super
|
41
|
-
MultiTenant.register_multi_tenant_model(subclass
|
47
|
+
MultiTenant.register_multi_tenant_model(subclass)
|
42
48
|
end
|
43
49
|
end
|
44
50
|
|
45
|
-
MultiTenant.register_multi_tenant_model(
|
51
|
+
MultiTenant.register_multi_tenant_model(self)
|
46
52
|
|
47
53
|
@partition_key = options[:partition_key] || MultiTenant.partition_key(tenant_name)
|
48
54
|
partition_key = @partition_key
|
49
55
|
|
50
56
|
# Create an implicit belongs_to association only if tenant class exists
|
51
57
|
if MultiTenant.tenant_klass_defined?(tenant_name)
|
52
|
-
belongs_to tenant_name, **options.slice(:class_name, :inverse_of).merge(foreign_key: options[:partition_key])
|
58
|
+
belongs_to tenant_name, **options.slice(:class_name, :inverse_of, :optional).merge(foreign_key: options[:partition_key])
|
53
59
|
end
|
54
60
|
|
55
61
|
# New instances should have the tenant set
|
@@ -1,6 +1,10 @@
|
|
1
|
-
require '
|
1
|
+
require 'active_support/current_attributes'
|
2
2
|
|
3
3
|
module MultiTenant
|
4
|
+
class Current < ::ActiveSupport::CurrentAttributes
|
5
|
+
attribute :tenant
|
6
|
+
end
|
7
|
+
|
4
8
|
def self.tenant_klass_defined?(tenant_name)
|
5
9
|
!!tenant_name.to_s.classify.safe_constantize
|
6
10
|
end
|
@@ -24,13 +28,23 @@ module MultiTenant
|
|
24
28
|
def self.with_lock_workaround_enabled?; @@enable_with_lock_workaround; end
|
25
29
|
|
26
30
|
# Registry that maps table names to models (used by the query rewriter)
|
27
|
-
def self.register_multi_tenant_model(
|
28
|
-
@@multi_tenant_models ||=
|
29
|
-
@@multi_tenant_models
|
31
|
+
def self.register_multi_tenant_model(model_klass)
|
32
|
+
@@multi_tenant_models ||= []
|
33
|
+
@@multi_tenant_models.push(model_klass)
|
34
|
+
|
35
|
+
remove_class_variable(:@@multi_tenant_model_table_names) if defined?(@@multi_tenant_model_table_names)
|
30
36
|
end
|
37
|
+
|
31
38
|
def self.multi_tenant_model_for_table(table_name)
|
32
|
-
@@multi_tenant_models ||=
|
33
|
-
|
39
|
+
@@multi_tenant_models ||= []
|
40
|
+
|
41
|
+
if !defined?(@@multi_tenant_model_table_names)
|
42
|
+
@@multi_tenant_model_table_names = @@multi_tenant_models.map { |model|
|
43
|
+
[model.table_name, model] if model.table_name
|
44
|
+
}.compact.to_h
|
45
|
+
end
|
46
|
+
|
47
|
+
@@multi_tenant_model_table_names[table_name.to_s]
|
34
48
|
end
|
35
49
|
|
36
50
|
def self.multi_tenant_model_for_arel(arel)
|
@@ -43,11 +57,11 @@ module MultiTenant
|
|
43
57
|
end
|
44
58
|
|
45
59
|
def self.current_tenant=(tenant)
|
46
|
-
|
60
|
+
Current.tenant = tenant
|
47
61
|
end
|
48
62
|
|
49
63
|
def self.current_tenant
|
50
|
-
|
64
|
+
Current.tenant
|
51
65
|
end
|
52
66
|
|
53
67
|
def self.current_tenant_id
|
@@ -295,12 +295,9 @@ module ActiveRecord
|
|
295
295
|
end
|
296
296
|
|
297
297
|
node_list.select{ |n| n.is_a? Arel::Nodes::Join }.each do |node_join|
|
298
|
-
if
|
299
|
-
(ActiveRecord::VERSION::MAJOR == 5 &&
|
300
|
-
!node_join.right.expr.right.is_a?(Arel::Attributes::Attribute)))
|
298
|
+
if !node_join.right
|
301
299
|
next
|
302
300
|
end
|
303
|
-
|
304
301
|
relation_right, relation_left = relations_from_node_join(node_join)
|
305
302
|
|
306
303
|
next unless relation_right && relation_left
|
@@ -322,13 +319,13 @@ module ActiveRecord
|
|
322
319
|
|
323
320
|
private
|
324
321
|
def relations_from_node_join(node_join)
|
325
|
-
if
|
322
|
+
if node_join.right.expr.is_a?(Arel::Nodes::Equality)
|
326
323
|
return node_join.right.expr.right.relation, node_join.right.expr.left.relation
|
327
324
|
end
|
328
325
|
|
329
|
-
children = node_join.right.expr.children
|
326
|
+
children = [node_join.right.expr.children].flatten
|
330
327
|
|
331
|
-
tenant_applied = children.any?(MultiTenant::TenantEnforcementClause) ||
|
328
|
+
tenant_applied = children.any?{|c| c.is_a?(MultiTenant::TenantEnforcementClause) || c.is_a?(MultiTenant::TenantJoinEnforcementClause)}
|
332
329
|
if tenant_applied || children.empty?
|
333
330
|
return nil, nil
|
334
331
|
end
|
@@ -70,6 +70,47 @@ describe MultiTenant do
|
|
70
70
|
it { expect(@partition_key_not_model_task.non_model_id).to be 77 }
|
71
71
|
end
|
72
72
|
|
73
|
+
describe 'Changes table_name after multi_tenant called' do
|
74
|
+
before do
|
75
|
+
account_klass.has_many(:posts, anonymous_class: post_klass)
|
76
|
+
post_klass.belongs_to(:account, anonymous_class: account_klass)
|
77
|
+
|
78
|
+
@account1 = account_klass.create! name: 'foo'
|
79
|
+
@account2 = account_klass.create! name: 'bar'
|
80
|
+
|
81
|
+
@post1 = @account1.posts.create! name: 'foobar'
|
82
|
+
@post2 = @account2.posts.create! name: 'baz'
|
83
|
+
|
84
|
+
MultiTenant.current_tenant = @account1
|
85
|
+
@posts = post_klass.all
|
86
|
+
end
|
87
|
+
|
88
|
+
let(:account_klass) do
|
89
|
+
Class.new(Account) do
|
90
|
+
def self.name
|
91
|
+
'Account'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
let(:post_klass) do
|
97
|
+
Class.new(ActiveRecord::Base) do
|
98
|
+
self.table_name = 'unknown'
|
99
|
+
|
100
|
+
multi_tenant(:account)
|
101
|
+
|
102
|
+
self.table_name = 'posts'
|
103
|
+
|
104
|
+
def self.name
|
105
|
+
'Post'
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
it { expect(@posts.length).to eq(1) }
|
111
|
+
it { expect(@posts).to eq([@post1]) }
|
112
|
+
end
|
113
|
+
|
73
114
|
# Scoping models
|
74
115
|
describe 'Project.all should be scoped to the current tenant if set' do
|
75
116
|
before do
|
@@ -144,6 +185,16 @@ describe MultiTenant do
|
|
144
185
|
end
|
145
186
|
end
|
146
187
|
|
188
|
+
it 'handles belongs_to with optional: true' do
|
189
|
+
MultiTenant.with(account) do
|
190
|
+
sub_task
|
191
|
+
end
|
192
|
+
|
193
|
+
record = sub_task.optional_sub_tasks.create!
|
194
|
+
expect(record.reload.sub_task).to eq(sub_task)
|
195
|
+
expect(record.account_id).to eq(nil)
|
196
|
+
end
|
197
|
+
|
147
198
|
it 'handles has_many through' do
|
148
199
|
MultiTenant.with(account) do
|
149
200
|
expect(project.sub_tasks).to eq [sub_task]
|
data/spec/schema.rb
CHANGED
@@ -34,6 +34,13 @@ ARGV.grep(/\w+_spec\.rb/).empty? && ActiveRecord::Schema.define(version: 1) do
|
|
34
34
|
t.column :type, :string
|
35
35
|
end
|
36
36
|
|
37
|
+
create_table :optional_sub_tasks, force: true do |t|
|
38
|
+
t.references :account, :integer
|
39
|
+
t.column :sub_task_id, :integer
|
40
|
+
t.column :name, :string
|
41
|
+
t.column :type, :string
|
42
|
+
end
|
43
|
+
|
37
44
|
create_table :countries, force: true do |t|
|
38
45
|
t.column :name, :string
|
39
46
|
end
|
@@ -106,6 +113,11 @@ ARGV.grep(/\w+_spec\.rb/).empty? && ActiveRecord::Schema.define(version: 1) do
|
|
106
113
|
t.column :domain_id, :integer
|
107
114
|
end
|
108
115
|
|
116
|
+
create_table :posts, force: true, partition_key: :account_id do |t|
|
117
|
+
t.column :account_id, :integer
|
118
|
+
t.column :name, :string
|
119
|
+
end
|
120
|
+
|
109
121
|
create_distributed_table :accounts, :id
|
110
122
|
create_distributed_table :projects, :account_id
|
111
123
|
create_distributed_table :managers, :account_id
|
@@ -121,6 +133,7 @@ ARGV.grep(/\w+_spec\.rb/).empty? && ActiveRecord::Schema.define(version: 1) do
|
|
121
133
|
create_distributed_table :allowed_places, :account_id
|
122
134
|
create_distributed_table :domains, :account_id
|
123
135
|
create_distributed_table :pages, :account_id
|
136
|
+
create_distributed_table :posts, :account_id
|
124
137
|
create_reference_table :categories
|
125
138
|
end
|
126
139
|
|
@@ -128,6 +141,7 @@ class Account < ActiveRecord::Base
|
|
128
141
|
multi_tenant :account
|
129
142
|
has_many :projects
|
130
143
|
has_one :manager, inverse_of: :account
|
144
|
+
has_many :optional_sub_tasks
|
131
145
|
end
|
132
146
|
|
133
147
|
class Project < ActiveRecord::Base
|
@@ -159,6 +173,15 @@ class SubTask < ActiveRecord::Base
|
|
159
173
|
multi_tenant :account
|
160
174
|
belongs_to :task
|
161
175
|
has_one :project, through: :task
|
176
|
+
has_many :optional_sub_tasks
|
177
|
+
end
|
178
|
+
|
179
|
+
with_belongs_to_required_by_default do
|
180
|
+
class OptionalSubTask < ActiveRecord::Base
|
181
|
+
multi_tenant :account, optional: true
|
182
|
+
belongs_to :account, optional: true
|
183
|
+
belongs_to :sub_task
|
184
|
+
end
|
162
185
|
end
|
163
186
|
|
164
187
|
class StiSubTask < SubTask
|
@@ -198,6 +221,7 @@ class Comment < ActiveRecord::Base
|
|
198
221
|
end
|
199
222
|
|
200
223
|
class Organization < ActiveRecord::Base
|
224
|
+
multi_tenant :organization
|
201
225
|
has_many :uuid_records
|
202
226
|
end
|
203
227
|
|
data/spec/spec_helper.rb
CHANGED
@@ -46,4 +46,11 @@ def uses_prepared_statements?
|
|
46
46
|
ActiveRecord::Base.connection.prepared_statements
|
47
47
|
end
|
48
48
|
|
49
|
+
def with_belongs_to_required_by_default(&block)
|
50
|
+
default_value = ActiveRecord::Base.belongs_to_required_by_default
|
51
|
+
ActiveRecord::Base.belongs_to_required_by_default = true
|
52
|
+
yield
|
53
|
+
ensure
|
54
|
+
ActiveRecord::Base.belongs_to_required_by_default = default_value
|
55
|
+
end
|
49
56
|
require 'schema'
|
metadata
CHANGED
@@ -1,43 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-multi-tenant
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.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: 2022-
|
11
|
+
date: 2022-05-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: request_store
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 1.0.5
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: 1.0.5
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: rails
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
30
16
|
requirements:
|
31
17
|
- - ">="
|
32
18
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
19
|
+
version: '5.2'
|
34
20
|
type: :runtime
|
35
21
|
prerelease: false
|
36
22
|
version_requirements: !ruby/object:Gem::Requirement
|
37
23
|
requirements:
|
38
24
|
- - ">="
|
39
25
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
26
|
+
version: '5.2'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: rspec
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -168,10 +154,12 @@ files:
|
|
168
154
|
- activerecord-multi-tenant.gemspec
|
169
155
|
- docker-compose.yml
|
170
156
|
- gemfiles/.bundle/config
|
157
|
+
- gemfiles/active_record_5.2.3.gemfile
|
171
158
|
- gemfiles/active_record_5.2.gemfile
|
172
159
|
- gemfiles/active_record_6.0.gemfile
|
173
160
|
- gemfiles/active_record_6.1.gemfile
|
174
161
|
- gemfiles/active_record_7.0.gemfile
|
162
|
+
- gemfiles/rails_5.2.3.gemfile
|
175
163
|
- gemfiles/rails_5.2.gemfile
|
176
164
|
- gemfiles/rails_6.0.gemfile
|
177
165
|
- gemfiles/rails_6.1.gemfile
|