activerecord-multi-tenant 1.1.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 +73 -0
- data/.gitignore +2 -0
- data/.rspec +1 -0
- data/Appraisals +16 -0
- data/CHANGELOG.md +28 -0
- data/activerecord-multi-tenant.gemspec +1 -2
- data/gemfiles/active_record_5.2.3.gemfile +16 -0
- data/gemfiles/active_record_6.1.gemfile +8 -0
- data/gemfiles/active_record_7.0.gemfile +8 -0
- data/gemfiles/rails_5.2.3.gemfile +16 -0
- data/gemfiles/rails_6.1.gemfile +8 -0
- data/gemfiles/rails_7.0.gemfile +8 -0
- data/lib/activerecord-multi-tenant/arel_visitors_depth_first.rb +200 -0
- data/lib/activerecord-multi-tenant/copy_from_client.rb +2 -2
- data/lib/activerecord-multi-tenant/model_extensions.rb +16 -4
- data/lib/activerecord-multi-tenant/multi_tenant.rb +22 -8
- data/lib/activerecord-multi-tenant/query_rewriter.rb +14 -9
- data/lib/activerecord-multi-tenant/sidekiq.rb +5 -1
- data/lib/activerecord-multi-tenant/version.rb +1 -1
- data/spec/activerecord-multi-tenant/model_extensions_spec.rb +109 -50
- data/spec/activerecord-multi-tenant/query_rewriter_spec.rb +17 -0
- data/spec/activerecord-multi-tenant/record_finding_spec.rb +36 -0
- data/spec/activerecord-multi-tenant/sidekiq_spec.rb +11 -0
- data/spec/schema.rb +51 -4
- data/spec/spec_helper.rb +7 -0
- metadata +14 -25
- data/.travis.yml +0 -32
- data/Gemfile.lock +0 -197
- data/gemfiles/active_record_5.2.gemfile.lock +0 -188
- data/gemfiles/active_record_6.0.gemfile.lock +0 -198
- data/gemfiles/rails_5.2.gemfile.lock +0 -188
- data/gemfiles/rails_6.0.gemfile.lock +0 -198
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
|
@@ -0,0 +1,73 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- master
|
7
|
+
pull_request:
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
build:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
strategy:
|
13
|
+
fail-fast: false
|
14
|
+
matrix:
|
15
|
+
ruby:
|
16
|
+
- '2.5'
|
17
|
+
- '2.6'
|
18
|
+
- '2.7'
|
19
|
+
- '3.0'
|
20
|
+
- '3.1'
|
21
|
+
gemfile:
|
22
|
+
- rails_5.2.3
|
23
|
+
- rails_5.2
|
24
|
+
- rails_6.0
|
25
|
+
- rails_6.1
|
26
|
+
- rails_7.0
|
27
|
+
- active_record_5.2.3
|
28
|
+
- active_record_5.2
|
29
|
+
- active_record_6.0
|
30
|
+
- active_record_6.1
|
31
|
+
- active_record_7.0
|
32
|
+
prepared_statements: [true, false]
|
33
|
+
exclude:
|
34
|
+
# activesupport-7.0.0 requires ruby version >= 2.7.0
|
35
|
+
- ruby: '2.5'
|
36
|
+
gemfile: 'rails_7.0'
|
37
|
+
- ruby: '2.5'
|
38
|
+
gemfile: 'active_record_7.0'
|
39
|
+
- ruby: '2.6'
|
40
|
+
gemfile: 'rails_7.0'
|
41
|
+
- ruby: '2.6'
|
42
|
+
gemfile: 'active_record_7.0'
|
43
|
+
# ruby >3 and activesupport 5.2 are not compatible
|
44
|
+
- ruby: '3.0'
|
45
|
+
gemfile: 'rails_5.2'
|
46
|
+
- ruby: '3.0'
|
47
|
+
gemfile: 'active_record_5.2'
|
48
|
+
- ruby: '3.1'
|
49
|
+
gemfile: 'rails_5.2'
|
50
|
+
- ruby: '3.1'
|
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'
|
60
|
+
name: Ruby ${{ matrix.ruby }} / ${{ matrix.gemfile }} ${{ (matrix.prepared_statements && 'w/ prepared statements') || '' }}
|
61
|
+
env:
|
62
|
+
BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile
|
63
|
+
PREPARED_STATEMENTS: ${{ matrix.prepared_statements && '1' }}
|
64
|
+
steps:
|
65
|
+
- uses: actions/checkout@v2
|
66
|
+
- run: |
|
67
|
+
docker-compose up -d
|
68
|
+
- uses: ruby/setup-ruby@v1
|
69
|
+
with:
|
70
|
+
ruby-version: ${{ matrix.ruby }}
|
71
|
+
bundler-cache: true
|
72
|
+
- run: |
|
73
|
+
bundle exec rake spec
|
data/.gitignore
CHANGED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--force-color
|
data/Appraisals
CHANGED
@@ -14,6 +14,14 @@ appraise 'rails-6.0' do
|
|
14
14
|
gem 'rails', '~> 6.0.3'
|
15
15
|
end
|
16
16
|
|
17
|
+
appraise 'rails-6.1' do
|
18
|
+
gem 'rails', '~> 6.1.0'
|
19
|
+
end
|
20
|
+
|
21
|
+
appraise 'rails-7.0' do
|
22
|
+
gem 'rails', '~> 7.0.0'
|
23
|
+
end
|
24
|
+
|
17
25
|
appraise 'active-record-5.2' do
|
18
26
|
gem 'activerecord', '~> 5.2.0'
|
19
27
|
gem 'i18n', '~> 0.9.5'
|
@@ -29,3 +37,11 @@ end
|
|
29
37
|
appraise 'active-record-6.0' do
|
30
38
|
gem 'activerecord', '~> 6.0.3'
|
31
39
|
end
|
40
|
+
|
41
|
+
appraise 'active-record-6.1' do
|
42
|
+
gem 'activerecord', '~> 6.1.0'
|
43
|
+
end
|
44
|
+
|
45
|
+
appraise 'active-record-7.0' do
|
46
|
+
gem 'activerecord', '~> 7.0.0'
|
47
|
+
end
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,33 @@
|
|
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
|
+
|
12
|
+
## 1.2.0 2022-03-29
|
13
|
+
|
14
|
+
* Test Rails 7 & Ruby 3
|
15
|
+
* Fix regression in 1.1.1 involving deleted tenants [#123](https://github.com/citusdata/activerecord-multi-tenant/pull/123)
|
16
|
+
* Fix incorrect SQL generated when joining two models and one has a default scope [#132](https://github.com/citusdata/activerecord-multi-tenant/pull/132)
|
17
|
+
* Update for Rails 5+ removal of type_cast_for_database [#135](https://github.com/citusdata/activerecord-multi-tenant/pull/135)
|
18
|
+
|
19
|
+
|
20
|
+
## 1.1.1 2021-01-15
|
21
|
+
|
22
|
+
* Add support for Rails 6.1 [#108](https://github.com/citusdata/activerecord-multi-tenant/pull/108)
|
23
|
+
* Fix statement cache for has_many through relations [#103](https://github.com/citusdata/activerecord-multi-tenant/pull/103)
|
24
|
+
|
25
|
+
|
26
|
+
## 1.1.0 2020-08-06
|
27
|
+
|
28
|
+
* See commits for changes:
|
29
|
+
https://github.com/citusdata/activerecord-multi-tenant/commits/v1.1.0
|
30
|
+
|
3
31
|
|
4
32
|
## 1.0.4 2019-10-30
|
5
33
|
|
@@ -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: "../"
|
@@ -0,0 +1,200 @@
|
|
1
|
+
module MultiTenant
|
2
|
+
class ArelVisitorsDepthFirst < Arel::Visitors::Visitor
|
3
|
+
def initialize(block = nil)
|
4
|
+
@block = block || Proc.new
|
5
|
+
super()
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def visit(o, _ = nil)
|
11
|
+
super
|
12
|
+
@block.call o
|
13
|
+
end
|
14
|
+
|
15
|
+
def unary(o)
|
16
|
+
visit o.expr
|
17
|
+
end
|
18
|
+
alias :visit_Arel_Nodes_Else :unary
|
19
|
+
alias :visit_Arel_Nodes_Group :unary
|
20
|
+
alias :visit_Arel_Nodes_Cube :unary
|
21
|
+
alias :visit_Arel_Nodes_RollUp :unary
|
22
|
+
alias :visit_Arel_Nodes_GroupingSet :unary
|
23
|
+
alias :visit_Arel_Nodes_GroupingElement :unary
|
24
|
+
alias :visit_Arel_Nodes_Grouping :unary
|
25
|
+
alias :visit_Arel_Nodes_Having :unary
|
26
|
+
alias :visit_Arel_Nodes_Lateral :unary
|
27
|
+
alias :visit_Arel_Nodes_Limit :unary
|
28
|
+
alias :visit_Arel_Nodes_Not :unary
|
29
|
+
alias :visit_Arel_Nodes_Offset :unary
|
30
|
+
alias :visit_Arel_Nodes_On :unary
|
31
|
+
alias :visit_Arel_Nodes_Ordering :unary
|
32
|
+
alias :visit_Arel_Nodes_Ascending :unary
|
33
|
+
alias :visit_Arel_Nodes_Descending :unary
|
34
|
+
alias :visit_Arel_Nodes_UnqualifiedColumn :unary
|
35
|
+
alias :visit_Arel_Nodes_OptimizerHints :unary
|
36
|
+
alias :visit_Arel_Nodes_ValuesList :unary
|
37
|
+
|
38
|
+
def function(o)
|
39
|
+
visit o.expressions
|
40
|
+
visit o.alias
|
41
|
+
visit o.distinct
|
42
|
+
end
|
43
|
+
alias :visit_Arel_Nodes_Avg :function
|
44
|
+
alias :visit_Arel_Nodes_Exists :function
|
45
|
+
alias :visit_Arel_Nodes_Max :function
|
46
|
+
alias :visit_Arel_Nodes_Min :function
|
47
|
+
alias :visit_Arel_Nodes_Sum :function
|
48
|
+
|
49
|
+
def visit_Arel_Nodes_NamedFunction(o)
|
50
|
+
visit o.name
|
51
|
+
visit o.expressions
|
52
|
+
visit o.distinct
|
53
|
+
visit o.alias
|
54
|
+
end
|
55
|
+
|
56
|
+
def visit_Arel_Nodes_Count(o)
|
57
|
+
visit o.expressions
|
58
|
+
visit o.alias
|
59
|
+
visit o.distinct
|
60
|
+
end
|
61
|
+
|
62
|
+
def visit_Arel_Nodes_Case(o)
|
63
|
+
visit o.case
|
64
|
+
visit o.conditions
|
65
|
+
visit o.default
|
66
|
+
end
|
67
|
+
|
68
|
+
def nary(o)
|
69
|
+
o.children.each { |child| visit child }
|
70
|
+
end
|
71
|
+
alias :visit_Arel_Nodes_And :nary
|
72
|
+
|
73
|
+
def binary(o)
|
74
|
+
visit o.left
|
75
|
+
visit o.right
|
76
|
+
end
|
77
|
+
alias :visit_Arel_Nodes_As :binary
|
78
|
+
alias :visit_Arel_Nodes_Assignment :binary
|
79
|
+
alias :visit_Arel_Nodes_Between :binary
|
80
|
+
alias :visit_Arel_Nodes_Concat :binary
|
81
|
+
alias :visit_Arel_Nodes_DeleteStatement :binary
|
82
|
+
alias :visit_Arel_Nodes_DoesNotMatch :binary
|
83
|
+
alias :visit_Arel_Nodes_Equality :binary
|
84
|
+
alias :visit_Arel_Nodes_FullOuterJoin :binary
|
85
|
+
alias :visit_Arel_Nodes_GreaterThan :binary
|
86
|
+
alias :visit_Arel_Nodes_GreaterThanOrEqual :binary
|
87
|
+
alias :visit_Arel_Nodes_In :binary
|
88
|
+
alias :visit_Arel_Nodes_InfixOperation :binary
|
89
|
+
alias :visit_Arel_Nodes_JoinSource :binary
|
90
|
+
alias :visit_Arel_Nodes_InnerJoin :binary
|
91
|
+
alias :visit_Arel_Nodes_LessThan :binary
|
92
|
+
alias :visit_Arel_Nodes_LessThanOrEqual :binary
|
93
|
+
alias :visit_Arel_Nodes_Matches :binary
|
94
|
+
alias :visit_Arel_Nodes_NotEqual :binary
|
95
|
+
alias :visit_Arel_Nodes_NotIn :binary
|
96
|
+
alias :visit_Arel_Nodes_NotRegexp :binary
|
97
|
+
alias :visit_Arel_Nodes_IsNotDistinctFrom :binary
|
98
|
+
alias :visit_Arel_Nodes_IsDistinctFrom :binary
|
99
|
+
alias :visit_Arel_Nodes_Or :binary
|
100
|
+
alias :visit_Arel_Nodes_OuterJoin :binary
|
101
|
+
alias :visit_Arel_Nodes_Regexp :binary
|
102
|
+
alias :visit_Arel_Nodes_RightOuterJoin :binary
|
103
|
+
alias :visit_Arel_Nodes_TableAlias :binary
|
104
|
+
alias :visit_Arel_Nodes_When :binary
|
105
|
+
|
106
|
+
def visit_Arel_Nodes_StringJoin(o)
|
107
|
+
visit o.left
|
108
|
+
end
|
109
|
+
|
110
|
+
def visit_Arel_Attribute(o)
|
111
|
+
visit o.relation
|
112
|
+
visit o.name
|
113
|
+
end
|
114
|
+
alias :visit_Arel_Attributes_Integer :visit_Arel_Attribute
|
115
|
+
alias :visit_Arel_Attributes_Float :visit_Arel_Attribute
|
116
|
+
alias :visit_Arel_Attributes_String :visit_Arel_Attribute
|
117
|
+
alias :visit_Arel_Attributes_Time :visit_Arel_Attribute
|
118
|
+
alias :visit_Arel_Attributes_Boolean :visit_Arel_Attribute
|
119
|
+
alias :visit_Arel_Attributes_Attribute :visit_Arel_Attribute
|
120
|
+
alias :visit_Arel_Attributes_Decimal :visit_Arel_Attribute
|
121
|
+
|
122
|
+
def visit_Arel_Table(o)
|
123
|
+
visit o.name
|
124
|
+
end
|
125
|
+
|
126
|
+
def terminal(o)
|
127
|
+
end
|
128
|
+
alias :visit_ActiveSupport_Multibyte_Chars :terminal
|
129
|
+
alias :visit_ActiveSupport_StringInquirer :terminal
|
130
|
+
alias :visit_Arel_Nodes_Lock :terminal
|
131
|
+
alias :visit_Arel_Nodes_Node :terminal
|
132
|
+
alias :visit_Arel_Nodes_SqlLiteral :terminal
|
133
|
+
alias :visit_Arel_Nodes_BindParam :terminal
|
134
|
+
alias :visit_Arel_Nodes_Window :terminal
|
135
|
+
alias :visit_Arel_Nodes_True :terminal
|
136
|
+
alias :visit_Arel_Nodes_False :terminal
|
137
|
+
alias :visit_BigDecimal :terminal
|
138
|
+
alias :visit_Class :terminal
|
139
|
+
alias :visit_Date :terminal
|
140
|
+
alias :visit_DateTime :terminal
|
141
|
+
alias :visit_FalseClass :terminal
|
142
|
+
alias :visit_Float :terminal
|
143
|
+
alias :visit_Integer :terminal
|
144
|
+
alias :visit_NilClass :terminal
|
145
|
+
alias :visit_String :terminal
|
146
|
+
alias :visit_Symbol :terminal
|
147
|
+
alias :visit_Time :terminal
|
148
|
+
alias :visit_TrueClass :terminal
|
149
|
+
|
150
|
+
def visit_Arel_Nodes_InsertStatement(o)
|
151
|
+
visit o.relation
|
152
|
+
visit o.columns
|
153
|
+
visit o.values
|
154
|
+
end
|
155
|
+
|
156
|
+
def visit_Arel_Nodes_SelectCore(o)
|
157
|
+
visit o.projections
|
158
|
+
visit o.source
|
159
|
+
visit o.wheres
|
160
|
+
visit o.groups
|
161
|
+
visit o.windows
|
162
|
+
visit o.havings
|
163
|
+
end
|
164
|
+
|
165
|
+
def visit_Arel_Nodes_SelectStatement(o)
|
166
|
+
visit o.cores
|
167
|
+
visit o.orders
|
168
|
+
visit o.limit
|
169
|
+
visit o.lock
|
170
|
+
visit o.offset
|
171
|
+
end
|
172
|
+
|
173
|
+
def visit_Arel_Nodes_UpdateStatement(o)
|
174
|
+
visit o.relation
|
175
|
+
visit o.values
|
176
|
+
visit o.wheres
|
177
|
+
visit o.orders
|
178
|
+
visit o.limit
|
179
|
+
end
|
180
|
+
|
181
|
+
def visit_Arel_Nodes_Comment(o)
|
182
|
+
visit o.values
|
183
|
+
end
|
184
|
+
|
185
|
+
def visit_Array(o)
|
186
|
+
o.each { |i| visit i }
|
187
|
+
end
|
188
|
+
alias :visit_Set :visit_Array
|
189
|
+
|
190
|
+
def visit_Hash(o)
|
191
|
+
o.each { |k, v| visit(k); visit(v) }
|
192
|
+
end
|
193
|
+
|
194
|
+
DISPATCH = dispatch_cache
|
195
|
+
|
196
|
+
def get_dispatch_cache
|
197
|
+
DISPATCH
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -9,7 +9,7 @@ module MultiTenant
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def <<(row)
|
12
|
-
row = row.map.with_index { |val, idx| @column_types[idx].
|
12
|
+
row = row.map.with_index { |val, idx| @column_types[idx].serialize(val) }
|
13
13
|
@conn.put_copy_data(row)
|
14
14
|
@count += 1
|
15
15
|
end
|
@@ -18,7 +18,7 @@ module MultiTenant
|
|
18
18
|
module CopyFromClient
|
19
19
|
def copy_from_client(columns, &block)
|
20
20
|
conn = connection.raw_connection
|
21
|
-
column_types = columns.map { |c|
|
21
|
+
column_types = columns.map { |c| type_for_attribute(c.to_s) }
|
22
22
|
helper = MultiTenant::CopyFromClientHelper.new(conn, column_types)
|
23
23
|
conn.copy_data %{COPY #{quoted_table_name}("#{columns.join('","')}") FROM STDIN}, PG::TextEncoder::CopyRow.new do
|
24
24
|
block.call helper
|
@@ -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
|
@@ -129,6 +135,12 @@ class ActiveRecord::Associations::Association
|
|
129
135
|
alias skip_statement_cache_orig skip_statement_cache?
|
130
136
|
def skip_statement_cache?(*scope)
|
131
137
|
return true if klass.respond_to?(:scoped_by_tenant?) && klass.scoped_by_tenant?
|
138
|
+
|
139
|
+
if reflection.through_reflection
|
140
|
+
through_klass = reflection.through_reflection.klass
|
141
|
+
return true if through_klass.respond_to?(:scoped_by_tenant?) && through_klass.scoped_by_tenant?
|
142
|
+
end
|
143
|
+
|
132
144
|
skip_statement_cache_orig(*scope)
|
133
145
|
end
|
134
146
|
end
|
@@ -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
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'active_record'
|
2
|
+
require_relative "./arel_visitors_depth_first.rb" unless Arel::Visitors.const_defined?(:DepthFirst)
|
2
3
|
|
3
4
|
module MultiTenant
|
4
5
|
class Table
|
@@ -31,6 +32,7 @@ module MultiTenant
|
|
31
32
|
@arel_node = arel_node
|
32
33
|
@known_relations = []
|
33
34
|
@handled_relations = []
|
35
|
+
@discovering = false
|
34
36
|
end
|
35
37
|
|
36
38
|
def discover_relations
|
@@ -54,7 +56,7 @@ module MultiTenant
|
|
54
56
|
end
|
55
57
|
end
|
56
58
|
|
57
|
-
class ArelTenantVisitor < Arel::Visitors::DepthFirst
|
59
|
+
class ArelTenantVisitor < Arel::Visitors.const_defined?(:DepthFirst) ? Arel::Visitors::DepthFirst : ::MultiTenant::ArelVisitorsDepthFirst
|
58
60
|
def initialize(arel)
|
59
61
|
super(Proc.new {})
|
60
62
|
@statement_node_id = nil
|
@@ -120,6 +122,8 @@ module MultiTenant
|
|
120
122
|
alias :visit_Arel_Nodes_FullOuterJoin :visit_Arel_Nodes_OuterJoin
|
121
123
|
alias :visit_Arel_Nodes_RightOuterJoin :visit_Arel_Nodes_OuterJoin
|
122
124
|
|
125
|
+
alias :visit_ActiveModel_Attribute :terminal
|
126
|
+
|
123
127
|
private
|
124
128
|
|
125
129
|
def tenant_relation?(table_name)
|
@@ -272,7 +276,11 @@ module ActiveRecord
|
|
272
276
|
if node.wheres.empty?
|
273
277
|
node.wheres = [enforcement_clause]
|
274
278
|
else
|
275
|
-
node.wheres[0]
|
279
|
+
if node.wheres[0].is_a?(Arel::Nodes::And)
|
280
|
+
node.wheres[0].children << enforcement_clause
|
281
|
+
else
|
282
|
+
node.wheres[0] = enforcement_clause.and(node.wheres[0])
|
283
|
+
end
|
276
284
|
end
|
277
285
|
else
|
278
286
|
raise "UnknownContext"
|
@@ -287,12 +295,9 @@ module ActiveRecord
|
|
287
295
|
end
|
288
296
|
|
289
297
|
node_list.select{ |n| n.is_a? Arel::Nodes::Join }.each do |node_join|
|
290
|
-
if
|
291
|
-
(ActiveRecord::VERSION::MAJOR == 5 &&
|
292
|
-
!node_join.right.expr.right.is_a?(Arel::Attributes::Attribute)))
|
298
|
+
if !node_join.right
|
293
299
|
next
|
294
300
|
end
|
295
|
-
|
296
301
|
relation_right, relation_left = relations_from_node_join(node_join)
|
297
302
|
|
298
303
|
next unless relation_right && relation_left
|
@@ -314,13 +319,13 @@ module ActiveRecord
|
|
314
319
|
|
315
320
|
private
|
316
321
|
def relations_from_node_join(node_join)
|
317
|
-
if
|
322
|
+
if node_join.right.expr.is_a?(Arel::Nodes::Equality)
|
318
323
|
return node_join.right.expr.right.relation, node_join.right.expr.left.relation
|
319
324
|
end
|
320
325
|
|
321
|
-
children = node_join.right.expr.children
|
326
|
+
children = [node_join.right.expr.children].flatten
|
322
327
|
|
323
|
-
tenant_applied = children.any?(MultiTenant::TenantEnforcementClause) ||
|
328
|
+
tenant_applied = children.any?{|c| c.is_a?(MultiTenant::TenantEnforcementClause) || c.is_a?(MultiTenant::TenantJoinEnforcementClause)}
|
324
329
|
if tenant_applied || children.empty?
|
325
330
|
return nil, nil
|
326
331
|
end
|
@@ -18,7 +18,11 @@ module Sidekiq::Middleware::MultiTenant
|
|
18
18
|
class Server
|
19
19
|
def call(worker_class, msg, queue)
|
20
20
|
if msg.has_key?('multi_tenant')
|
21
|
-
tenant =
|
21
|
+
tenant = begin
|
22
|
+
msg['multi_tenant']['class'].constantize.find(msg['multi_tenant']['id'])
|
23
|
+
rescue ActiveRecord::RecordNotFound
|
24
|
+
msg['multi_tenant']['id']
|
25
|
+
end
|
22
26
|
MultiTenant.with(tenant) do
|
23
27
|
yield
|
24
28
|
end
|