activerecord-multi-tenant 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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