activerecord-multi-tenant 0.3.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +12 -6
- data/CHANGELOG.md +9 -0
- data/Gemfile.lock +1 -1
- data/README.md +5 -1
- data/gemfiles/rails_3.2.gemfile.lock +3 -3
- data/gemfiles/rails_4.0.gemfile.lock +5 -5
- data/gemfiles/rails_4.1.gemfile.lock +5 -5
- data/gemfiles/rails_4.2.gemfile.lock +6 -6
- data/gemfiles/rails_5.0.gemfile.lock +6 -6
- data/lib/activerecord-multi-tenant/model_extensions.rb +44 -55
- data/lib/activerecord-multi-tenant/multi_tenant.rb +17 -38
- data/lib/activerecord-multi-tenant/version.rb +1 -1
- data/spec/activerecord-multi-tenant/model_extensions_spec.rb +44 -1
- data/spec/schema.rb +14 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1c30e8a7b37b923b66c5043fb73f3c9e776cea85
|
|
4
|
+
data.tar.gz: 63a2537270723a549cab19a3a75e5c9eaee4fdac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5fe0741bcd4279f6852aa8fc64a25921bb8fb9953cbce564a81ec611602bc0321ca6ededc84ade5053795d64c3e1d3b2a055c6e5c19aa54003ea18c65aa5651e
|
|
7
|
+
data.tar.gz: f59653a116f517ceafdd91677518113299bf86b281259a0816c66299c3ac5b2fa8ec2d347078034ace050adb21aeb12c40ff476e36fe8f9e238f695d99c1f3ad
|
data/.travis.yml
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
+
sudo: required
|
|
2
|
+
cache: bundler
|
|
3
|
+
|
|
1
4
|
language: ruby
|
|
2
5
|
|
|
3
6
|
rvm:
|
|
4
7
|
- 2.1
|
|
5
|
-
- 2.2.6
|
|
6
8
|
- 2.3.3
|
|
7
|
-
- 2.4
|
|
8
|
-
|
|
9
|
-
script: "bundle exec rake spec"
|
|
10
9
|
|
|
11
10
|
gemfile:
|
|
12
11
|
- gemfiles/rails_3.2.gemfile
|
|
@@ -21,5 +20,12 @@ matrix:
|
|
|
21
20
|
- gemfile: gemfiles/rails_5.0.gemfile
|
|
22
21
|
rvm: 2.1
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
services:
|
|
24
|
+
- docker
|
|
25
|
+
|
|
26
|
+
before_install:
|
|
27
|
+
- docker-compose up -d
|
|
28
|
+
- sleep 5 # wait for postgres to become available
|
|
29
|
+
|
|
30
|
+
script:
|
|
31
|
+
- bundle exec rake spec
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 2017-03-22
|
|
4
|
+
|
|
5
|
+
* Infer multi_tenant setting from parent classes [@webandtech](https://github.com/webandtech) [#6](https://github.com/citusdata/activerecord-multi-tenant/pull/6)
|
|
6
|
+
* Remove use of global tenant klass variable [@webandtech](https://github.com/webandtech) [#6](https://github.com/citusdata/activerecord-multi-tenant/pull/6)
|
|
7
|
+
* Support passing ID values to MultiTenant.with directly [@webandtech](https://github.com/webandtech) [#6](https://github.com/citusdata/activerecord-multi-tenant/pull/6)
|
|
8
|
+
* This effectively deprecates with_id, but we'll keep it around for now
|
|
9
|
+
* Remove unnecessary validation for invalid belongs_to association
|
|
10
|
+
|
|
11
|
+
|
|
3
12
|
## 0.3.4 2017-02-22
|
|
4
13
|
|
|
5
14
|
* Expand with_lock workaround to cover lock! as well
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -69,7 +69,11 @@ end
|
|
|
69
69
|
|
|
70
70
|
* **What if I have a table that doesn't relate to my tenant?** (e.g. templates that are the same in every account)
|
|
71
71
|
|
|
72
|
-
We recommend not using activerecord-multi-tenant on these tables. In case only some records in a table are not associated to a tenant (i.e. your templates are in the same table as actual objects), we recommend setting the tenant_id to 0, and then using MultiTenant.
|
|
72
|
+
We recommend not using activerecord-multi-tenant on these tables. In case only some records in a table are not associated to a tenant (i.e. your templates are in the same table as actual objects), we recommend setting the tenant_id to 0, and then using MultiTenant.with(0) to access these objects.
|
|
73
|
+
|
|
74
|
+
* **What if my tenant model is not defined in my application?**
|
|
75
|
+
|
|
76
|
+
The tenant model does not have to be defined. Use the gem as if the model was present. `MultiTenant.with` accepts either a tenant id or model instance.
|
|
73
77
|
|
|
74
78
|
## Credits
|
|
75
79
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ../
|
|
3
3
|
specs:
|
|
4
|
-
activerecord-multi-tenant (0.
|
|
4
|
+
activerecord-multi-tenant (0.4.0)
|
|
5
5
|
rails (>= 3.1)
|
|
6
6
|
request_store (>= 1.0.5)
|
|
7
7
|
|
|
@@ -45,7 +45,7 @@ GEM
|
|
|
45
45
|
diff-lcs (1.3)
|
|
46
46
|
erubis (2.7.0)
|
|
47
47
|
hike (1.2.3)
|
|
48
|
-
i18n (0.8.
|
|
48
|
+
i18n (0.8.1)
|
|
49
49
|
journey (1.0.4)
|
|
50
50
|
json (1.8.6)
|
|
51
51
|
mail (2.5.4)
|
|
@@ -53,7 +53,7 @@ GEM
|
|
|
53
53
|
treetop (~> 1.4.8)
|
|
54
54
|
mime-types (1.25.1)
|
|
55
55
|
multi_json (1.12.1)
|
|
56
|
-
pg (0.
|
|
56
|
+
pg (0.20.0)
|
|
57
57
|
polyglot (0.3.5)
|
|
58
58
|
power_assert (1.0.1)
|
|
59
59
|
rack (1.4.7)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ../
|
|
3
3
|
specs:
|
|
4
|
-
activerecord-multi-tenant (0.
|
|
4
|
+
activerecord-multi-tenant (0.4.0)
|
|
5
5
|
rails (>= 3.1)
|
|
6
6
|
request_store (>= 1.0.5)
|
|
7
7
|
|
|
@@ -38,11 +38,11 @@ GEM
|
|
|
38
38
|
thor (>= 0.14.0)
|
|
39
39
|
arel (4.0.2)
|
|
40
40
|
builder (3.1.4)
|
|
41
|
-
concurrent-ruby (1.0.
|
|
41
|
+
concurrent-ruby (1.0.5)
|
|
42
42
|
database_cleaner (1.3.0)
|
|
43
43
|
diff-lcs (1.3)
|
|
44
44
|
erubis (2.7.0)
|
|
45
|
-
i18n (0.8.
|
|
45
|
+
i18n (0.8.1)
|
|
46
46
|
mail (2.6.4)
|
|
47
47
|
mime-types (>= 1.16, < 4)
|
|
48
48
|
mime-types (3.1)
|
|
@@ -50,7 +50,7 @@ GEM
|
|
|
50
50
|
mime-types-data (3.2016.0521)
|
|
51
51
|
minitest (4.7.5)
|
|
52
52
|
multi_json (1.12.1)
|
|
53
|
-
pg (0.
|
|
53
|
+
pg (0.20.0)
|
|
54
54
|
rack (1.5.5)
|
|
55
55
|
rack-test (0.6.3)
|
|
56
56
|
rack (>= 1.0)
|
|
@@ -98,7 +98,7 @@ GEM
|
|
|
98
98
|
activesupport (>= 3.0)
|
|
99
99
|
sprockets (>= 2.8, < 4.0)
|
|
100
100
|
thor (0.19.4)
|
|
101
|
-
thread_safe (0.3.
|
|
101
|
+
thread_safe (0.3.6)
|
|
102
102
|
tzinfo (0.3.52)
|
|
103
103
|
|
|
104
104
|
PLATFORMS
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ../
|
|
3
3
|
specs:
|
|
4
|
-
activerecord-multi-tenant (0.
|
|
4
|
+
activerecord-multi-tenant (0.4.0)
|
|
5
5
|
rails (>= 3.1)
|
|
6
6
|
request_store (>= 1.0.5)
|
|
7
7
|
|
|
@@ -40,11 +40,11 @@ GEM
|
|
|
40
40
|
thor (>= 0.14.0)
|
|
41
41
|
arel (5.0.1.20140414130214)
|
|
42
42
|
builder (3.2.3)
|
|
43
|
-
concurrent-ruby (1.0.
|
|
43
|
+
concurrent-ruby (1.0.5)
|
|
44
44
|
database_cleaner (1.3.0)
|
|
45
45
|
diff-lcs (1.3)
|
|
46
46
|
erubis (2.7.0)
|
|
47
|
-
i18n (0.8.
|
|
47
|
+
i18n (0.8.1)
|
|
48
48
|
json (1.8.6)
|
|
49
49
|
mail (2.6.4)
|
|
50
50
|
mime-types (>= 1.16, < 4)
|
|
@@ -52,7 +52,7 @@ GEM
|
|
|
52
52
|
mime-types-data (~> 3.2015)
|
|
53
53
|
mime-types-data (3.2016.0521)
|
|
54
54
|
minitest (5.10.1)
|
|
55
|
-
pg (0.
|
|
55
|
+
pg (0.20.0)
|
|
56
56
|
rack (1.5.5)
|
|
57
57
|
rack-test (0.6.3)
|
|
58
58
|
rack (>= 1.0)
|
|
@@ -102,7 +102,7 @@ GEM
|
|
|
102
102
|
activesupport (>= 3.0)
|
|
103
103
|
sprockets (>= 2.8, < 4.0)
|
|
104
104
|
thor (0.19.4)
|
|
105
|
-
thread_safe (0.3.
|
|
105
|
+
thread_safe (0.3.6)
|
|
106
106
|
tzinfo (1.2.2)
|
|
107
107
|
thread_safe (~> 0.1)
|
|
108
108
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ../
|
|
3
3
|
specs:
|
|
4
|
-
activerecord-multi-tenant (0.
|
|
4
|
+
activerecord-multi-tenant (0.4.0)
|
|
5
5
|
rails (>= 3.1)
|
|
6
6
|
request_store (>= 1.0.5)
|
|
7
7
|
|
|
@@ -48,13 +48,13 @@ GEM
|
|
|
48
48
|
thor (>= 0.14.0)
|
|
49
49
|
arel (6.0.4)
|
|
50
50
|
builder (3.2.3)
|
|
51
|
-
concurrent-ruby (1.0.
|
|
51
|
+
concurrent-ruby (1.0.5)
|
|
52
52
|
database_cleaner (1.3.0)
|
|
53
53
|
diff-lcs (1.3)
|
|
54
54
|
erubis (2.7.0)
|
|
55
55
|
globalid (0.3.7)
|
|
56
56
|
activesupport (>= 4.1.0)
|
|
57
|
-
i18n (0.8.
|
|
57
|
+
i18n (0.8.1)
|
|
58
58
|
loofah (2.0.3)
|
|
59
59
|
nokogiri (>= 1.5.9)
|
|
60
60
|
mail (2.6.4)
|
|
@@ -64,9 +64,9 @@ GEM
|
|
|
64
64
|
mime-types-data (3.2016.0521)
|
|
65
65
|
mini_portile2 (2.1.0)
|
|
66
66
|
minitest (5.10.1)
|
|
67
|
-
nokogiri (1.7.
|
|
67
|
+
nokogiri (1.7.1)
|
|
68
68
|
mini_portile2 (~> 2.1.0)
|
|
69
|
-
pg (0.
|
|
69
|
+
pg (0.20.0)
|
|
70
70
|
rack (1.6.5)
|
|
71
71
|
rack-test (0.6.3)
|
|
72
72
|
rack (>= 1.0)
|
|
@@ -125,7 +125,7 @@ GEM
|
|
|
125
125
|
activesupport (>= 4.0)
|
|
126
126
|
sprockets (>= 3.0.0)
|
|
127
127
|
thor (0.19.4)
|
|
128
|
-
thread_safe (0.3.
|
|
128
|
+
thread_safe (0.3.6)
|
|
129
129
|
tzinfo (1.2.2)
|
|
130
130
|
thread_safe (~> 0.1)
|
|
131
131
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ../
|
|
3
3
|
specs:
|
|
4
|
-
activerecord-multi-tenant (0.
|
|
4
|
+
activerecord-multi-tenant (0.4.0)
|
|
5
5
|
rails (>= 3.1)
|
|
6
6
|
request_store (>= 1.0.5)
|
|
7
7
|
|
|
@@ -51,13 +51,13 @@ GEM
|
|
|
51
51
|
thor (>= 0.14.0)
|
|
52
52
|
arel (7.1.4)
|
|
53
53
|
builder (3.2.3)
|
|
54
|
-
concurrent-ruby (1.0.
|
|
54
|
+
concurrent-ruby (1.0.5)
|
|
55
55
|
database_cleaner (1.3.0)
|
|
56
56
|
diff-lcs (1.3)
|
|
57
57
|
erubis (2.7.0)
|
|
58
58
|
globalid (0.3.7)
|
|
59
59
|
activesupport (>= 4.1.0)
|
|
60
|
-
i18n (0.8.
|
|
60
|
+
i18n (0.8.1)
|
|
61
61
|
loofah (2.0.3)
|
|
62
62
|
nokogiri (>= 1.5.9)
|
|
63
63
|
mail (2.6.4)
|
|
@@ -69,9 +69,9 @@ GEM
|
|
|
69
69
|
mini_portile2 (2.1.0)
|
|
70
70
|
minitest (5.10.1)
|
|
71
71
|
nio4r (1.2.1)
|
|
72
|
-
nokogiri (1.7.
|
|
72
|
+
nokogiri (1.7.1)
|
|
73
73
|
mini_portile2 (~> 2.1.0)
|
|
74
|
-
pg (0.
|
|
74
|
+
pg (0.20.0)
|
|
75
75
|
rack (2.0.1)
|
|
76
76
|
rack-test (0.6.3)
|
|
77
77
|
rack (>= 1.0)
|
|
@@ -129,7 +129,7 @@ GEM
|
|
|
129
129
|
activesupport (>= 4.0)
|
|
130
130
|
sprockets (>= 3.0.0)
|
|
131
131
|
thor (0.19.4)
|
|
132
|
-
thread_safe (0.3.
|
|
132
|
+
thread_safe (0.3.6)
|
|
133
133
|
tzinfo (1.2.2)
|
|
134
134
|
thread_safe (~> 0.1)
|
|
135
135
|
websocket-driver (0.6.5)
|
|
@@ -1,39 +1,45 @@
|
|
|
1
1
|
module MultiTenant
|
|
2
2
|
module ModelExtensionsClassMethods
|
|
3
|
-
|
|
4
|
-
# Workaround for https://github.com/citusdata/citus/issues/687
|
|
5
|
-
if to_s.underscore.to_sym == tenant
|
|
6
|
-
before_create -> { self.id ||= self.class.connection.select_value("SELECT nextval('" + [self.class.table_name, self.class.primary_key, 'seq'].join('_') + "'::regclass)") }
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
# Typically we don't need to run on the tenant model itself
|
|
10
|
-
if to_s.underscore.to_sym != tenant
|
|
11
|
-
MultiTenant.set_tenant_klass(tenant)
|
|
3
|
+
DEFAULT_ID_FIELD = 'id'.freeze
|
|
12
4
|
|
|
5
|
+
def multi_tenant(tenant_name, options = {})
|
|
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)") }
|
|
9
|
+
else
|
|
13
10
|
class << self
|
|
14
11
|
def scoped_by_tenant?
|
|
15
12
|
true
|
|
16
13
|
end
|
|
17
14
|
|
|
15
|
+
# Allow partition_key to be set from a superclass if not already set in this class
|
|
18
16
|
def partition_key
|
|
19
|
-
@partition_key
|
|
17
|
+
@partition_key ||= ancestors.detect{ |k| k.instance_variable_get(:@partition_key) }
|
|
18
|
+
.try(:instance_variable_get, :@partition_key)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Avoid primary_key errors when using composite primary keys (e.g. id, tenant_id)
|
|
22
|
+
def primary_key
|
|
23
|
+
return @primary_key if @primary_key
|
|
24
|
+
return @primary_key = super || DEFAULT_ID_FIELD if Rails::VERSION::MAJOR < 5
|
|
25
|
+
|
|
26
|
+
primary_object_keys = (connection.schema_cache.primary_keys(table_name) || []) - [partition_key]
|
|
27
|
+
if primary_object_keys.size == 1
|
|
28
|
+
@primary_key = primary_object_keys.first
|
|
29
|
+
else
|
|
30
|
+
@primary_key = DEFAULT_ID_FIELD
|
|
31
|
+
end
|
|
20
32
|
end
|
|
21
33
|
end
|
|
22
34
|
|
|
23
|
-
@partition_key = options[:partition_key] || MultiTenant.partition_key
|
|
35
|
+
@partition_key = options[:partition_key] || MultiTenant.partition_key(tenant_name)
|
|
24
36
|
partition_key = @partition_key
|
|
25
37
|
|
|
26
|
-
#
|
|
27
|
-
if
|
|
28
|
-
|
|
29
|
-
self.primary_key = primary_object_keys.first if primary_object_keys.size == 1
|
|
30
|
-
else
|
|
31
|
-
self.primary_key = 'id' if primary_key.nil?
|
|
38
|
+
# Create an implicit belongs_to association only if tenant class exists
|
|
39
|
+
if MultiTenant.tenant_klass_defined?(tenant_name)
|
|
40
|
+
belongs_to tenant_name, options.slice(:class_name, :inverse_of).merge(foreign_key: partition_key)
|
|
32
41
|
end
|
|
33
42
|
|
|
34
|
-
# Create the association
|
|
35
|
-
belongs_to tenant, options.slice(:class_name, :inverse_of).merge(foreign_key: partition_key)
|
|
36
|
-
|
|
37
43
|
# Ensure all queries include the partition key
|
|
38
44
|
default_scope lambda {
|
|
39
45
|
if MultiTenant.current_tenant_id
|
|
@@ -50,43 +56,26 @@ module MultiTenant
|
|
|
50
56
|
end
|
|
51
57
|
}, on: :create
|
|
52
58
|
|
|
53
|
-
# Validate that associations belong to the tenant, currently only for belongs_to
|
|
54
|
-
polymorphic_foreign_keys = reflect_on_all_associations(:belongs_to).select do |a|
|
|
55
|
-
a.options[:polymorphic]
|
|
56
|
-
end.map { |a| a.foreign_key }
|
|
57
|
-
|
|
58
|
-
reflect_on_all_associations(:belongs_to).each do |a|
|
|
59
|
-
unless a == reflect_on_association(tenant) || polymorphic_foreign_keys.include?(a.foreign_key)
|
|
60
|
-
association_class = a.options[:class_name].nil? ? a.name.to_s.classify.constantize : a.options[:class_name].constantize
|
|
61
|
-
validates_each a.foreign_key.to_sym do |record, attr, value|
|
|
62
|
-
primary_key = if association_class.respond_to?(:primary_key)
|
|
63
|
-
association_class.primary_key
|
|
64
|
-
else
|
|
65
|
-
a.primary_key
|
|
66
|
-
end.to_sym
|
|
67
|
-
record.errors.add attr, 'association is invalid [MultiTenant]' unless value.nil? || association_class.where(primary_key => value).any?
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
59
|
to_include = Module.new do
|
|
73
|
-
define_method "#{partition_key}=" do |
|
|
74
|
-
write_attribute("#{partition_key}",
|
|
60
|
+
define_method "#{partition_key}=" do |tenant_id|
|
|
61
|
+
write_attribute("#{partition_key}", tenant_id)
|
|
75
62
|
raise MultiTenant::TenantIsImmutable if send("#{partition_key}_changed?") && persisted? && !send("#{partition_key}_was").nil?
|
|
76
|
-
|
|
63
|
+
tenant_id
|
|
77
64
|
end
|
|
78
65
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
66
|
+
if MultiTenant.tenant_klass_defined?(tenant_name)
|
|
67
|
+
define_method "#{tenant_name}=" do |model|
|
|
68
|
+
super(model)
|
|
69
|
+
raise MultiTenant::TenantIsImmutable if send("#{partition_key}_changed?") && persisted? && !send("#{partition_key}_was").nil?
|
|
70
|
+
model
|
|
71
|
+
end
|
|
84
72
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
73
|
+
define_method "#{tenant_name}" do
|
|
74
|
+
if !MultiTenant.current_tenant_is_id? && MultiTenant.current_tenant_id && public_send(partition_key) == MultiTenant.current_tenant_id
|
|
75
|
+
return MultiTenant.current_tenant
|
|
76
|
+
else
|
|
77
|
+
super()
|
|
78
|
+
end
|
|
90
79
|
end
|
|
91
80
|
end
|
|
92
81
|
end
|
|
@@ -94,7 +83,7 @@ module MultiTenant
|
|
|
94
83
|
|
|
95
84
|
around_save -> (record, block) {
|
|
96
85
|
if persisted? && MultiTenant.current_tenant_id.nil?
|
|
97
|
-
MultiTenant.
|
|
86
|
+
MultiTenant.with(record.public_send(partition_key)) { block.call }
|
|
98
87
|
else
|
|
99
88
|
block.call
|
|
100
89
|
end
|
|
@@ -102,7 +91,7 @@ module MultiTenant
|
|
|
102
91
|
|
|
103
92
|
around_update -> (record, block) {
|
|
104
93
|
if MultiTenant.current_tenant_id.nil?
|
|
105
|
-
MultiTenant.
|
|
94
|
+
MultiTenant.with(record.public_send(partition_key)) { block.call }
|
|
106
95
|
else
|
|
107
96
|
block.call
|
|
108
97
|
end
|
|
@@ -110,7 +99,7 @@ module MultiTenant
|
|
|
110
99
|
|
|
111
100
|
around_destroy -> (record, block) {
|
|
112
101
|
if MultiTenant.current_tenant_id.nil?
|
|
113
|
-
MultiTenant.
|
|
102
|
+
MultiTenant.with(record.public_send(partition_key)) { block.call }
|
|
114
103
|
else
|
|
115
104
|
block.call
|
|
116
105
|
end
|
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
require 'request_store'
|
|
2
2
|
|
|
3
3
|
module MultiTenant
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def self.set_tenant_klass(klass)
|
|
7
|
-
@@tenant_klass = klass
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def self.tenant_klass
|
|
11
|
-
@@tenant_klass
|
|
4
|
+
def self.tenant_klass_defined?(tenant_name)
|
|
5
|
+
!!tenant_name.to_s.classify.safe_constantize
|
|
12
6
|
end
|
|
13
7
|
|
|
14
|
-
def self.partition_key
|
|
15
|
-
"#{
|
|
8
|
+
def self.partition_key(tenant_name)
|
|
9
|
+
"#{tenant_name.to_s}_id"
|
|
16
10
|
end
|
|
17
11
|
|
|
18
12
|
# Workaroud to make "with_lock" work until https://github.com/citusdata/citus/issues/1236 is fixed
|
|
@@ -28,43 +22,28 @@ module MultiTenant
|
|
|
28
22
|
RequestStore.store[:current_tenant]
|
|
29
23
|
end
|
|
30
24
|
|
|
31
|
-
def self.current_tenant_id
|
|
32
|
-
|
|
25
|
+
def self.current_tenant_id
|
|
26
|
+
current_tenant_is_id? ? current_tenant : current_tenant.try(:id)
|
|
33
27
|
end
|
|
34
28
|
|
|
35
|
-
def self.
|
|
36
|
-
current_tenant.
|
|
29
|
+
def self.current_tenant_is_id?
|
|
30
|
+
current_tenant.is_a?(String) || current_tenant.is_a?(Integer)
|
|
37
31
|
end
|
|
38
32
|
|
|
39
33
|
def self.with(tenant, &block)
|
|
34
|
+
return block.call if self.current_tenant == tenant
|
|
40
35
|
old_tenant = self.current_tenant
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
self.current_tenant = old_tenant
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def self.with_id(tenant_id, &block)
|
|
50
|
-
if MultiTenant.current_tenant_id == tenant_id
|
|
51
|
-
block.call
|
|
52
|
-
else
|
|
53
|
-
MultiTenant.with(TenantIdWrapper.new(id: tenant_id), &block)
|
|
36
|
+
begin
|
|
37
|
+
self.current_tenant = tenant
|
|
38
|
+
return block.call
|
|
39
|
+
ensure
|
|
40
|
+
self.current_tenant = old_tenant
|
|
54
41
|
end
|
|
55
42
|
end
|
|
56
43
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
class TenantIdWrapper
|
|
61
|
-
attr_reader :id
|
|
44
|
+
# Preserve backward compatibility for people using .with_id
|
|
45
|
+
singleton_class.send(:alias_method, :with_id, :with)
|
|
62
46
|
|
|
63
|
-
|
|
64
|
-
@id = id
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def new_record?; true; end
|
|
68
|
-
def touch; nil; end
|
|
47
|
+
class TenantIsImmutable < StandardError
|
|
69
48
|
end
|
|
70
49
|
end
|
|
@@ -56,6 +56,15 @@ describe MultiTenant do
|
|
|
56
56
|
it { expect(@custom_partition_key_task.account).to eq(@account) }
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
describe 'Tenant model not defined' do
|
|
60
|
+
before do
|
|
61
|
+
MultiTenant.current_tenant = 77
|
|
62
|
+
@partition_key_not_model_task = PartitionKeyNotModelTask.create! name: 'foo'
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it { expect(@partition_key_not_model_task.non_model_id).to be 77 }
|
|
66
|
+
end
|
|
67
|
+
|
|
59
68
|
# Scoping models
|
|
60
69
|
describe 'Project.all should be scoped to the current tenant if set' do
|
|
61
70
|
before do
|
|
@@ -77,7 +86,7 @@ describe MultiTenant do
|
|
|
77
86
|
before do
|
|
78
87
|
@account = Account.create! name: 'foo'
|
|
79
88
|
@project = @account.projects.create! name: 'foobar'
|
|
80
|
-
MultiTenant.current_tenant= @account1
|
|
89
|
+
MultiTenant.current_tenant = @account1
|
|
81
90
|
end
|
|
82
91
|
|
|
83
92
|
it { @project.account }
|
|
@@ -137,6 +146,40 @@ describe MultiTenant do
|
|
|
137
146
|
end
|
|
138
147
|
end
|
|
139
148
|
|
|
149
|
+
describe 'Subclass of Multi Tenant Model' do
|
|
150
|
+
let(:account) { Account.create!(name: 'foo') }
|
|
151
|
+
let(:project) { Project.create!(name: 'project', account: account) }
|
|
152
|
+
let(:task) { project.tasks.create!(name: 'task') }
|
|
153
|
+
let(:sti_task) { StiSubTask.create!(task: task, name: 'sub task') }
|
|
154
|
+
|
|
155
|
+
it 'has partition key' do
|
|
156
|
+
expect(StiSubTask.partition_key).to eq 'account_id'
|
|
157
|
+
expect(StiSubTask.instance_variable_get(:@partition_key)).to eq 'account_id'
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it 'has primary key' do
|
|
161
|
+
expect(StiSubTask.primary_key).to eq 'id'
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
it 'handles belongs_to through' do
|
|
165
|
+
MultiTenant.with(account) do
|
|
166
|
+
expect(sti_task.project).to eq project
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
it 'handles has_many through' do
|
|
171
|
+
MultiTenant.with(account) do
|
|
172
|
+
expect(project.sub_tasks).to eq [sti_task]
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it 'handles unscoped' do
|
|
177
|
+
MultiTenant.with(account) do
|
|
178
|
+
expect(StiSubTask.unscoped.find(sti_task.id)).to eq sti_task
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
140
183
|
# ::with
|
|
141
184
|
describe "::with" do
|
|
142
185
|
it "should set current_tenant to the specified tenant inside the block" do
|
data/spec/schema.rb
CHANGED
|
@@ -28,6 +28,7 @@ ARGV.grep(/\w+_spec\.rb/).empty? && ActiveRecord::Schema.define(version: 1) do
|
|
|
28
28
|
t.column :account_id, :integer
|
|
29
29
|
t.column :name, :string
|
|
30
30
|
t.column :task_id, :integer
|
|
31
|
+
t.column :type, :string
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
create_table :countries, force: true do |t|
|
|
@@ -55,6 +56,11 @@ ARGV.grep(/\w+_spec\.rb/).empty? && ActiveRecord::Schema.define(version: 1) do
|
|
|
55
56
|
t.column :commentable_type, :string
|
|
56
57
|
end
|
|
57
58
|
|
|
59
|
+
create_table :partition_key_not_model_tasks, force: true, partition_key: :non_model_id do |t|
|
|
60
|
+
t.column :non_model_id, :integer
|
|
61
|
+
t.column :name, :string
|
|
62
|
+
end
|
|
63
|
+
|
|
58
64
|
create_distributed_table :accounts, :id
|
|
59
65
|
create_distributed_table :projects, :account_id
|
|
60
66
|
create_distributed_table :managers, :account_id
|
|
@@ -63,6 +69,7 @@ ARGV.grep(/\w+_spec\.rb/).empty? && ActiveRecord::Schema.define(version: 1) do
|
|
|
63
69
|
create_distributed_table :aliased_tasks, :account_id
|
|
64
70
|
create_distributed_table :custom_partition_key_tasks, :accountID
|
|
65
71
|
create_distributed_table :comments, :account_id
|
|
72
|
+
create_distributed_table :partition_key_not_model_tasks, :non_model_id
|
|
66
73
|
end
|
|
67
74
|
|
|
68
75
|
class Account < ActiveRecord::Base
|
|
@@ -104,6 +111,9 @@ class SubTask < ActiveRecord::Base
|
|
|
104
111
|
has_one :project, through: :task
|
|
105
112
|
end
|
|
106
113
|
|
|
114
|
+
class StiSubTask < SubTask
|
|
115
|
+
end
|
|
116
|
+
|
|
107
117
|
class UnscopedModel < ActiveRecord::Base
|
|
108
118
|
validates_uniqueness_of :name
|
|
109
119
|
end
|
|
@@ -123,6 +133,10 @@ class CustomPartitionKeyTask < ActiveRecord::Base
|
|
|
123
133
|
end
|
|
124
134
|
end
|
|
125
135
|
|
|
136
|
+
class PartitionKeyNotModelTask < ActiveRecord::Base
|
|
137
|
+
multi_tenant :non_model
|
|
138
|
+
end
|
|
139
|
+
|
|
126
140
|
class Comment < ActiveRecord::Base
|
|
127
141
|
multi_tenant :account
|
|
128
142
|
belongs_to :commentable, polymorphic: true
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activerecord-multi-tenant
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Citus Data
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2017-
|
|
11
|
+
date: 2017-03-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: request_store
|