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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3ed816e4227187f3f64ae1e1b53471c74f220150
4
- data.tar.gz: acd8708805299354b2516a61fe3365f557ac09d4
3
+ metadata.gz: fc1deedd61a8e692c880e666ccd16542079b3b5a
4
+ data.tar.gz: e71ddea3d21d23c511e0d85d947b974f5ff40591
5
5
  SHA512:
6
- metadata.gz: 74c9aa0bba774511e236db9e08f830ebc09703b8f58d3d6f39c97454032ac607a0f5b7050ec24a78a6b3322cc7df9b11f658ef61f8ddbbaf0726c8b73c5b32d0
7
- data.tar.gz: b5f1427977dac05e87491fa72e604a6454067d9d8b3742ee5b63f367de0dc6411bf17386e868a5761a3bce1c9df5166ac00eecc0d1e522e52a64397b91cbb73b
6
+ metadata.gz: c7354a9d0c30caa2264d35739edf4f181945bf035326c13354824fee2eb3d2717804236b412d5f5fa9352a498a62aba7c846b12fcd4b0566e907b0bbca3276a5
7
+ data.tar.gz: 6d9aaf1c06cc24daf27423eec9fe1c032070f109c06d69c0c915c68ab549da89e37057388a65aed42e400a13abbc206285fa744216fbbed25ee35f2f2a106a05
@@ -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/active_record_5.0.gemfile
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/active_record_5.0.gemfile
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 'active-record-5.0' do
23
- gem 'activerecord', '5.0.2'
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
@@ -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)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- activerecord-multi-tenant (0.4.1)
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.3.7)
59
- activesupport (>= 4.1.0)
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.4)
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://img.shields.io/gem/v/activerecord-multi-tenant.svg)](https://rubygems.org/gems/activerecord-multi-tenant) [ ![](https://img.shields.io/gem/dt/activerecord-multi-tenant.svg)](https://rubygems.org/gems/activerecord-multi-tenant)
2
2
 
3
- ActiveRecord/Rails integration for multi-tenant databases, in particular the Citus extension for PostgreSQL.
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)
@@ -3,6 +3,6 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "appraisal"
6
- gem "activerecord", "5.0.2"
6
+ gem "activerecord", "5.1.0"
7
7
 
8
8
  gemspec :path => "../"
@@ -1,66 +1,66 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- activerecord-multi-tenant (0.4.1)
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.2)
12
- actionpack (= 5.0.2)
13
- nio4r (>= 1.2, < 3.0)
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.2)
16
- actionpack (= 5.0.2)
17
- actionview (= 5.0.2)
18
- activejob (= 5.0.2)
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.2)
22
- actionview (= 5.0.2)
23
- activesupport (= 5.0.2)
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.2)
29
- activesupport (= 5.0.2)
28
+ actionview (5.1.0)
29
+ activesupport (= 5.1.0)
30
30
  builder (~> 3.1)
31
- erubis (~> 2.7.0)
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.2)
35
- activesupport (= 5.0.2)
34
+ activejob (5.1.0)
35
+ activesupport (= 5.1.0)
36
36
  globalid (>= 0.3.6)
37
- activemodel (5.0.2)
38
- activesupport (= 5.0.2)
39
- activerecord (5.0.2)
40
- activemodel (= 5.0.2)
41
- activesupport (= 5.0.2)
42
- arel (~> 7.0)
43
- activesupport (5.0.2)
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.1.0)
48
+ appraisal (2.2.0)
49
49
  bundler
50
50
  rake
51
51
  thor (>= 0.14.0)
52
- arel (7.1.4)
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
- erubis (2.7.0)
58
- globalid (0.3.7)
59
- activesupport (>= 4.1.0)
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.4)
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.2)
79
- actioncable (= 5.0.2)
80
- actionmailer (= 5.0.2)
81
- actionpack (= 5.0.2)
82
- actionview (= 5.0.2)
83
- activejob (= 5.0.2)
84
- activemodel (= 5.0.2)
85
- activerecord (= 5.0.2)
86
- activesupport (= 5.0.2)
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.2)
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.2)
96
- actionpack (= 5.0.2)
97
- activesupport (= 5.0.2)
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.5.0)
104
- rspec-core (~> 3.5.0)
105
- rspec-expectations (~> 3.5.0)
106
- rspec-mocks (~> 3.5.0)
107
- rspec-core (3.5.4)
108
- rspec-support (~> 3.5.0)
109
- rspec-expectations (3.5.0)
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.5.0)
112
- rspec-mocks (3.5.0)
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.5.0)
115
- rspec-rails (3.5.2)
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.5.0)
120
- rspec-expectations (~> 3.5.0)
121
- rspec-mocks (~> 3.5.0)
122
- rspec-support (~> 3.5.0)
123
- rspec-support (3.5.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
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.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.2)
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.14.5
154
+ 1.12.5
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- activerecord-multi-tenant (0.4.1)
4
+ activerecord-multi-tenant (0.5.0)
5
5
  rails (>= 3.1)
6
6
  request_store (>= 1.0.5)
7
7
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- activerecord-multi-tenant (0.4.1)
4
+ activerecord-multi-tenant (0.5.0)
5
5
  rails (>= 3.1)
6
6
  request_store (>= 1.0.5)
7
7
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- activerecord-multi-tenant (0.4.1)
4
+ activerecord-multi-tenant (0.5.0)
5
5
  rails (>= 3.1)
6
6
  request_store (>= 1.0.5)
7
7
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- activerecord-multi-tenant (0.4.1)
4
+ activerecord-multi-tenant (0.5.0)
5
5
  rails (>= 3.1)
6
6
  request_store (>= 1.0.5)
7
7
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- activerecord-multi-tenant (0.4.1)
4
+ activerecord-multi-tenant (0.5.0)
5
5
  rails (>= 3.1)
6
6
  request_store (>= 1.0.5)
7
7
 
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal"
6
+ gem "rails", "5.1.0"
7
+
8
+ gemspec :path => "../"
@@ -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/default_scope'
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
- # This is the tenant model itself. Workaround for https://github.com/citusdata/citus/issues/687
8
- before_create -> { self.id ||= self.class.connection.select_value("SELECT nextval('" + [self.class.table_name, self.class.primary_key, 'seq'].join('_') + "'::regclass)") }
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 Rails::VERSION::MAJOR < 5
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) || []) - [partition_key]
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
- before_validation Proc.new { |record|
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
- }, on: :create
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
@@ -1,3 +1,3 @@
1
1
  module MultiTenant
2
- VERSION = '0.4.1'
2
+ VERSION = '0.5.0'
3
3
  end
@@ -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 in a patch release we can run tests with
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.1
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-03-24 00:00:00.000000000 Z
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.0.gemfile
142
- - gemfiles/active_record_5.0.gemfile.lock
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/default_scope.rb
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