activerecord-multi-tenant 0.7.0 → 0.8.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: 0ad5f8df62dcf4cb4308e7b52736ba20212cc154
4
- data.tar.gz: 244c9cdcd00b6d62a982a9dfa6917dade6aacf60
3
+ metadata.gz: 37f0a1949a56f1bf5bbf3c5e4e00f0e5afdc87f8
4
+ data.tar.gz: bcf4da0d8b2228a3cac88ca747286beabfb543c8
5
5
  SHA512:
6
- metadata.gz: d2cde81a900904e331d3f4558b5ebfbb55547bebef5ec841087081a23598bcc89bbbd7d077fab260cc70a0fb5b0aa8ab535d4b89040cb4f784b6299e2d27231c
7
- data.tar.gz: 406344742c98800907c67436dc7d711e85a11d73d78b262504586de9c0a943cef854181493f6985dac3a0dcf5feb902ddff00b0ea0f323edfb5446d846483bad
6
+ metadata.gz: 54f89ab0a2b8b648a55be5de6e31fbf7acbafb125f530348bc30b0e1ddde48444a0be30aa60c54ee17e075fbc0f868aae45b94277daf977e1d0160385fe1d426
7
+ data.tar.gz: 60483177cedff455c9faafdfe6f3370cfd09ab90bbb5c5ed10a1c4b71c606c425f325611c063f77441d0647b66093fc30661b8bba7ee346121acb98bd8a18fab
data/.travis.yml CHANGED
@@ -4,12 +4,11 @@ cache: bundler
4
4
  language: ruby
5
5
 
6
6
  rvm:
7
- - 2.1
8
- - 2.3.3
9
- - 2.4.0
7
+ - 2.2.7
8
+ - 2.3.4
9
+ - 2.4.1
10
10
 
11
11
  gemfile:
12
- - gemfiles/rails_3.2.gemfile
13
12
  - gemfiles/rails_4.0.gemfile
14
13
  - gemfiles/rails_4.1.gemfile
15
14
  - gemfiles/rails_4.2.gemfile
@@ -20,17 +19,9 @@ gemfile:
20
19
  matrix:
21
20
  fast_finish: true
22
21
  exclude:
23
- - rvm: 2.1
24
- gemfile: gemfiles/rails_5.0.gemfile
25
- - rvm: 2.1
26
- gemfile: gemfiles/rails_5.1.gemfile
27
- - rvm: 2.1
28
- gemfile: gemfiles/active_record_5.1.gemfile
29
- - rvm: 2.4.0
30
- gemfile: gemfiles/rails_3.2.gemfile
31
- - rvm: 2.4.0
22
+ - rvm: 2.4.1
32
23
  gemfile: gemfiles/rails_4.0.gemfile
33
- - rvm: 2.4.0
24
+ - rvm: 2.4.1
34
25
  gemfile: gemfiles/rails_4.1.gemfile
35
26
 
36
27
  services:
data/Appraisals CHANGED
@@ -1,8 +1,3 @@
1
- appraise 'rails-3.2' do
2
- gem 'test-unit', '~> 3.0'
3
- gem 'rails', '3.2.22.5'
4
- end
5
-
6
1
  appraise 'rails-4.0' do
7
2
  gem 'rails', '4.0.13'
8
3
  end
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.0 2017-08-16
4
+
5
+ * Significant improvements and simplifications of query rewriting
6
+ * Big thanks to [Kyle Bock](https://github.com/kwbock) and [Ben Olive](https://github.com/sionide21)
7
+ for (re-)writing this code and verifying it works well
8
+ * This fixes caching issues across multiple MultiTenant.with { } blocks when
9
+ interacting with the Rails statement cache
10
+ * Drop support for Rails 3.2
11
+ * The arel version used in Rails 3.2 has caused more trouble than its worth -
12
+ it seems less troublesome to ask any users of this library to upgrade to at
13
+ least Rails 4.0
14
+
15
+
3
16
  ## 0.7.0 2017-07-18
4
17
 
5
18
  * Switch back to Relation-based mechanism of hooking into ActiveRecord (this resolves issues for simple queries that didn't get rewritten)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- activerecord-multi-tenant (0.7.0)
4
+ activerecord-multi-tenant (0.8.0)
5
5
  rails (>= 3.1)
6
6
  request_store (>= 1.0.5)
7
7
 
@@ -51,6 +51,8 @@ GEM
51
51
  thor (>= 0.14.0)
52
52
  arel (7.1.4)
53
53
  builder (3.2.2)
54
+ byebug (9.0.6)
55
+ coderay (1.1.1)
54
56
  concurrent-ruby (1.0.4)
55
57
  connection_pool (2.2.1)
56
58
  diff-lcs (1.2.5)
@@ -60,7 +62,7 @@ GEM
60
62
  i18n (0.7.0)
61
63
  loofah (2.0.3)
62
64
  nokogiri (>= 1.5.9)
63
- mail (2.6.5)
65
+ mail (2.6.6)
64
66
  mime-types (>= 1.16, < 4)
65
67
  method_source (0.8.2)
66
68
  mime-types (3.1)
@@ -72,6 +74,13 @@ GEM
72
74
  nokogiri (1.7.0)
73
75
  mini_portile2 (~> 2.1.0)
74
76
  pg (0.19.0)
77
+ pry (0.10.4)
78
+ coderay (~> 1.1.0)
79
+ method_source (~> 0.8.1)
80
+ slop (~> 3.4)
81
+ pry-byebug (3.4.2)
82
+ byebug (~> 9.0)
83
+ pry (~> 0.10)
75
84
  rack (2.0.1)
76
85
  rack-protection (1.5.3)
77
86
  rack
@@ -129,6 +138,7 @@ GEM
129
138
  connection_pool (~> 2.2, >= 2.2.0)
130
139
  rack-protection (>= 1.5.0)
131
140
  redis (~> 3.2, >= 3.2.1)
141
+ slop (3.6.0)
132
142
  sprockets (3.7.1)
133
143
  concurrent-ruby (~> 1.0)
134
144
  rack (> 1, < 3)
@@ -151,6 +161,8 @@ DEPENDENCIES
151
161
  activerecord-multi-tenant!
152
162
  appraisal
153
163
  pg
164
+ pry
165
+ pry-byebug
154
166
  rake
155
167
  rspec (>= 3.0)
156
168
  rspec-rails
@@ -158,4 +170,4 @@ DEPENDENCIES
158
170
  thor
159
171
 
160
172
  BUNDLED WITH
161
- 1.15.1
173
+ 1.15.3
data/README.md CHANGED
@@ -16,7 +16,7 @@ gem 'activerecord-multi-tenant'
16
16
 
17
17
  ## Supported Rails versions
18
18
 
19
- All Ruby on Rails versions starting with 3.2 or newer are supported.
19
+ All Ruby on Rails versions starting with 4.0 or newer are supported.
20
20
 
21
21
  This gem only supports ActiveRecord (the Rails default ORM), and not alternative ORMs like Sequel.
22
22
 
@@ -101,4 +101,4 @@ This gem was initially based on [acts_as_tenant](https://github.com/ErwinM/acts_
101
101
  ## License
102
102
 
103
103
  Licensed under the MIT license<br>
104
- Copyright (c) 2016, Citus Data Inc.
104
+ Copyright (c) 2017, Citus Data Inc.
@@ -24,4 +24,6 @@ Gem::Specification.new do |s|
24
24
  s.add_development_dependency 'rake'
25
25
  s.add_development_dependency 'sidekiq'
26
26
  s.add_development_dependency 'thor'
27
+ s.add_development_dependency 'pry'
28
+ s.add_development_dependency 'pry-byebug'
27
29
  end
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- activerecord-multi-tenant (0.7.0)
4
+ activerecord-multi-tenant (0.8.0)
5
5
  rails (>= 3.1)
6
6
  request_store (>= 1.0.5)
7
7
 
@@ -51,6 +51,8 @@ GEM
51
51
  thor (>= 0.14.0)
52
52
  arel (8.0.0)
53
53
  builder (3.2.3)
54
+ byebug (9.0.6)
55
+ coderay (1.1.1)
54
56
  concurrent-ruby (1.0.5)
55
57
  connection_pool (2.2.1)
56
58
  diff-lcs (1.3)
@@ -60,7 +62,7 @@ GEM
60
62
  i18n (0.8.1)
61
63
  loofah (2.0.3)
62
64
  nokogiri (>= 1.5.9)
63
- mail (2.6.5)
65
+ mail (2.6.6)
64
66
  mime-types (>= 1.16, < 4)
65
67
  method_source (0.8.2)
66
68
  mime-types (3.1)
@@ -68,10 +70,17 @@ GEM
68
70
  mime-types-data (3.2016.0521)
69
71
  mini_portile2 (2.1.0)
70
72
  minitest (5.10.1)
71
- nio4r (2.0.0)
73
+ nio4r (2.1.0)
72
74
  nokogiri (1.7.1)
73
75
  mini_portile2 (~> 2.1.0)
74
76
  pg (0.20.0)
77
+ pry (0.10.4)
78
+ coderay (~> 1.1.0)
79
+ method_source (~> 0.8.1)
80
+ slop (~> 3.4)
81
+ pry-byebug (3.4.2)
82
+ byebug (~> 9.0)
83
+ pry (~> 0.10)
75
84
  rack (2.0.1)
76
85
  rack-protection (1.5.3)
77
86
  rack
@@ -129,6 +138,7 @@ GEM
129
138
  connection_pool (~> 2.2, >= 2.2.0)
130
139
  rack-protection (>= 1.5.0)
131
140
  redis (~> 3.2, >= 3.2.1)
141
+ slop (3.6.0)
132
142
  sprockets (3.7.1)
133
143
  concurrent-ruby (~> 1.0)
134
144
  rack (> 1, < 3)
@@ -152,6 +162,8 @@ DEPENDENCIES
152
162
  activerecord-multi-tenant!
153
163
  appraisal
154
164
  pg
165
+ pry
166
+ pry-byebug
155
167
  rake
156
168
  rspec (>= 3.0)
157
169
  rspec-rails
@@ -159,4 +171,4 @@ DEPENDENCIES
159
171
  thor
160
172
 
161
173
  BUNDLED WITH
162
- 1.15.1
174
+ 1.15.3
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- activerecord-multi-tenant (0.7.0)
4
+ activerecord-multi-tenant (0.8.0)
5
5
  rails (>= 3.1)
6
6
  request_store (>= 1.0.5)
7
7
 
@@ -38,6 +38,8 @@ GEM
38
38
  thor (>= 0.14.0)
39
39
  arel (4.0.2)
40
40
  builder (3.1.4)
41
+ byebug (9.0.6)
42
+ coderay (1.1.1)
41
43
  concurrent-ruby (1.0.5)
42
44
  connection_pool (2.2.1)
43
45
  diff-lcs (1.3)
@@ -45,12 +47,20 @@ GEM
45
47
  i18n (0.8.1)
46
48
  mail (2.6.4)
47
49
  mime-types (>= 1.16, < 4)
50
+ method_source (0.8.2)
48
51
  mime-types (3.1)
49
52
  mime-types-data (~> 3.2015)
50
53
  mime-types-data (3.2016.0521)
51
54
  minitest (4.7.5)
52
55
  multi_json (1.12.1)
53
56
  pg (0.20.0)
57
+ pry (0.10.4)
58
+ coderay (~> 1.1.0)
59
+ method_source (~> 0.8.1)
60
+ slop (~> 3.4)
61
+ pry-byebug (3.4.2)
62
+ byebug (~> 9.0)
63
+ pry (~> 0.10)
54
64
  rack (1.5.5)
55
65
  rack-protection (2.0.0)
56
66
  rack
@@ -98,6 +108,7 @@ GEM
98
108
  connection_pool (~> 2.2, >= 2.2.0)
99
109
  rack-protection (>= 1.5.0)
100
110
  redis (~> 3.3, >= 3.3.3)
111
+ slop (3.6.0)
101
112
  sprockets (3.7.1)
102
113
  concurrent-ruby (~> 1.0)
103
114
  rack (> 1, < 3)
@@ -116,6 +127,8 @@ DEPENDENCIES
116
127
  activerecord-multi-tenant!
117
128
  appraisal
118
129
  pg
130
+ pry
131
+ pry-byebug
119
132
  rails (= 4.0.13)
120
133
  rake
121
134
  rspec (>= 3.0)
@@ -124,4 +137,4 @@ DEPENDENCIES
124
137
  thor
125
138
 
126
139
  BUNDLED WITH
127
- 1.15.1
140
+ 1.15.3
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- activerecord-multi-tenant (0.7.0)
4
+ activerecord-multi-tenant (0.8.0)
5
5
  rails (>= 3.1)
6
6
  request_store (>= 1.0.5)
7
7
 
@@ -40,6 +40,8 @@ GEM
40
40
  thor (>= 0.14.0)
41
41
  arel (5.0.1.20140414130214)
42
42
  builder (3.2.3)
43
+ byebug (9.0.6)
44
+ coderay (1.1.1)
43
45
  concurrent-ruby (1.0.5)
44
46
  connection_pool (2.2.1)
45
47
  diff-lcs (1.3)
@@ -48,11 +50,19 @@ GEM
48
50
  json (1.8.6)
49
51
  mail (2.6.4)
50
52
  mime-types (>= 1.16, < 4)
53
+ method_source (0.8.2)
51
54
  mime-types (3.1)
52
55
  mime-types-data (~> 3.2015)
53
56
  mime-types-data (3.2016.0521)
54
57
  minitest (5.10.1)
55
58
  pg (0.20.0)
59
+ pry (0.10.4)
60
+ coderay (~> 1.1.0)
61
+ method_source (~> 0.8.1)
62
+ slop (~> 3.4)
63
+ pry-byebug (3.4.2)
64
+ byebug (~> 9.0)
65
+ pry (~> 0.10)
56
66
  rack (1.5.5)
57
67
  rack-protection (2.0.0)
58
68
  rack
@@ -102,6 +112,7 @@ GEM
102
112
  connection_pool (~> 2.2, >= 2.2.0)
103
113
  rack-protection (>= 1.5.0)
104
114
  redis (~> 3.3, >= 3.3.3)
115
+ slop (3.6.0)
105
116
  sprockets (3.7.1)
106
117
  concurrent-ruby (~> 1.0)
107
118
  rack (> 1, < 3)
@@ -121,6 +132,8 @@ DEPENDENCIES
121
132
  activerecord-multi-tenant!
122
133
  appraisal
123
134
  pg
135
+ pry
136
+ pry-byebug
124
137
  rails (= 4.1.16)
125
138
  rake
126
139
  rspec (>= 3.0)
@@ -129,4 +142,4 @@ DEPENDENCIES
129
142
  thor
130
143
 
131
144
  BUNDLED WITH
132
- 1.15.1
145
+ 1.15.3
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- activerecord-multi-tenant (0.7.0)
4
+ activerecord-multi-tenant (0.8.0)
5
5
  rails (>= 3.1)
6
6
  request_store (>= 1.0.5)
7
7
 
@@ -48,6 +48,8 @@ GEM
48
48
  thor (>= 0.14.0)
49
49
  arel (6.0.4)
50
50
  builder (3.2.3)
51
+ byebug (9.0.6)
52
+ coderay (1.1.1)
51
53
  concurrent-ruby (1.0.5)
52
54
  connection_pool (2.2.1)
53
55
  diff-lcs (1.3)
@@ -59,6 +61,7 @@ GEM
59
61
  nokogiri (>= 1.5.9)
60
62
  mail (2.6.4)
61
63
  mime-types (>= 1.16, < 4)
64
+ method_source (0.8.2)
62
65
  mime-types (3.1)
63
66
  mime-types-data (~> 3.2015)
64
67
  mime-types-data (3.2016.0521)
@@ -67,6 +70,13 @@ GEM
67
70
  nokogiri (1.7.1)
68
71
  mini_portile2 (~> 2.1.0)
69
72
  pg (0.20.0)
73
+ pry (0.10.4)
74
+ coderay (~> 1.1.0)
75
+ method_source (~> 0.8.1)
76
+ slop (~> 3.4)
77
+ pry-byebug (3.4.2)
78
+ byebug (~> 9.0)
79
+ pry (~> 0.10)
70
80
  rack (1.6.5)
71
81
  rack-protection (1.5.3)
72
82
  rack
@@ -125,6 +135,7 @@ GEM
125
135
  connection_pool (~> 2.2, >= 2.2.0)
126
136
  rack-protection (>= 1.5.0)
127
137
  redis (~> 3.2, >= 3.2.1)
138
+ slop (3.6.0)
128
139
  sprockets (3.7.1)
129
140
  concurrent-ruby (~> 1.0)
130
141
  rack (> 1, < 3)
@@ -144,6 +155,8 @@ DEPENDENCIES
144
155
  activerecord-multi-tenant!
145
156
  appraisal
146
157
  pg
158
+ pry
159
+ pry-byebug
147
160
  rails (= 4.2.8)
148
161
  rake
149
162
  rspec (>= 3.0)
@@ -152,4 +165,4 @@ DEPENDENCIES
152
165
  thor
153
166
 
154
167
  BUNDLED WITH
155
- 1.15.1
168
+ 1.15.3
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- activerecord-multi-tenant (0.7.0)
4
+ activerecord-multi-tenant (0.8.0)
5
5
  rails (>= 3.1)
6
6
  request_store (>= 1.0.5)
7
7
 
@@ -51,6 +51,8 @@ GEM
51
51
  thor (>= 0.14.0)
52
52
  arel (7.1.4)
53
53
  builder (3.2.3)
54
+ byebug (9.0.6)
55
+ coderay (1.1.1)
54
56
  concurrent-ruby (1.0.5)
55
57
  connection_pool (2.2.1)
56
58
  diff-lcs (1.3)
@@ -72,6 +74,13 @@ GEM
72
74
  nokogiri (1.7.1)
73
75
  mini_portile2 (~> 2.1.0)
74
76
  pg (0.20.0)
77
+ pry (0.10.4)
78
+ coderay (~> 1.1.0)
79
+ method_source (~> 0.8.1)
80
+ slop (~> 3.4)
81
+ pry-byebug (3.4.2)
82
+ byebug (~> 9.0)
83
+ pry (~> 0.10)
75
84
  rack (2.0.1)
76
85
  rack-protection (1.5.3)
77
86
  rack
@@ -129,6 +138,7 @@ GEM
129
138
  connection_pool (~> 2.2, >= 2.2.0)
130
139
  rack-protection (>= 1.5.0)
131
140
  redis (~> 3.2, >= 3.2.1)
141
+ slop (3.6.0)
132
142
  sprockets (3.7.1)
133
143
  concurrent-ruby (~> 1.0)
134
144
  rack (> 1, < 3)
@@ -151,6 +161,8 @@ DEPENDENCIES
151
161
  activerecord-multi-tenant!
152
162
  appraisal
153
163
  pg
164
+ pry
165
+ pry-byebug
154
166
  rails (= 5.0.1)
155
167
  rake
156
168
  rspec (>= 3.0)
@@ -159,4 +171,4 @@ DEPENDENCIES
159
171
  thor
160
172
 
161
173
  BUNDLED WITH
162
- 1.15.1
174
+ 1.15.3
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- activerecord-multi-tenant (0.7.0)
4
+ activerecord-multi-tenant (0.8.0)
5
5
  rails (>= 3.1)
6
6
  request_store (>= 1.0.5)
7
7
 
@@ -51,6 +51,8 @@ GEM
51
51
  thor (>= 0.14.0)
52
52
  arel (8.0.0)
53
53
  builder (3.2.3)
54
+ byebug (9.0.6)
55
+ coderay (1.1.1)
54
56
  concurrent-ruby (1.0.5)
55
57
  connection_pool (2.2.1)
56
58
  diff-lcs (1.3)
@@ -72,6 +74,13 @@ GEM
72
74
  nokogiri (1.7.1)
73
75
  mini_portile2 (~> 2.1.0)
74
76
  pg (0.20.0)
77
+ pry (0.10.4)
78
+ coderay (~> 1.1.0)
79
+ method_source (~> 0.8.1)
80
+ slop (~> 3.4)
81
+ pry-byebug (3.4.2)
82
+ byebug (~> 9.0)
83
+ pry (~> 0.10)
75
84
  rack (2.0.1)
76
85
  rack-protection (1.5.3)
77
86
  rack
@@ -129,6 +138,7 @@ GEM
129
138
  connection_pool (~> 2.2, >= 2.2.0)
130
139
  rack-protection (>= 1.5.0)
131
140
  redis (~> 3.2, >= 3.2.1)
141
+ slop (3.6.0)
132
142
  sprockets (3.7.1)
133
143
  concurrent-ruby (~> 1.0)
134
144
  rack (> 1, < 3)
@@ -151,6 +161,8 @@ DEPENDENCIES
151
161
  activerecord-multi-tenant!
152
162
  appraisal
153
163
  pg
164
+ pry
165
+ pry-byebug
154
166
  rails (= 5.1.0)
155
167
  rake
156
168
  rspec (>= 3.0)
@@ -159,4 +171,4 @@ DEPENDENCIES
159
171
  thor
160
172
 
161
173
  BUNDLED WITH
162
- 1.15.1
174
+ 1.15.3
@@ -15,4 +15,4 @@ module MultiTenant
15
15
  end
16
16
  end
17
17
 
18
- ActiveSupport::Notifications.subscribe('sql.active_record', MultiTenant::QueryMonitor.new) if ActiveRecord::VERSION::MAJOR >= 4
18
+ ActiveSupport::Notifications.subscribe('sql.active_record', MultiTenant::QueryMonitor.new)
@@ -1,60 +1,117 @@
1
1
  require 'active_record'
2
2
 
3
3
  module MultiTenant
4
+ class Table
5
+ attr_reader :arel_table
6
+
7
+ def initialize(arel_table)
8
+ @arel_table = arel_table
9
+ end
10
+
11
+ def eql?(rhs)
12
+ self.class == rhs.class &&
13
+ equality_fields.eql?(rhs.equality_fields)
14
+ end
15
+
16
+ def hash
17
+ equality_fields.hash
18
+ end
19
+
20
+ protected
21
+
22
+ def equality_fields
23
+ [arel_table.name, arel_table.table_alias]
24
+ end
25
+ end
26
+
27
+ class Context
28
+ attr_reader :arel_node, :known_relations, :handled_relations
29
+
30
+ def initialize(arel_node)
31
+ @arel_node = arel_node
32
+ @known_relations = []
33
+ @handled_relations = []
34
+ end
35
+
36
+ def discover_relations
37
+ old_discovering = @discovering
38
+ @discovering = true
39
+ yield
40
+ @discovering = old_discovering
41
+ end
42
+
43
+ def visited_relation(relation)
44
+ return unless @discovering
45
+ @known_relations << Table.new(relation)
46
+ end
47
+
48
+ def visited_handled_relation(relation)
49
+ @handled_relations << Table.new(relation)
50
+ end
51
+
52
+ def unhandled_relations
53
+ known_relations.uniq - handled_relations
54
+ end
55
+ end
56
+
4
57
  class ArelTenantVisitor < Arel::Visitors::DepthFirst
5
58
  def initialize(arel)
6
59
  super(Proc.new {})
7
- @tenant_relations = {}
8
- @existing_tenant_relations = {}
9
- @outer_joins_by_table_name = {}
10
60
  @statement_node_id = nil
11
61
 
62
+ @contexts = []
63
+ @current_context = nil
12
64
  accept(arel.ast)
13
65
  end
14
66
 
15
- def tenant_relations
16
- @tenant_relations
67
+ attr_reader :contexts
68
+
69
+ def visit_Arel_Attributes_Attribute(*args)
70
+ return if @current_context.nil?
71
+ super(*args)
17
72
  end
18
73
 
19
- def existing_tenant_relations
20
- @existing_tenant_relations
74
+ def visit_Arel_Nodes_Equality(o, *args)
75
+ if o.left.is_a?(Arel::Attributes::Attribute)
76
+ table_name = o.left.relation.table_name
77
+ model = MultiTenant.multi_tenant_model_for_table(table_name)
78
+ @current_context.visited_handled_relation(o.left.relation) if model.present? && o.left.name == model.partition_key
79
+ end
80
+ super(o, *args)
21
81
  end
22
82
 
23
- def outer_joins_by_table_name
24
- @outer_joins_by_table_name
83
+ def visit_MultiTenant_TenantEnforcementClause(o, *)
84
+ @current_context.visited_handled_relation(o.tenant_attribute.relation)
25
85
  end
26
86
 
27
87
  def visit_Arel_Table(o, _collector = nil)
28
- @tenant_relations[@statement_node_id] ||= []
29
- @tenant_relations[@statement_node_id] << o if tenant_relation?(o.table_name)
88
+ @current_context.visited_relation(o) if tenant_relation?(o.table_name)
30
89
  end
31
90
  alias :visit_Arel_Nodes_TableAlias :visit_Arel_Table
32
91
 
33
- def visit_Arel_Nodes_SelectStatement(o, _collector = nil)
34
- @statement_node_id = o.object_id
35
- visit o.cores
36
- visit o.orders
37
- visit o.limit
38
- visit o.lock
39
- visit o.offset
40
- end
41
-
42
- def visit_Arel_Nodes_Equality(o, _collector = nil)
43
- if o.left.is_a?(Arel::Attributes::Attribute)
44
- table_name = o.left.relation.table_name
45
- model = MultiTenant.multi_tenant_model_for_table(table_name)
46
- @existing_tenant_relations[@statement_node_id] ||= []
47
- @existing_tenant_relations[@statement_node_id] << o.left.relation if model.present? && o.left.name == model.partition_key
92
+ def visit_Arel_Nodes_SelectCore(o, *args)
93
+ nest_context(o) do
94
+ @current_context.discover_relations do
95
+ visit o.source
96
+ end
97
+ visit o.wheres
98
+ visit o.groups
99
+ visit o.windows
100
+ if defined?(o.having)
101
+ visit o.having
102
+ else
103
+ visit o.havings
104
+ end
48
105
  end
49
106
  end
50
107
 
51
108
  def visit_Arel_Nodes_OuterJoin(o, collector = nil)
52
- if o.left.is_a?(Arel::Nodes::TableAlias) || o.left.is_a?(Arel::Table)
53
- @outer_joins_by_table_name[@statement_node_id] ||= {}
54
- @outer_joins_by_table_name[@statement_node_id][o.left.name] = o
109
+ nest_context(o) do
110
+ @current_context.discover_relations do
111
+ visit o.left
112
+ visit o.right
113
+ end
55
114
  end
56
- visit o.left
57
- visit o.right
58
115
  end
59
116
  alias :visit_Arel_Nodes_FullOuterJoin :visit_Arel_Nodes_OuterJoin
60
117
  alias :visit_Arel_Nodes_RightOuterJoin :visit_Arel_Nodes_OuterJoin
@@ -64,9 +121,73 @@ module MultiTenant
64
121
  def tenant_relation?(table_name)
65
122
  MultiTenant.multi_tenant_model_for_table(table_name).present?
66
123
  end
124
+
125
+ DISPATCH = Hash.new do |hash, klass|
126
+ hash[klass] = "visit_#{(klass.name || '').gsub('::', '_')}"
127
+ end
128
+
129
+ def dispatch
130
+ DISPATCH
131
+ end
132
+
133
+ def get_dispatch_cache
134
+ dispatch
135
+ end
136
+
137
+ def nest_context(o)
138
+ old_context = @current_context
139
+ @current_context = Context.new(o)
140
+ @contexts << @current_context
141
+
142
+ yield
143
+
144
+ @current_context = old_context
145
+ end
146
+ end
147
+
148
+ class TenantEnforcementClause < Arel::Nodes::Node
149
+ attr_reader :tenant_attribute
150
+ def initialize(tenant_attribute)
151
+ @tenant_attribute = tenant_attribute
152
+ end
153
+
154
+ def to_s; to_sql; end
155
+ def to_str; to_sql; end
156
+
157
+ def to_sql(*)
158
+ if MultiTenant.current_tenant_id
159
+ tenant_arel.to_sql
160
+ else
161
+ '1=1'
162
+ end
163
+ end
164
+
165
+ private
166
+
167
+ def tenant_arel
168
+ if defined?(Arel::Nodes::Quoted)
169
+ @tenant_attribute.eq(Arel::Nodes::Quoted.new(MultiTenant.current_tenant_id))
170
+ else
171
+ @tenant_attribute.eq(MultiTenant.current_tenant_id)
172
+ end
173
+ end
174
+ end
175
+
176
+ module TenantValueVisitor
177
+ if ActiveRecord::VERSION::MAJOR > 4 || (ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR >= 2)
178
+ def visit_MultiTenant_TenantEnforcementClause(o, collector)
179
+ collector << o
180
+ end
181
+ else
182
+ def visit_MultiTenant_TenantEnforcementClause(o, a = nil)
183
+ o
184
+ end
185
+ end
67
186
  end
68
187
  end
69
188
 
189
+ Arel::Visitors::ToSql.include(MultiTenant::TenantValueVisitor)
190
+
70
191
  require 'active_record/relation'
71
192
  module ActiveRecord
72
193
  module QueryMethods
@@ -76,36 +197,23 @@ module ActiveRecord
76
197
 
77
198
  if MultiTenant.current_tenant_id && !MultiTenant.with_write_only_mode_enabled?
78
199
  visitor = MultiTenant::ArelTenantVisitor.new(arel)
79
- visitor.tenant_relations.each do |statement_node_id, relations|
80
- # Process every level of the statement separately, so we don't mix subselects
81
- known_relations = visitor.existing_tenant_relations[statement_node_id] || []
82
- outer_joins_by_table_name = visitor.outer_joins_by_table_name[statement_node_id] || {}
83
-
84
- relations.each do |relation|
85
- model = MultiTenant.multi_tenant_model_for_table(relation.table_name)
86
- next unless model.present?
87
- next if known_relations.map(&:name).include?(relation.name)
88
-
89
- tenant_relation = known_relations.reject { |r| outer_joins_by_table_name.key?(r.name) }.first
90
- tenant_value = if tenant_relation.present?
91
- known_model = MultiTenant.multi_tenant_model_for_table(tenant_relation.table_name)
92
- tenant_relation[known_model.partition_key]
93
- else
94
- MultiTenant.current_tenant_id
95
- end
96
-
97
- known_relations << relation
98
-
99
- outer_join = outer_joins_by_table_name[relation.name]
100
- if outer_join
101
- outer_join.right.expr = Arel::Nodes::And.new([outer_join.right.expr, relation[model.partition_key].eq(tenant_value)])
102
- else
103
- ctx = arel.ast.cores.last
104
- if ctx.wheres.size == 1
105
- ctx.wheres = [Arel::Nodes::And.new([ctx.wheres.first, relation[model.partition_key].eq(tenant_value)])]
200
+ visitor.contexts.each do |context|
201
+ node = context.arel_node
202
+ context.unhandled_relations.each do |relation|
203
+ model = MultiTenant.multi_tenant_model_for_table(relation.arel_table.table_name)
204
+ enforcement_clause = MultiTenant::TenantEnforcementClause.new(relation.arel_table[model.partition_key])
205
+
206
+ case node
207
+ when Arel::Nodes::Join #Arel::Nodes::OuterJoin, Arel::Nodes::RightOuterJoin, Arel::Nodes::FullOuterJoin
208
+ node.right.expr = node.right.expr.and(enforcement_clause)
209
+ when Arel::Nodes::SelectCore
210
+ if node.wheres.empty?
211
+ node.wheres = [enforcement_clause]
106
212
  else
107
- arel = arel.where(relation[model.partition_key].eq(tenant_value))
213
+ node.wheres[0] = enforcement_clause.and(node.wheres[0])
108
214
  end
215
+ else
216
+ raise "UnknownContext"
109
217
  end
110
218
  end
111
219
  end
@@ -1,3 +1,3 @@
1
1
  module MultiTenant
2
- VERSION = '0.7.0'
2
+ VERSION = '0.8.0'
3
3
  end
@@ -9,11 +9,7 @@ describe "Controller Extensions", type: :controller do
9
9
  include Rails.application.routes.url_helpers
10
10
  set_current_tenant_through_filter
11
11
 
12
- if ActionPack::VERSION::MAJOR < 4
13
- before_filter :your_method_that_finds_the_current_tenant
14
- else
15
- before_action :your_method_that_finds_the_current_tenant
16
- end
12
+ before_action :your_method_that_finds_the_current_tenant
17
13
 
18
14
  def your_method_that_finds_the_current_tenant
19
15
  current_account = Account.new
@@ -287,8 +287,71 @@ describe MultiTenant do
287
287
  end
288
288
  end
289
289
 
290
- if ActiveRecord::VERSION::MAJOR > 4 || (ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR > 0)
291
- # Reflection
290
+ it 'does not cache tenancy in associations' do
291
+ account1 = Account.create! name: 'test1'
292
+ account2 = Account.create! name: 'test2'
293
+
294
+ MultiTenant.with(account1) do
295
+ project1 = Project.create! name: 'something1'
296
+ task1 = Task.create! name: 'task1', project: project1
297
+ subtask1 = SubTask.create! task: task1
298
+
299
+ expect(subtask1.project).to be_present
300
+ end
301
+
302
+ MultiTenant.with(account2) do
303
+ project2 = Project.create! name: 'something2'
304
+ task2 = Task.create! name: 'task2', project: project2
305
+ subtask2 = SubTask.create! task: task2
306
+
307
+ expect(subtask2.project).to be_present
308
+ end
309
+ end
310
+
311
+ it "applies the team_id conditions in the where clause" do
312
+ expected_sql = <<-sql
313
+ SELECT "sub_tasks".* FROM "sub_tasks" INNER JOIN "tasks" ON "sub_tasks"."task_id" = "tasks"."id" WHERE "tasks"."account_id" = 1 AND "sub_tasks"."account_id" = 1 AND "tasks"."project_id" = 1
314
+ sql
315
+ account1 = Account.create! name: 'Account 1'
316
+
317
+ MultiTenant.with(account1) do
318
+ project1 = Project.create! name: 'Project 1'
319
+ task1 = Task.create! name: 'Task 1', project: project1
320
+ subtask1 = SubTask.create! task: task1
321
+
322
+ expect(project1.sub_tasks.to_sql).to eq(expected_sql.strip)
323
+ expect(project1.sub_tasks).to include(subtask1)
324
+ end
325
+ end
326
+
327
+ # Versions earlier than 4.2 pass an arel object to find_by_sql(...) and it would make
328
+ # this test unnecesssarily complicated to support that
329
+ if ActiveRecord::VERSION::MAJOR > 4 || (ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR >= 2)
330
+ it "only applies clauses when a tenant is set" do
331
+ account = Account.create! name: 'Account 1'
332
+ project = Project.create! name: 'Project 1', account: account
333
+ project2 = Project.create! name: 'Project 2', account: Account.create!(name: 'Account2')
334
+
335
+ MultiTenant.with(account) do
336
+ expected_sql = <<-sql.strip
337
+ SELECT "projects".* FROM "projects" WHERE "projects"."account_id" = #{account.id} AND "projects"."id" = #{project.id} LIMIT 1
338
+ sql
339
+ expect(Project).to receive(:find_by_sql).with(expected_sql, any_args).and_call_original
340
+ expect(Project.find(project.id)).to eq(project)
341
+ end
342
+
343
+ MultiTenant.with(nil) do
344
+ expected_sql = <<-sql.strip
345
+ SELECT "projects".* FROM "projects" WHERE 1=1 AND "projects"."id" = #{project2.id} LIMIT 1
346
+ sql
347
+ expect(Project).to receive(:find_by_sql).with(expected_sql, any_args).and_call_original
348
+ expect(Project.find(project2.id)).to eq(project2)
349
+ end
350
+ end
351
+ end
352
+
353
+ # Versions earlier than 4.1 have a different behaviour regarding unsaved associations
354
+ if ActiveRecord::VERSION::MAJOR > 4 || (ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR >= 1)
292
355
  describe 'with unsaved association' do
293
356
  before do
294
357
  @account = Account.create!(name: 'reflection tenant')
@@ -8,4 +8,12 @@ describe MultiTenant, 'Record finding' do
8
8
  expect(Project.find(project.id)).to be_present
9
9
  end
10
10
  end
11
+
12
+ it 'supports UUIDs' do
13
+ organization = Organization.create! name: 'test'
14
+ uuid_record = organization.uuid_records.create! description: 'something'
15
+ MultiTenant.with(organization) do
16
+ expect(UuidRecord.find(uuid_record.id)).to be_present
17
+ end
18
+ end
11
19
  end
data/spec/schema.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  # Resets the database, except when we are only running a specific spec
2
2
  ARGV.grep(/\w+_spec\.rb/).empty? && ActiveRecord::Schema.define(version: 1) do
3
+ enable_extension_on_all_nodes 'uuid-ossp'
4
+ enable_extension_on_all_nodes 'pgcrypto'
5
+
3
6
  create_table :accounts, force: true do |t|
4
7
  t.column :name, :string
5
8
  t.column :subdomain, :string
@@ -66,6 +69,15 @@ ARGV.grep(/\w+_spec\.rb/).empty? && ActiveRecord::Schema.define(version: 1) do
66
69
  t.column :name, :string
67
70
  end
68
71
 
72
+ create_table :organizations, force: true, id: :uuid do |t|
73
+ t.column :name, :string
74
+ end
75
+
76
+ create_table :uuid_records, force: true, partition_key: :organization_id do |t|
77
+ t.column :organization_id, :uuid
78
+ t.column :description, :string
79
+ end
80
+
69
81
  create_distributed_table :accounts, :id
70
82
  create_distributed_table :projects, :account_id
71
83
  create_distributed_table :managers, :account_id
@@ -76,6 +88,7 @@ ARGV.grep(/\w+_spec\.rb/).empty? && ActiveRecord::Schema.define(version: 1) do
76
88
  create_distributed_table :comments, :account_id
77
89
  create_distributed_table :partition_key_not_model_tasks, :non_model_id
78
90
  create_distributed_table :subclass_tasks, :non_model_id
91
+ create_distributed_table :uuid_records, :organization_id
79
92
  end
80
93
 
81
94
  class Account < ActiveRecord::Base
@@ -90,11 +103,7 @@ class Project < ActiveRecord::Base
90
103
  has_many :tasks
91
104
  has_many :sub_tasks, through: :tasks
92
105
 
93
- if ActiveRecord::VERSION::MAJOR < 4
94
- validates_uniqueness_of :name, scope: [:account_id]
95
- else
96
- validates_uniqueness_of :name, scope: [:account]
97
- end
106
+ validates_uniqueness_of :name, scope: [:account]
98
107
  end
99
108
 
100
109
  class Manager < ActiveRecord::Base
@@ -107,8 +116,6 @@ class Task < ActiveRecord::Base
107
116
  belongs_to :project
108
117
  has_many :sub_tasks
109
118
 
110
- default_scope -> { where(completed: nil).order('name') }
111
-
112
119
  validates_uniqueness_of :name
113
120
  end
114
121
 
@@ -133,11 +140,7 @@ end
133
140
  class CustomPartitionKeyTask < ActiveRecord::Base
134
141
  multi_tenant :account, partition_key: 'accountID'
135
142
 
136
- if ActiveRecord::VERSION::MAJOR < 4
137
- validates_uniqueness_of :name, scope: [:accountID]
138
- else
139
- validates_uniqueness_of :name, scope: [:account]
140
- end
143
+ validates_uniqueness_of :name, scope: [:account]
141
144
  end
142
145
 
143
146
  class PartitionKeyNotModelTask < ActiveRecord::Base
@@ -160,3 +163,11 @@ class Comment < ActiveRecord::Base
160
163
  belongs_to :task, -> { where(comments: { commentable_type: 'Task' }) }, foreign_key: 'commentable_id'
161
164
  end
162
165
  end
166
+
167
+ class Organization < ActiveRecord::Base
168
+ has_many :uuid_records
169
+ end
170
+
171
+ class UuidRecord < ActiveRecord::Base
172
+ multi_tenant :organization
173
+ end
data/spec/spec_helper.rb CHANGED
@@ -7,6 +7,9 @@ require 'rspec/rails'
7
7
 
8
8
  require 'activerecord-multi-tenant'
9
9
 
10
+ require 'bundler'
11
+ Bundler.require(:default, :development)
12
+
10
13
  dbconfig = YAML::load(IO.read(File.join(File.dirname(__FILE__), 'database.yml')))
11
14
  ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
12
15
  ActiveRecord::Base.establish_connection(dbconfig['test'])
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.7.0
4
+ version: 0.8.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-07-19 00:00:00.000000000 Z
11
+ date: 2017-08-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: request_store
@@ -122,6 +122,34 @@ dependencies:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pry
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: pry-byebug
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
125
153
  description: ''
126
154
  email: engage@citusdata.com
127
155
  executables: []
@@ -140,8 +168,6 @@ files:
140
168
  - docker-compose.yml
141
169
  - gemfiles/active_record_5.1.gemfile
142
170
  - gemfiles/active_record_5.1.gemfile.lock
143
- - gemfiles/rails_3.2.gemfile
144
- - gemfiles/rails_3.2.gemfile.lock
145
171
  - gemfiles/rails_4.0.gemfile
146
172
  - gemfiles/rails_4.0.gemfile.lock
147
173
  - gemfiles/rails_4.1.gemfile
@@ -1,9 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "appraisal"
6
- gem "test-unit", "~> 3.0"
7
- gem "rails", "3.2.22.5"
8
-
9
- gemspec :path => "../"
@@ -1,145 +0,0 @@
1
- PATH
2
- remote: ..
3
- specs:
4
- activerecord-multi-tenant (0.7.0)
5
- rails (>= 3.1)
6
- request_store (>= 1.0.5)
7
-
8
- GEM
9
- remote: https://rubygems.org/
10
- specs:
11
- actionmailer (3.2.22.5)
12
- actionpack (= 3.2.22.5)
13
- mail (~> 2.5.4)
14
- actionpack (3.2.22.5)
15
- activemodel (= 3.2.22.5)
16
- activesupport (= 3.2.22.5)
17
- builder (~> 3.0.0)
18
- erubis (~> 2.7.0)
19
- journey (~> 1.0.4)
20
- rack (~> 1.4.5)
21
- rack-cache (~> 1.2)
22
- rack-test (~> 0.6.1)
23
- sprockets (~> 2.2.1)
24
- activemodel (3.2.22.5)
25
- activesupport (= 3.2.22.5)
26
- builder (~> 3.0.0)
27
- activerecord (3.2.22.5)
28
- activemodel (= 3.2.22.5)
29
- activesupport (= 3.2.22.5)
30
- arel (~> 3.0.2)
31
- tzinfo (~> 0.3.29)
32
- activeresource (3.2.22.5)
33
- activemodel (= 3.2.22.5)
34
- activesupport (= 3.2.22.5)
35
- activesupport (3.2.22.5)
36
- i18n (~> 0.6, >= 0.6.4)
37
- multi_json (~> 1.0)
38
- appraisal (2.1.0)
39
- bundler
40
- rake
41
- thor (>= 0.14.0)
42
- arel (3.0.3)
43
- builder (3.0.4)
44
- concurrent-ruby (1.0.5)
45
- connection_pool (2.2.1)
46
- diff-lcs (1.3)
47
- erubis (2.7.0)
48
- hike (1.2.3)
49
- i18n (0.8.1)
50
- journey (1.0.4)
51
- json (1.8.6)
52
- mail (2.5.4)
53
- mime-types (~> 1.16)
54
- treetop (~> 1.4.8)
55
- mime-types (1.25.1)
56
- multi_json (1.12.1)
57
- pg (0.20.0)
58
- polyglot (0.3.5)
59
- power_assert (1.0.1)
60
- rack (1.4.7)
61
- rack-cache (1.7.0)
62
- rack (>= 0.4)
63
- rack-protection (2.0.0)
64
- rack
65
- rack-ssl (1.3.4)
66
- rack
67
- rack-test (0.6.3)
68
- rack (>= 1.0)
69
- rails (3.2.22.5)
70
- actionmailer (= 3.2.22.5)
71
- actionpack (= 3.2.22.5)
72
- activerecord (= 3.2.22.5)
73
- activeresource (= 3.2.22.5)
74
- activesupport (= 3.2.22.5)
75
- bundler (~> 1.0)
76
- railties (= 3.2.22.5)
77
- railties (3.2.22.5)
78
- actionpack (= 3.2.22.5)
79
- activesupport (= 3.2.22.5)
80
- rack-ssl (~> 1.3.2)
81
- rake (>= 0.8.7)
82
- rdoc (~> 3.4)
83
- thor (>= 0.14.6, < 2.0)
84
- rake (12.0.0)
85
- rdoc (3.12.2)
86
- json (~> 1.4)
87
- redis (3.3.3)
88
- request_store (1.3.2)
89
- rspec (3.5.0)
90
- rspec-core (~> 3.5.0)
91
- rspec-expectations (~> 3.5.0)
92
- rspec-mocks (~> 3.5.0)
93
- rspec-core (3.5.4)
94
- rspec-support (~> 3.5.0)
95
- rspec-expectations (3.5.0)
96
- diff-lcs (>= 1.2.0, < 2.0)
97
- rspec-support (~> 3.5.0)
98
- rspec-mocks (3.5.0)
99
- diff-lcs (>= 1.2.0, < 2.0)
100
- rspec-support (~> 3.5.0)
101
- rspec-rails (3.5.2)
102
- actionpack (>= 3.0)
103
- activesupport (>= 3.0)
104
- railties (>= 3.0)
105
- rspec-core (~> 3.5.0)
106
- rspec-expectations (~> 3.5.0)
107
- rspec-mocks (~> 3.5.0)
108
- rspec-support (~> 3.5.0)
109
- rspec-support (3.5.0)
110
- sidekiq (5.0.3)
111
- concurrent-ruby (~> 1.0)
112
- connection_pool (~> 2.2, >= 2.2.0)
113
- rack-protection (>= 1.5.0)
114
- redis (~> 3.3, >= 3.3.3)
115
- sprockets (2.2.3)
116
- hike (~> 1.2)
117
- multi_json (~> 1.0)
118
- rack (~> 1.0)
119
- tilt (~> 1.1, != 1.3.0)
120
- test-unit (3.2.3)
121
- power_assert
122
- thor (0.19.4)
123
- tilt (1.4.1)
124
- treetop (1.4.15)
125
- polyglot
126
- polyglot (>= 0.3.1)
127
- tzinfo (0.3.53)
128
-
129
- PLATFORMS
130
- ruby
131
-
132
- DEPENDENCIES
133
- activerecord-multi-tenant!
134
- appraisal
135
- pg
136
- rails (= 3.2.22.5)
137
- rake
138
- rspec (>= 3.0)
139
- rspec-rails
140
- sidekiq
141
- test-unit (~> 3.0)
142
- thor
143
-
144
- BUNDLED WITH
145
- 1.15.1