activerecord-multi-tenant 0.4.1 → 0.5.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/.travis.yml +5 -2
- data/Appraisals +6 -2
- data/CHANGELOG.md +9 -0
- data/Gemfile.lock +4 -4
- data/README.md +20 -1
- data/gemfiles/{active_record_5.0.gemfile → active_record_5.1.gemfile} +1 -1
- data/gemfiles/{active_record_5.0.gemfile.lock → active_record_5.1.gemfile.lock} +61 -61
- data/gemfiles/rails_3.2.gemfile.lock +1 -1
- data/gemfiles/rails_4.0.gemfile.lock +1 -1
- data/gemfiles/rails_4.1.gemfile.lock +1 -1
- data/gemfiles/rails_4.2.gemfile.lock +1 -1
- data/gemfiles/rails_5.0.gemfile.lock +1 -1
- data/gemfiles/rails_5.1.gemfile +8 -0
- data/gemfiles/rails_5.1.gemfile.lock +154 -0
- data/lib/activerecord-multi-tenant.rb +3 -1
- data/lib/activerecord-multi-tenant/fast_truncate.rb +32 -0
- data/lib/activerecord-multi-tenant/model_extensions.rb +8 -15
- data/lib/activerecord-multi-tenant/multi_tenant.rb +17 -0
- data/lib/activerecord-multi-tenant/query_monitor.rb +18 -0
- data/lib/activerecord-multi-tenant/query_rewriter.rb +47 -0
- data/lib/activerecord-multi-tenant/sidekiq.rb +59 -0
- data/lib/activerecord-multi-tenant/version.rb +1 -1
- data/spec/spec_helper.rb +1 -1
- metadata +11 -6
- data/lib/activerecord-multi-tenant/default_scope.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fc1deedd61a8e692c880e666ccd16542079b3b5a
|
4
|
+
data.tar.gz: e71ddea3d21d23c511e0d85d947b974f5ff40591
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c7354a9d0c30caa2264d35739edf4f181945bf035326c13354824fee2eb3d2717804236b412d5f5fa9352a498a62aba7c846b12fcd4b0566e907b0bbca3276a5
|
7
|
+
data.tar.gz: 6d9aaf1c06cc24daf27423eec9fe1c032070f109c06d69c0c915c68ab549da89e37057388a65aed42e400a13abbc206285fa744216fbbed25ee35f2f2a106a05
|
data/.travis.yml
CHANGED
@@ -14,7 +14,8 @@ gemfile:
|
|
14
14
|
- gemfiles/rails_4.1.gemfile
|
15
15
|
- gemfiles/rails_4.2.gemfile
|
16
16
|
- gemfiles/rails_5.0.gemfile
|
17
|
-
- gemfiles/
|
17
|
+
- gemfiles/rails_5.1.gemfile
|
18
|
+
- gemfiles/active_record_5.1.gemfile
|
18
19
|
|
19
20
|
matrix:
|
20
21
|
fast_finish: true
|
@@ -22,7 +23,9 @@ matrix:
|
|
22
23
|
- rvm: 2.1
|
23
24
|
gemfile: gemfiles/rails_5.0.gemfile
|
24
25
|
- rvm: 2.1
|
25
|
-
gemfile: gemfiles/
|
26
|
+
gemfile: gemfiles/rails_5.1.gemfile
|
27
|
+
- rvm: 2.1
|
28
|
+
gemfile: gemfiles/active_record_5.1.gemfile
|
26
29
|
- rvm: 2.4.0
|
27
30
|
gemfile: gemfiles/rails_3.2.gemfile
|
28
31
|
- rvm: 2.4.0
|
data/Appraisals
CHANGED
@@ -19,6 +19,10 @@ appraise 'rails-5.0' do
|
|
19
19
|
gem 'rails', '5.0.1'
|
20
20
|
end
|
21
21
|
|
22
|
-
appraise '
|
23
|
-
gem '
|
22
|
+
appraise 'rails-5.1' do
|
23
|
+
gem 'rails', '5.1.0'
|
24
|
+
end
|
25
|
+
|
26
|
+
appraise 'active-record-5.1' do
|
27
|
+
gem 'activerecord', '5.1.0'
|
24
28
|
end
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.5.0 2017-05-08
|
4
|
+
|
5
|
+
* Write-only mode that enables step-by-step migrations
|
6
|
+
* Add tenant_id to queries using rewrite instead of default_scope/unscoped [Ben Olive](https://github.com/sionide21)
|
7
|
+
* Query monitor that warns you about missing tenant_id
|
8
|
+
* Helper for fast truncation
|
9
|
+
* Sidekiq middleware
|
10
|
+
|
11
|
+
|
3
12
|
## 0.4.1 2017-03-23
|
4
13
|
|
5
14
|
* Allow use outside of Rails, e.g. when using Sinatra with ActiveRecord [@nathanstitt](https://github.com/nathanstitt) [#5](https://github.com/citusdata/activerecord-multi-tenant/pull/5)
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
activerecord-multi-tenant (0.
|
4
|
+
activerecord-multi-tenant (0.5.0)
|
5
5
|
rails (>= 3.1)
|
6
6
|
request_store (>= 1.0.5)
|
7
7
|
|
@@ -55,12 +55,12 @@ GEM
|
|
55
55
|
database_cleaner (1.3.0)
|
56
56
|
diff-lcs (1.2.5)
|
57
57
|
erubis (2.7.0)
|
58
|
-
globalid (0.
|
59
|
-
activesupport (>= 4.
|
58
|
+
globalid (0.4.0)
|
59
|
+
activesupport (>= 4.2.0)
|
60
60
|
i18n (0.7.0)
|
61
61
|
loofah (2.0.3)
|
62
62
|
nokogiri (>= 1.5.9)
|
63
|
-
mail (2.6.
|
63
|
+
mail (2.6.5)
|
64
64
|
mime-types (>= 1.16, < 4)
|
65
65
|
method_source (0.8.2)
|
66
66
|
mime-types (3.1)
|
data/README.md
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
# activerecord-multi-tenant [ ](https://rubygems.org/gems/activerecord-multi-tenant) [ ](https://rubygems.org/gems/activerecord-multi-tenant)
|
2
2
|
|
3
|
-
|
3
|
+
Introduction Post: https://www.citusdata.com/blog/2017/01/05/easily-scale-out-multi-tenant-apps/
|
4
|
+
|
5
|
+
ActiveRecord/Rails integration for multi-tenant databases, in particular the open-source [Citus](https://github.com/citusdata/citus) extension for PostgreSQL.
|
6
|
+
|
7
|
+
Enables easy scale-out by adding the tenant context to your queries, enabling the database (e.g. Citus) to efficiently route queries to the right database node.
|
4
8
|
|
5
9
|
## Installation
|
6
10
|
|
@@ -65,6 +69,21 @@ class ApplicationController < ActionController::Base
|
|
65
69
|
end
|
66
70
|
```
|
67
71
|
|
72
|
+
## Rolling out activerecord-multi-tenant for your application (write-only mode)
|
73
|
+
|
74
|
+
The library relies on tenant_id to be present and NOT NULL for all rows. However,
|
75
|
+
its often useful to have the library set the tenant_id for new records, and then backfilling
|
76
|
+
tenant_id for existing records as a background task.
|
77
|
+
|
78
|
+
To support this, there is a write-only mode, in which tenant_id is not included in queries,
|
79
|
+
but only set for new records. Include the following in an initializer to enable it:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
MultiTenant.enable_write_only_mode
|
83
|
+
```
|
84
|
+
|
85
|
+
Once you are ready to enforce tenancy, make your tenant_id column NOT NULL and simply remove that line.
|
86
|
+
|
68
87
|
## Frequently Asked Questions
|
69
88
|
|
70
89
|
* **What if I have a table that doesn't relate to my tenant?** (e.g. templates that are the same in every account)
|
@@ -1,66 +1,66 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../
|
3
3
|
specs:
|
4
|
-
activerecord-multi-tenant (0.
|
4
|
+
activerecord-multi-tenant (0.5.0)
|
5
5
|
rails (>= 3.1)
|
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
|
12
|
-
actionpack (= 5.0
|
13
|
-
nio4r (
|
11
|
+
actioncable (5.1.0)
|
12
|
+
actionpack (= 5.1.0)
|
13
|
+
nio4r (~> 2.0)
|
14
14
|
websocket-driver (~> 0.6.1)
|
15
|
-
actionmailer (5.0
|
16
|
-
actionpack (= 5.0
|
17
|
-
actionview (= 5.0
|
18
|
-
activejob (= 5.0
|
15
|
+
actionmailer (5.1.0)
|
16
|
+
actionpack (= 5.1.0)
|
17
|
+
actionview (= 5.1.0)
|
18
|
+
activejob (= 5.1.0)
|
19
19
|
mail (~> 2.5, >= 2.5.4)
|
20
20
|
rails-dom-testing (~> 2.0)
|
21
|
-
actionpack (5.0
|
22
|
-
actionview (= 5.0
|
23
|
-
activesupport (= 5.0
|
21
|
+
actionpack (5.1.0)
|
22
|
+
actionview (= 5.1.0)
|
23
|
+
activesupport (= 5.1.0)
|
24
24
|
rack (~> 2.0)
|
25
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
|
29
|
-
activesupport (= 5.0
|
28
|
+
actionview (5.1.0)
|
29
|
+
activesupport (= 5.1.0)
|
30
30
|
builder (~> 3.1)
|
31
|
-
|
31
|
+
erubi (~> 1.4)
|
32
32
|
rails-dom-testing (~> 2.0)
|
33
33
|
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
34
|
-
activejob (5.0
|
35
|
-
activesupport (= 5.0
|
34
|
+
activejob (5.1.0)
|
35
|
+
activesupport (= 5.1.0)
|
36
36
|
globalid (>= 0.3.6)
|
37
|
-
activemodel (5.0
|
38
|
-
activesupport (= 5.0
|
39
|
-
activerecord (5.0
|
40
|
-
activemodel (= 5.0
|
41
|
-
activesupport (= 5.0
|
42
|
-
arel (~>
|
43
|
-
activesupport (5.0
|
37
|
+
activemodel (5.1.0)
|
38
|
+
activesupport (= 5.1.0)
|
39
|
+
activerecord (5.1.0)
|
40
|
+
activemodel (= 5.1.0)
|
41
|
+
activesupport (= 5.1.0)
|
42
|
+
arel (~> 8.0)
|
43
|
+
activesupport (5.1.0)
|
44
44
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
45
45
|
i18n (~> 0.7)
|
46
46
|
minitest (~> 5.1)
|
47
47
|
tzinfo (~> 1.1)
|
48
|
-
appraisal (2.
|
48
|
+
appraisal (2.2.0)
|
49
49
|
bundler
|
50
50
|
rake
|
51
51
|
thor (>= 0.14.0)
|
52
|
-
arel (
|
52
|
+
arel (8.0.0)
|
53
53
|
builder (3.2.3)
|
54
54
|
concurrent-ruby (1.0.5)
|
55
55
|
database_cleaner (1.3.0)
|
56
56
|
diff-lcs (1.3)
|
57
|
-
|
58
|
-
globalid (0.
|
59
|
-
activesupport (>= 4.
|
57
|
+
erubi (1.6.0)
|
58
|
+
globalid (0.4.0)
|
59
|
+
activesupport (>= 4.2.0)
|
60
60
|
i18n (0.8.1)
|
61
61
|
loofah (2.0.3)
|
62
62
|
nokogiri (>= 1.5.9)
|
63
|
-
mail (2.6.
|
63
|
+
mail (2.6.5)
|
64
64
|
mime-types (>= 1.16, < 4)
|
65
65
|
method_source (0.8.2)
|
66
66
|
mime-types (3.1)
|
@@ -75,52 +75,52 @@ GEM
|
|
75
75
|
rack (2.0.1)
|
76
76
|
rack-test (0.6.3)
|
77
77
|
rack (>= 1.0)
|
78
|
-
rails (5.0
|
79
|
-
actioncable (= 5.0
|
80
|
-
actionmailer (= 5.0
|
81
|
-
actionpack (= 5.0
|
82
|
-
actionview (= 5.0
|
83
|
-
activejob (= 5.0
|
84
|
-
activemodel (= 5.0
|
85
|
-
activerecord (= 5.0
|
86
|
-
activesupport (= 5.0
|
78
|
+
rails (5.1.0)
|
79
|
+
actioncable (= 5.1.0)
|
80
|
+
actionmailer (= 5.1.0)
|
81
|
+
actionpack (= 5.1.0)
|
82
|
+
actionview (= 5.1.0)
|
83
|
+
activejob (= 5.1.0)
|
84
|
+
activemodel (= 5.1.0)
|
85
|
+
activerecord (= 5.1.0)
|
86
|
+
activesupport (= 5.1.0)
|
87
87
|
bundler (>= 1.3.0, < 2.0)
|
88
|
-
railties (= 5.0
|
88
|
+
railties (= 5.1.0)
|
89
89
|
sprockets-rails (>= 2.0.0)
|
90
90
|
rails-dom-testing (2.0.2)
|
91
91
|
activesupport (>= 4.2.0, < 6.0)
|
92
92
|
nokogiri (~> 1.6)
|
93
93
|
rails-html-sanitizer (1.0.3)
|
94
94
|
loofah (~> 2.0)
|
95
|
-
railties (5.0
|
96
|
-
actionpack (= 5.0
|
97
|
-
activesupport (= 5.0
|
95
|
+
railties (5.1.0)
|
96
|
+
actionpack (= 5.1.0)
|
97
|
+
activesupport (= 5.1.0)
|
98
98
|
method_source
|
99
99
|
rake (>= 0.8.7)
|
100
100
|
thor (>= 0.18.1, < 2.0)
|
101
101
|
rake (12.0.0)
|
102
102
|
request_store (1.3.2)
|
103
|
-
rspec (3.
|
104
|
-
rspec-core (~> 3.
|
105
|
-
rspec-expectations (~> 3.
|
106
|
-
rspec-mocks (~> 3.
|
107
|
-
rspec-core (3.
|
108
|
-
rspec-support (~> 3.
|
109
|
-
rspec-expectations (3.
|
103
|
+
rspec (3.6.0)
|
104
|
+
rspec-core (~> 3.6.0)
|
105
|
+
rspec-expectations (~> 3.6.0)
|
106
|
+
rspec-mocks (~> 3.6.0)
|
107
|
+
rspec-core (3.6.0)
|
108
|
+
rspec-support (~> 3.6.0)
|
109
|
+
rspec-expectations (3.6.0)
|
110
110
|
diff-lcs (>= 1.2.0, < 2.0)
|
111
|
-
rspec-support (~> 3.
|
112
|
-
rspec-mocks (3.
|
111
|
+
rspec-support (~> 3.6.0)
|
112
|
+
rspec-mocks (3.6.0)
|
113
113
|
diff-lcs (>= 1.2.0, < 2.0)
|
114
|
-
rspec-support (~> 3.
|
115
|
-
rspec-rails (3.
|
114
|
+
rspec-support (~> 3.6.0)
|
115
|
+
rspec-rails (3.6.0)
|
116
116
|
actionpack (>= 3.0)
|
117
117
|
activesupport (>= 3.0)
|
118
118
|
railties (>= 3.0)
|
119
|
-
rspec-core (~> 3.
|
120
|
-
rspec-expectations (~> 3.
|
121
|
-
rspec-mocks (~> 3.
|
122
|
-
rspec-support (~> 3.
|
123
|
-
rspec-support (3.
|
119
|
+
rspec-core (~> 3.6.0)
|
120
|
+
rspec-expectations (~> 3.6.0)
|
121
|
+
rspec-mocks (~> 3.6.0)
|
122
|
+
rspec-support (~> 3.6.0)
|
123
|
+
rspec-support (3.6.0)
|
124
124
|
sprockets (3.7.1)
|
125
125
|
concurrent-ruby (~> 1.0)
|
126
126
|
rack (> 1, < 3)
|
@@ -130,7 +130,7 @@ GEM
|
|
130
130
|
sprockets (>= 3.0.0)
|
131
131
|
thor (0.19.4)
|
132
132
|
thread_safe (0.3.6)
|
133
|
-
tzinfo (1.2.
|
133
|
+
tzinfo (1.2.3)
|
134
134
|
thread_safe (~> 0.1)
|
135
135
|
websocket-driver (0.6.5)
|
136
136
|
websocket-extensions (>= 0.1.0)
|
@@ -140,7 +140,7 @@ PLATFORMS
|
|
140
140
|
ruby
|
141
141
|
|
142
142
|
DEPENDENCIES
|
143
|
-
activerecord (= 5.0
|
143
|
+
activerecord (= 5.1.0)
|
144
144
|
activerecord-multi-tenant!
|
145
145
|
appraisal
|
146
146
|
database_cleaner (~> 1.3.0)
|
@@ -151,4 +151,4 @@ DEPENDENCIES
|
|
151
151
|
thor
|
152
152
|
|
153
153
|
BUNDLED WITH
|
154
|
-
1.
|
154
|
+
1.12.5
|
@@ -0,0 +1,154 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ../
|
3
|
+
specs:
|
4
|
+
activerecord-multi-tenant (0.5.0)
|
5
|
+
rails (>= 3.1)
|
6
|
+
request_store (>= 1.0.5)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
actioncable (5.1.0)
|
12
|
+
actionpack (= 5.1.0)
|
13
|
+
nio4r (~> 2.0)
|
14
|
+
websocket-driver (~> 0.6.1)
|
15
|
+
actionmailer (5.1.0)
|
16
|
+
actionpack (= 5.1.0)
|
17
|
+
actionview (= 5.1.0)
|
18
|
+
activejob (= 5.1.0)
|
19
|
+
mail (~> 2.5, >= 2.5.4)
|
20
|
+
rails-dom-testing (~> 2.0)
|
21
|
+
actionpack (5.1.0)
|
22
|
+
actionview (= 5.1.0)
|
23
|
+
activesupport (= 5.1.0)
|
24
|
+
rack (~> 2.0)
|
25
|
+
rack-test (~> 0.6.3)
|
26
|
+
rails-dom-testing (~> 2.0)
|
27
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
28
|
+
actionview (5.1.0)
|
29
|
+
activesupport (= 5.1.0)
|
30
|
+
builder (~> 3.1)
|
31
|
+
erubi (~> 1.4)
|
32
|
+
rails-dom-testing (~> 2.0)
|
33
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
34
|
+
activejob (5.1.0)
|
35
|
+
activesupport (= 5.1.0)
|
36
|
+
globalid (>= 0.3.6)
|
37
|
+
activemodel (5.1.0)
|
38
|
+
activesupport (= 5.1.0)
|
39
|
+
activerecord (5.1.0)
|
40
|
+
activemodel (= 5.1.0)
|
41
|
+
activesupport (= 5.1.0)
|
42
|
+
arel (~> 8.0)
|
43
|
+
activesupport (5.1.0)
|
44
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
45
|
+
i18n (~> 0.7)
|
46
|
+
minitest (~> 5.1)
|
47
|
+
tzinfo (~> 1.1)
|
48
|
+
appraisal (2.2.0)
|
49
|
+
bundler
|
50
|
+
rake
|
51
|
+
thor (>= 0.14.0)
|
52
|
+
arel (8.0.0)
|
53
|
+
builder (3.2.3)
|
54
|
+
concurrent-ruby (1.0.5)
|
55
|
+
database_cleaner (1.3.0)
|
56
|
+
diff-lcs (1.3)
|
57
|
+
erubi (1.6.0)
|
58
|
+
globalid (0.4.0)
|
59
|
+
activesupport (>= 4.2.0)
|
60
|
+
i18n (0.8.1)
|
61
|
+
loofah (2.0.3)
|
62
|
+
nokogiri (>= 1.5.9)
|
63
|
+
mail (2.6.5)
|
64
|
+
mime-types (>= 1.16, < 4)
|
65
|
+
method_source (0.8.2)
|
66
|
+
mime-types (3.1)
|
67
|
+
mime-types-data (~> 3.2015)
|
68
|
+
mime-types-data (3.2016.0521)
|
69
|
+
mini_portile2 (2.1.0)
|
70
|
+
minitest (5.10.1)
|
71
|
+
nio4r (2.0.0)
|
72
|
+
nokogiri (1.7.1)
|
73
|
+
mini_portile2 (~> 2.1.0)
|
74
|
+
pg (0.20.0)
|
75
|
+
rack (2.0.1)
|
76
|
+
rack-test (0.6.3)
|
77
|
+
rack (>= 1.0)
|
78
|
+
rails (5.1.0)
|
79
|
+
actioncable (= 5.1.0)
|
80
|
+
actionmailer (= 5.1.0)
|
81
|
+
actionpack (= 5.1.0)
|
82
|
+
actionview (= 5.1.0)
|
83
|
+
activejob (= 5.1.0)
|
84
|
+
activemodel (= 5.1.0)
|
85
|
+
activerecord (= 5.1.0)
|
86
|
+
activesupport (= 5.1.0)
|
87
|
+
bundler (>= 1.3.0, < 2.0)
|
88
|
+
railties (= 5.1.0)
|
89
|
+
sprockets-rails (>= 2.0.0)
|
90
|
+
rails-dom-testing (2.0.2)
|
91
|
+
activesupport (>= 4.2.0, < 6.0)
|
92
|
+
nokogiri (~> 1.6)
|
93
|
+
rails-html-sanitizer (1.0.3)
|
94
|
+
loofah (~> 2.0)
|
95
|
+
railties (5.1.0)
|
96
|
+
actionpack (= 5.1.0)
|
97
|
+
activesupport (= 5.1.0)
|
98
|
+
method_source
|
99
|
+
rake (>= 0.8.7)
|
100
|
+
thor (>= 0.18.1, < 2.0)
|
101
|
+
rake (12.0.0)
|
102
|
+
request_store (1.3.2)
|
103
|
+
rspec (3.6.0)
|
104
|
+
rspec-core (~> 3.6.0)
|
105
|
+
rspec-expectations (~> 3.6.0)
|
106
|
+
rspec-mocks (~> 3.6.0)
|
107
|
+
rspec-core (3.6.0)
|
108
|
+
rspec-support (~> 3.6.0)
|
109
|
+
rspec-expectations (3.6.0)
|
110
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
111
|
+
rspec-support (~> 3.6.0)
|
112
|
+
rspec-mocks (3.6.0)
|
113
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
114
|
+
rspec-support (~> 3.6.0)
|
115
|
+
rspec-rails (3.6.0)
|
116
|
+
actionpack (>= 3.0)
|
117
|
+
activesupport (>= 3.0)
|
118
|
+
railties (>= 3.0)
|
119
|
+
rspec-core (~> 3.6.0)
|
120
|
+
rspec-expectations (~> 3.6.0)
|
121
|
+
rspec-mocks (~> 3.6.0)
|
122
|
+
rspec-support (~> 3.6.0)
|
123
|
+
rspec-support (3.6.0)
|
124
|
+
sprockets (3.7.1)
|
125
|
+
concurrent-ruby (~> 1.0)
|
126
|
+
rack (> 1, < 3)
|
127
|
+
sprockets-rails (3.2.0)
|
128
|
+
actionpack (>= 4.0)
|
129
|
+
activesupport (>= 4.0)
|
130
|
+
sprockets (>= 3.0.0)
|
131
|
+
thor (0.19.4)
|
132
|
+
thread_safe (0.3.6)
|
133
|
+
tzinfo (1.2.3)
|
134
|
+
thread_safe (~> 0.1)
|
135
|
+
websocket-driver (0.6.5)
|
136
|
+
websocket-extensions (>= 0.1.0)
|
137
|
+
websocket-extensions (0.1.2)
|
138
|
+
|
139
|
+
PLATFORMS
|
140
|
+
ruby
|
141
|
+
|
142
|
+
DEPENDENCIES
|
143
|
+
activerecord-multi-tenant!
|
144
|
+
appraisal
|
145
|
+
database_cleaner (~> 1.3.0)
|
146
|
+
pg
|
147
|
+
rails (= 5.1.0)
|
148
|
+
rake
|
149
|
+
rspec (>= 3.0)
|
150
|
+
rspec-rails
|
151
|
+
thor
|
152
|
+
|
153
|
+
BUNDLED WITH
|
154
|
+
1.12.5
|
@@ -2,10 +2,12 @@ if Object.const_defined?(:ActionController)
|
|
2
2
|
require_relative 'activerecord-multi-tenant/controller_extensions'
|
3
3
|
end
|
4
4
|
require_relative 'activerecord-multi-tenant/copy_from_client'
|
5
|
-
require_relative 'activerecord-multi-tenant/
|
5
|
+
require_relative 'activerecord-multi-tenant/fast_truncate'
|
6
6
|
require_relative 'activerecord-multi-tenant/migrations'
|
7
7
|
require_relative 'activerecord-multi-tenant/model_extensions'
|
8
8
|
require_relative 'activerecord-multi-tenant/multi_tenant'
|
9
|
+
require_relative 'activerecord-multi-tenant/query_rewriter'
|
10
|
+
require_relative 'activerecord-multi-tenant/query_monitor'
|
9
11
|
require_relative 'activerecord-multi-tenant/referential_integrity'
|
10
12
|
require_relative 'activerecord-multi-tenant/version'
|
11
13
|
require_relative 'activerecord-multi-tenant/with_lock'
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Truncates only the tables that have been modified, according to sequence
|
2
|
+
# values
|
3
|
+
module MultiTenant
|
4
|
+
module FastTruncate
|
5
|
+
def self.run(exclude: ['schema_migrations'])
|
6
|
+
# This is a slightly faster version of DatabaseCleaner.clean_with(:truncation, pre_count: true)
|
7
|
+
ActiveRecord::Base.connection.execute format(%(
|
8
|
+
DO LANGUAGE plpgsql $$
|
9
|
+
DECLARE
|
10
|
+
t record;
|
11
|
+
tables text[];
|
12
|
+
seq_exists boolean;
|
13
|
+
needs_truncate boolean;
|
14
|
+
BEGIN
|
15
|
+
FOR t IN SELECT schemaname, tablename FROM pg_tables WHERE schemaname = 'public' AND tablename NOT IN (%s) LOOP
|
16
|
+
EXECUTE 'SELECT EXISTS (SELECT * from pg_class c WHERE c.relkind = ''S'' AND c.relname=''' || t.tablename || '_id_seq'')' into seq_exists;
|
17
|
+
IF seq_exists THEN
|
18
|
+
EXECUTE 'SELECT last_value != start_value FROM ' || t.tablename || '_id_seq' INTO needs_truncate;
|
19
|
+
ELSE
|
20
|
+
needs_truncate := true;
|
21
|
+
END IF;
|
22
|
+
|
23
|
+
IF needs_truncate THEN
|
24
|
+
tables := array_append(tables, quote_ident(t.schemaname) || '.' || quote_ident(t.tablename));
|
25
|
+
END IF;
|
26
|
+
END LOOP;
|
27
|
+
|
28
|
+
EXECUTE 'TRUNCATE TABLE ' || array_to_string(tables, ', ') || ' RESTART IDENTITY CASCADE';
|
29
|
+
END$$;), exclude.map { |t| "'" + t + "'" }.join('\n'))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -4,8 +4,10 @@ module MultiTenant
|
|
4
4
|
|
5
5
|
def multi_tenant(tenant_name, options = {})
|
6
6
|
if to_s.underscore.to_sym == tenant_name
|
7
|
-
|
8
|
-
|
7
|
+
unless MultiTenant.with_write_only_mode_enabled?
|
8
|
+
# This is the tenant model itself. Workaround for https://github.com/citusdata/citus/issues/687
|
9
|
+
before_create -> { self.id ||= self.class.connection.select_value("SELECT nextval('" + [self.class.table_name, self.class.primary_key, 'seq'].join('_') + "'::regclass)") }
|
10
|
+
end
|
9
11
|
else
|
10
12
|
class << self
|
11
13
|
def scoped_by_tenant?
|
@@ -21,9 +23,9 @@ module MultiTenant
|
|
21
23
|
# Avoid primary_key errors when using composite primary keys (e.g. id, tenant_id)
|
22
24
|
def primary_key
|
23
25
|
return @primary_key if @primary_key
|
24
|
-
return @primary_key = super || DEFAULT_ID_FIELD if
|
26
|
+
return @primary_key = super || DEFAULT_ID_FIELD if ActiveRecord::VERSION::MAJOR < 5
|
25
27
|
|
26
|
-
primary_object_keys = (connection.schema_cache.primary_keys(table_name)
|
28
|
+
primary_object_keys = Array.wrap(connection.schema_cache.primary_keys(table_name)) - [partition_key]
|
27
29
|
if primary_object_keys.size == 1
|
28
30
|
@primary_key = primary_object_keys.first
|
29
31
|
else
|
@@ -40,21 +42,12 @@ module MultiTenant
|
|
40
42
|
belongs_to tenant_name, options.slice(:class_name, :inverse_of).merge(foreign_key: partition_key)
|
41
43
|
end
|
42
44
|
|
43
|
-
# Ensure all queries include the partition key
|
44
|
-
default_scope lambda {
|
45
|
-
if MultiTenant.current_tenant_id
|
46
|
-
where(arel_table[partition_key].eq(MultiTenant.current_tenant_id))
|
47
|
-
else
|
48
|
-
ActiveRecord::VERSION::MAJOR < 4 ? scoped : all
|
49
|
-
end
|
50
|
-
}
|
51
|
-
|
52
45
|
# New instances should have the tenant set
|
53
|
-
|
46
|
+
after_initialize Proc.new { |record|
|
54
47
|
if MultiTenant.current_tenant_id && record.public_send(partition_key.to_sym).nil?
|
55
48
|
record.public_send("#{partition_key}=".to_sym, MultiTenant.current_tenant_id)
|
56
49
|
end
|
57
|
-
}
|
50
|
+
}
|
58
51
|
|
59
52
|
to_include = Module.new do
|
60
53
|
define_method "#{partition_key}=" do |tenant_id|
|
@@ -9,6 +9,15 @@ module MultiTenant
|
|
9
9
|
"#{tenant_name.to_s}_id"
|
10
10
|
end
|
11
11
|
|
12
|
+
# In some cases we only have an ID - if defined we'll return the default tenant class in such cases
|
13
|
+
def self.default_tenant_class=(tenant_class); @@default_tenant_class = tenant_class; end
|
14
|
+
def self.default_tenant_class; @@default_tenant_class; end
|
15
|
+
|
16
|
+
# Write-only Mode - this only adds the tenant_id to new records, but doesn't
|
17
|
+
# require its presence for SELECTs/UPDATEs/DELETEs
|
18
|
+
def self.enable_write_only_mode; @@enable_write_only_mode = true; end
|
19
|
+
def self.with_write_only_mode_enabled?; @@enable_write_only_mode ||= false; end
|
20
|
+
|
12
21
|
# Workaroud to make "with_lock" work until https://github.com/citusdata/citus/issues/1236 is fixed
|
13
22
|
@@enable_with_lock_workaround = false
|
14
23
|
def self.enable_with_lock_workaround; @@enable_with_lock_workaround = true; end
|
@@ -30,6 +39,14 @@ module MultiTenant
|
|
30
39
|
current_tenant.is_a?(String) || current_tenant.is_a?(Integer)
|
31
40
|
end
|
32
41
|
|
42
|
+
def self.current_tenant_class
|
43
|
+
if current_tenant_is_id?
|
44
|
+
MultiTenant.default_tenant_class || fail('Only have tenant id, and no default tenant class set')
|
45
|
+
elsif current_tenant
|
46
|
+
MultiTenant.current_tenant.class.name
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
33
50
|
def self.with(tenant, &block)
|
34
51
|
return block.call if self.current_tenant == tenant
|
35
52
|
old_tenant = self.current_tenant
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Add generic warning when queries fail and there is no tenant set
|
2
|
+
module MultiTenant
|
3
|
+
# Option to enable query monitor
|
4
|
+
@@enable_query_monitor = false
|
5
|
+
def self.enable_query_monitor; @@enable_query_monitor = true; end
|
6
|
+
def self.query_monitor_enabled?; @@enable_query_monitor; end
|
7
|
+
|
8
|
+
class QueryMonitor
|
9
|
+
def start(name, id, payload); end
|
10
|
+
def finish(name, id, payload)
|
11
|
+
return unless MultiTenant.query_monitor_enabled?
|
12
|
+
return unless payload[:exception].present? && MultiTenant.current_tenant_id.nil?
|
13
|
+
Rails.logger.info 'WARNING: Tenant not present - make sure to add MultiTenant.with(tenant) { ... }'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
ActiveSupport::Notifications.subscribe('sql.active_record', MultiTenant::QueryMonitor.new) if ActiveRecord::VERSION::MAJOR >= 4
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
class TTTenantVisitor < Arel::Visitors::DepthFirst
|
4
|
+
def initialize(arel)
|
5
|
+
super(Proc.new {})
|
6
|
+
@tenant_relations = []
|
7
|
+
|
8
|
+
accept(arel.ast)
|
9
|
+
end
|
10
|
+
|
11
|
+
def tenant_relations
|
12
|
+
@tenant_relations.uniq
|
13
|
+
end
|
14
|
+
|
15
|
+
def visit_Arel_Table(table, _collector = nil)
|
16
|
+
@tenant_relations << table if tenant_relation?(table)
|
17
|
+
end
|
18
|
+
|
19
|
+
def visit_Arel_Nodes_TableAlias(table_alias, _collector = nil)
|
20
|
+
@tenant_relations << table_alias if tenant_relation?(table_alias.left)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def tenant_relation?(table)
|
26
|
+
model = table.name.classify.constantize
|
27
|
+
model && model.respond_to?(:scoped_by_tenant?) && model.scoped_by_tenant?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
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))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
arel
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'sidekiq/client'
|
2
|
+
|
3
|
+
module Sidekiq::Middleware::MultiTenant
|
4
|
+
# Get the current tenant and store in the message to be sent to Sidekiq.
|
5
|
+
class Client
|
6
|
+
def call(worker_class, msg, queue, redis_pool)
|
7
|
+
msg['multi_tenant'] ||=
|
8
|
+
{
|
9
|
+
'class' => MultiTenant.current_tenant_class,
|
10
|
+
'id' => MultiTenant.current_tenant_id
|
11
|
+
} if MultiTenant.current_tenant.present?
|
12
|
+
|
13
|
+
yield
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Pull the tenant out and run the current thread with it.
|
18
|
+
class Server
|
19
|
+
def call(worker_class, msg, queue)
|
20
|
+
if msg.has_key?('multi_tenant')
|
21
|
+
tenant = msg['multi_tenant']['class'].constantize.find(msg['multi_tenant']['id'])
|
22
|
+
MultiTenant.with(tenant) do
|
23
|
+
yield
|
24
|
+
end
|
25
|
+
else
|
26
|
+
Rails.logger.warn("Running #{worker_class} without tenant.")
|
27
|
+
yield
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module Sidekiq
|
34
|
+
class Client
|
35
|
+
def push_bulk_with_tenants(items)
|
36
|
+
job = items['jobs'].first
|
37
|
+
return [] unless job # no jobs to push
|
38
|
+
raise ArgumentError, "Bulk arguments must be an Array of Hashes: [{ 'args' => [1], 'tenant_id' => 1 }, ...]" if !job.is_a?(Hash)
|
39
|
+
|
40
|
+
normed = normalize_item(items.except('jobs').merge('args' => []))
|
41
|
+
payloads = items['jobs'].map do |job|
|
42
|
+
MultiTenant.with(job['tenant_id']) do
|
43
|
+
copy = normed.merge('args' => job['args'], 'jid' => SecureRandom.hex(12), 'enqueued_at' => Time.now.to_f)
|
44
|
+
result = process_single(items['class'], copy)
|
45
|
+
result ? result : nil
|
46
|
+
end
|
47
|
+
end.compact
|
48
|
+
|
49
|
+
raw_push(payloads) if !payloads.empty?
|
50
|
+
payloads.collect { |payload| payload['jid'] }
|
51
|
+
end
|
52
|
+
|
53
|
+
class << self
|
54
|
+
def push_bulk_with_tenants(items)
|
55
|
+
new.push_bulk_with_tenants(items)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -25,7 +25,7 @@ RSpec.configure do |config|
|
|
25
25
|
DatabaseCleaner[:active_record].strategy = :truncation
|
26
26
|
DatabaseCleaner[:active_record].clean
|
27
27
|
|
28
|
-
# Keep this here until https://github.com/citusdata/citus/issues/1236 is fixed
|
28
|
+
# Keep this here until https://github.com/citusdata/citus/issues/1236 is fixed
|
29
29
|
MultiTenant.enable_with_lock_workaround
|
30
30
|
end
|
31
31
|
|
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.
|
4
|
+
version: 0.5.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-
|
11
|
+
date: 2017-05-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: request_store
|
@@ -138,8 +138,8 @@ files:
|
|
138
138
|
- Rakefile
|
139
139
|
- activerecord-multi-tenant.gemspec
|
140
140
|
- docker-compose.yml
|
141
|
-
- gemfiles/active_record_5.
|
142
|
-
- gemfiles/active_record_5.
|
141
|
+
- gemfiles/active_record_5.1.gemfile
|
142
|
+
- gemfiles/active_record_5.1.gemfile.lock
|
143
143
|
- gemfiles/rails_3.2.gemfile
|
144
144
|
- gemfiles/rails_3.2.gemfile.lock
|
145
145
|
- gemfiles/rails_4.0.gemfile
|
@@ -150,14 +150,19 @@ files:
|
|
150
150
|
- gemfiles/rails_4.2.gemfile.lock
|
151
151
|
- gemfiles/rails_5.0.gemfile
|
152
152
|
- gemfiles/rails_5.0.gemfile.lock
|
153
|
+
- gemfiles/rails_5.1.gemfile
|
154
|
+
- gemfiles/rails_5.1.gemfile.lock
|
153
155
|
- lib/activerecord-multi-tenant.rb
|
154
156
|
- lib/activerecord-multi-tenant/controller_extensions.rb
|
155
157
|
- lib/activerecord-multi-tenant/copy_from_client.rb
|
156
|
-
- lib/activerecord-multi-tenant/
|
158
|
+
- lib/activerecord-multi-tenant/fast_truncate.rb
|
157
159
|
- lib/activerecord-multi-tenant/migrations.rb
|
158
160
|
- lib/activerecord-multi-tenant/model_extensions.rb
|
159
161
|
- lib/activerecord-multi-tenant/multi_tenant.rb
|
162
|
+
- lib/activerecord-multi-tenant/query_monitor.rb
|
163
|
+
- lib/activerecord-multi-tenant/query_rewriter.rb
|
160
164
|
- lib/activerecord-multi-tenant/referential_integrity.rb
|
165
|
+
- lib/activerecord-multi-tenant/sidekiq.rb
|
161
166
|
- lib/activerecord-multi-tenant/version.rb
|
162
167
|
- lib/activerecord-multi-tenant/with_lock.rb
|
163
168
|
- spec/activerecord-multi-tenant/controller_extensions_spec.rb
|
@@ -188,7 +193,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
188
193
|
version: '0'
|
189
194
|
requirements: []
|
190
195
|
rubyforge_project:
|
191
|
-
rubygems_version: 2.5.1
|
196
|
+
rubygems_version: 2.4.5.1
|
192
197
|
signing_key:
|
193
198
|
specification_version: 4
|
194
199
|
summary: ActiveRecord/Rails integration for multi-tenant databases, in particular
|
@@ -1,16 +0,0 @@
|
|
1
|
-
require 'active_record'
|
2
|
-
|
3
|
-
class ActiveRecord::Base
|
4
|
-
class << self
|
5
|
-
alias :unscoped_orig :unscoped
|
6
|
-
def unscoped
|
7
|
-
scope = if respond_to?(:scoped_by_tenant?) && MultiTenant.current_tenant_id
|
8
|
-
unscoped_orig.where(arel_table[self.partition_key].eq(MultiTenant.current_tenant_id))
|
9
|
-
else
|
10
|
-
unscoped_orig
|
11
|
-
end
|
12
|
-
|
13
|
-
block_given? ? scope.scoping { yield } : scope
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|