activerecord-tenanted 0.7.0 → 0.9.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/lib/active_record/tenanted/associations.rb +32 -0
- data/lib/active_record/tenanted/database_adapters/base.rb +27 -0
- data/lib/active_record/tenanted/database_adapters/sqlite.rb +24 -13
- data/lib/active_record/tenanted/database_configurations/base_config.rb +10 -2
- data/lib/active_record/tenanted/global_id.rb +7 -2
- data/lib/active_record/tenanted/message_pack.rb +26 -0
- data/lib/active_record/tenanted/railtie.rb +8 -0
- data/lib/active_record/tenanted/storage.rb +6 -0
- data/lib/active_record/tenanted/tenant.rb +137 -19
- data/lib/active_record/tenanted/untenanted_connection_pool.rb +3 -0
- data/lib/active_record/tenanted/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 92b0d3851167bdcfb0abd394b770ad1d13474c3cf9716caed9f0bf3b9a49a226
|
|
4
|
+
data.tar.gz: 5cb5eee41c5e6723042c536fe04db854519a076e2223ac60bd5df862185978c5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 42beaf7932556597872dc01cff858c4f934f1f545e114f3fa0e48890d06d962847a34ec40ac81dab6fa8d3bde253a62ffa41b1645d3a5fe2f07aa2c0f6e670fd
|
|
7
|
+
data.tar.gz: 1a117271fc26dfc6950669a6a2ccf2adac1d017ae00a30d206e540fce3325ff80d5822902b9cc8f35f85359349cb7e5c66dc061397c43ef610c30f3e56b5e7e3
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module Tenanted
|
|
5
|
+
# Tenant context is checked when an association reads or writes the database, and not when the
|
|
6
|
+
# Association object is created. Serializers create associations to dump and restore their
|
|
7
|
+
# targets without ever going near a connection, and must not be tripped up by the check.
|
|
8
|
+
#
|
|
9
|
+
# `klass` is resolved at both of these seams, so a polymorphic association is checked against
|
|
10
|
+
# the class it actually points at rather than being presumed tenanted.
|
|
11
|
+
module Associations # :nodoc:
|
|
12
|
+
# Every query built on behalf of an association funnels through here, including the ones
|
|
13
|
+
# issued by a collection proxy that has outlived the tenant context it was created in.
|
|
14
|
+
def scope(...)
|
|
15
|
+
ensure_owner_tenant_context_safety
|
|
16
|
+
super
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
# Called by the collection and singular association readers, so that the exception is
|
|
21
|
+
# raised at the call site that made the mistake and not at the eventual query.
|
|
22
|
+
def ensure_klass_exists!
|
|
23
|
+
super
|
|
24
|
+
ensure_owner_tenant_context_safety
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def ensure_owner_tenant_context_safety
|
|
28
|
+
owner.ensure_tenant_context_safety if owner.class.tenanted? && klass&.tenanted?
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module Tenanted
|
|
5
|
+
module DatabaseAdapters # :nodoc:
|
|
6
|
+
class Base
|
|
7
|
+
attr_reader :db_config
|
|
8
|
+
|
|
9
|
+
def initialize(db_config)
|
|
10
|
+
@db_config = db_config
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# The Active Storage disk service interpolates the tenant name into a filesystem path, so a
|
|
14
|
+
# valid name must be a single, safe path segment whatever the database allows.
|
|
15
|
+
def valid_tenant_name?(tenant_name)
|
|
16
|
+
raise "#{self.class} must implement #{__method__}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def validate_tenant_name(tenant_name)
|
|
20
|
+
unless valid_tenant_name?(tenant_name)
|
|
21
|
+
raise BadTenantNameError, "Invalid tenant name: #{tenant_name.inspect}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -6,7 +6,7 @@ module ActiveRecord
|
|
|
6
6
|
#
|
|
7
7
|
# TODO: This still feels to me like it's not _quite_ right. I think we could further refactor this by:
|
|
8
8
|
#
|
|
9
|
-
# 1. Moving tenant_databases and
|
|
9
|
+
# 1. Moving tenant_databases and valid_tenant_name? to BaseConfig, and subclassing it for
|
|
10
10
|
# each database
|
|
11
11
|
# 2. Moving create_database, drop_database, database_exist?, database_ready?,
|
|
12
12
|
# acquire_ready_lock, ensure_database_directory_exists, and database_path to the SQLite
|
|
@@ -14,30 +14,41 @@ module ActiveRecord
|
|
|
14
14
|
# 3. Moving test_workerize and path_for to be SQLite connection adapter class methods,
|
|
15
15
|
# possibly into Rails
|
|
16
16
|
#
|
|
17
|
-
class SQLite
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
class SQLite < Base
|
|
18
|
+
# A tenant name is interpolated into the database path as a single path segment, and that
|
|
19
|
+
# path may be a URI (see #path_for), so a name is limited to the RFC 3986 unreserved
|
|
20
|
+
# characters and may not begin with a dot or a tilde. A leading dot either traverses out of
|
|
21
|
+
# the tenant directory or hides the database from the glob in #tenant_databases; a leading
|
|
22
|
+
# tilde is expanded by File.expand_path when the name begins a relative path; "%" is
|
|
23
|
+
# percent-decoded by SQLite when it opens a URI filename; "?" and "#" truncate the path; and
|
|
24
|
+
# other punctuation is either reserved in a URI or makes URI.parse raise.
|
|
25
|
+
TENANT_NAME_PATTERN = /\A[A-Za-z0-9_-][A-Za-z0-9._~-]*\z/
|
|
23
26
|
|
|
24
27
|
def tenant_databases
|
|
25
|
-
glob = path_for(db_config.
|
|
26
|
-
scanner = Regexp.new(path_for(db_config.
|
|
28
|
+
glob = path_for(db_config.database_pattern_for("*"))
|
|
29
|
+
scanner = Regexp.new(path_for(db_config.database_pattern_for("(.+)")))
|
|
27
30
|
|
|
28
31
|
Dir.glob(glob).filter_map do |path|
|
|
29
32
|
result = path.scan(scanner).flatten.first
|
|
33
|
+
|
|
30
34
|
if result.nil?
|
|
31
35
|
Rails.logger.warn "ActiveRecord::Tenanted: Cannot parse tenant name from filename #{path.inspect}"
|
|
36
|
+
next
|
|
32
37
|
end
|
|
38
|
+
|
|
39
|
+
unless valid_tenant_name?(result)
|
|
40
|
+
Rails.logger.warn "ActiveRecord::Tenanted: Skipping database with an invalid tenant name #{result.inspect} in #{path.inspect}"
|
|
41
|
+
next
|
|
42
|
+
end
|
|
43
|
+
|
|
33
44
|
result
|
|
34
45
|
end
|
|
35
46
|
end
|
|
36
47
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
def valid_tenant_name?(tenant_name)
|
|
49
|
+
tenant_name.encoding.ascii_compatible? &&
|
|
50
|
+
tenant_name.valid_encoding? &&
|
|
51
|
+
TENANT_NAME_PATTERN.match?(tenant_name)
|
|
41
52
|
end
|
|
42
53
|
|
|
43
54
|
def create_database
|
|
@@ -27,7 +27,13 @@ module ActiveRecord
|
|
|
27
27
|
|
|
28
28
|
config_adapter.validate_tenant_name(tenant_name)
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
database_pattern_for(tenant_name)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Interpolates a glob or a regular expression into the database path. The pattern is not a
|
|
34
|
+
# tenant name and is not validated as one.
|
|
35
|
+
def database_pattern_for(pattern) # :nodoc:
|
|
36
|
+
db = sprintf(database, tenant: pattern)
|
|
31
37
|
|
|
32
38
|
if test_worker_id
|
|
33
39
|
db = config_adapter.test_workerize(db, test_worker_id)
|
|
@@ -41,10 +47,12 @@ module ActiveRecord
|
|
|
41
47
|
end
|
|
42
48
|
|
|
43
49
|
def new_tenant_config(tenant_name)
|
|
50
|
+
database = database_for(tenant_name) # validates the tenant name before it is used below
|
|
51
|
+
|
|
44
52
|
config_name = "#{name}_#{tenant_name}"
|
|
45
53
|
config_hash = configuration_hash.dup.tap do |hash|
|
|
46
54
|
hash[:tenant] = tenant_name
|
|
47
|
-
hash[:database] =
|
|
55
|
+
hash[:database] = database
|
|
48
56
|
hash[:tenanted_config_name] = name
|
|
49
57
|
end
|
|
50
58
|
Tenanted::DatabaseConfigurations::TenantConfig.new(env_name, config_name, config_hash)
|
|
@@ -9,10 +9,15 @@ module ActiveRecord
|
|
|
9
9
|
params && params[:tenant]
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
class Locator
|
|
12
|
+
class Locator < ::GlobalID::Locator::UnscopedLocator
|
|
13
13
|
def locate(gid, options = {})
|
|
14
14
|
ensure_tenant_context_safety(gid)
|
|
15
|
-
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def locate_many(gids, options = {})
|
|
19
|
+
gids.each { |gid| ensure_tenant_context_safety(gid) }
|
|
20
|
+
super
|
|
16
21
|
end
|
|
17
22
|
|
|
18
23
|
private
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module Tenanted
|
|
5
|
+
module MessagePack # :nodoc:
|
|
6
|
+
# This patch sees every record in a payload, unlike the Marshal, YAML, and JSON hooks, which
|
|
7
|
+
# are methods on the record and so only ever run for tenanted models.
|
|
8
|
+
module Decoder
|
|
9
|
+
def build_record(entry)
|
|
10
|
+
class_name, attributes_hash, * = entry
|
|
11
|
+
|
|
12
|
+
if ActiveSupport::MessagePack::Extensions.load_class(class_name).tenanted?
|
|
13
|
+
has_tenant = attributes_hash.key?("tenant")
|
|
14
|
+
tenant_name = attributes_hash.delete("tenant")
|
|
15
|
+
|
|
16
|
+
super.tap do |record|
|
|
17
|
+
record.instance_variable_set(:@tenant, tenant_name) if has_tenant
|
|
18
|
+
end
|
|
19
|
+
else
|
|
20
|
+
super
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -75,6 +75,7 @@ module ActiveRecord
|
|
|
75
75
|
ActiveSupport.on_load(:active_record) do
|
|
76
76
|
prepend ActiveRecord::Tenanted::Base
|
|
77
77
|
ActiveRecord::Relation.prepend ActiveRecord::Tenanted::Relation
|
|
78
|
+
ActiveRecord::Associations::Association.prepend ActiveRecord::Tenanted::Associations
|
|
78
79
|
end
|
|
79
80
|
end
|
|
80
81
|
|
|
@@ -93,6 +94,13 @@ module ActiveRecord
|
|
|
93
94
|
config.active_record.check_schema_cache_dump_version = false
|
|
94
95
|
end
|
|
95
96
|
|
|
97
|
+
initializer "active_record_tenanted.message_pack" do
|
|
98
|
+
ActiveSupport.on_load(:message_pack) do
|
|
99
|
+
require "active_record/message_pack"
|
|
100
|
+
ActiveRecord::MessagePack::Decoder.prepend ActiveRecord::Tenanted::MessagePack::Decoder
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
96
104
|
initializer "active_record_tenanted.monkey_patches" do
|
|
97
105
|
ActiveSupport.on_load(:active_record) do
|
|
98
106
|
ActiveRecord::Tenanted::Patches::Attributes.apply_patch
|
|
@@ -10,6 +10,12 @@ module ActiveRecord
|
|
|
10
10
|
raise NoTenantError, "Cannot access Active Storage Disk service without a tenant"
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
# The tenant is read from the connection context, which Rails' own `connected_to` can
|
|
14
|
+
# set without going through this gem's tenant API, so the name is checked here too. A
|
|
15
|
+
# shard set that way may be a Symbol or an Integer.
|
|
16
|
+
tenant = tenant.to_s
|
|
17
|
+
klass.tenanted_root_config.config_adapter.validate_tenant_name(tenant)
|
|
18
|
+
|
|
13
19
|
sprintf(@root, tenant: tenant)
|
|
14
20
|
else
|
|
15
21
|
super
|
|
@@ -9,13 +9,24 @@ module ActiveRecord
|
|
|
9
9
|
prepended do
|
|
10
10
|
attr_reader :tenant
|
|
11
11
|
|
|
12
|
-
before_save :
|
|
12
|
+
before_save :ensure_belongs_to_tenant_safety, prepend: true
|
|
13
|
+
before_save :ensure_tenant_context_safety, prepend: true
|
|
14
|
+
before_destroy :ensure_tenant_context_safety, prepend: true
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
def cache_key
|
|
16
18
|
tenant ? "#{tenant}/#{super}" : super
|
|
17
19
|
end
|
|
18
20
|
|
|
21
|
+
def ==(other)
|
|
22
|
+
super && tenant == other.tenant
|
|
23
|
+
end
|
|
24
|
+
alias eql? ==
|
|
25
|
+
|
|
26
|
+
def hash
|
|
27
|
+
[ super, tenant ].hash
|
|
28
|
+
end
|
|
29
|
+
|
|
19
30
|
def inspect
|
|
20
31
|
return super unless tenant
|
|
21
32
|
|
|
@@ -30,14 +41,114 @@ module ActiveRecord
|
|
|
30
41
|
super(options.merge(tenant: tenant))
|
|
31
42
|
end
|
|
32
43
|
|
|
33
|
-
def
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
44
|
+
def reload(...)
|
|
45
|
+
ensure_tenant_context_safety
|
|
46
|
+
|
|
47
|
+
super
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def valid?(...)
|
|
51
|
+
ensure_tenant_context_safety
|
|
52
|
+
|
|
53
|
+
super
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def delete
|
|
57
|
+
ensure_tenant_context_safety
|
|
58
|
+
|
|
59
|
+
super
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# #update_column delegates here
|
|
63
|
+
def update_columns(...)
|
|
64
|
+
ensure_tenant_context_safety
|
|
65
|
+
|
|
66
|
+
super
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def touch(...)
|
|
70
|
+
ensure_tenant_context_safety
|
|
71
|
+
|
|
72
|
+
super
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# #decrement! delegates here
|
|
76
|
+
def increment!(...)
|
|
77
|
+
ensure_tenant_context_safety
|
|
78
|
+
|
|
79
|
+
super
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# A belongs_to writes its foreign key onto this record, so nothing else notices when the
|
|
83
|
+
# target came from another tenant's database.
|
|
84
|
+
def ensure_belongs_to_tenant_safety
|
|
85
|
+
self.class.reflect_on_all_associations(:belongs_to).each do |reflection|
|
|
86
|
+
# Avoid creating the Association object for a belongs_to that was never touched
|
|
87
|
+
next unless association_cached?(reflection.name)
|
|
88
|
+
|
|
89
|
+
target = association(reflection.name).target
|
|
90
|
+
|
|
91
|
+
if target && target.class.tenanted? && target.tenant != tenant
|
|
92
|
+
raise WrongTenantError,
|
|
93
|
+
"#{self.class} model belongs to tenant #{tenant.inspect}, but its " \
|
|
94
|
+
"#{reflection.name} association belongs to tenant #{target.tenant.inspect}"
|
|
37
95
|
end
|
|
38
96
|
end
|
|
39
97
|
end
|
|
40
98
|
|
|
99
|
+
def ensure_tenant_context_safety
|
|
100
|
+
self_tenant = self.tenant
|
|
101
|
+
current_tenant = self.class.current_tenant
|
|
102
|
+
|
|
103
|
+
if current_tenant.nil?
|
|
104
|
+
raise NoTenantError, "Cannot connect to a tenanted database while untenanted (#{self.class})"
|
|
105
|
+
elsif self_tenant != current_tenant
|
|
106
|
+
raise WrongTenantError,
|
|
107
|
+
"#{self.class} model belongs to tenant #{self_tenant.inspect}, " \
|
|
108
|
+
"but current tenant is #{current_tenant.inspect}"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# The tenant is an ordinary attribute in serialized snapshots of the record, the same way Job
|
|
113
|
+
# and GlobalId serialize the tenant. Rails's only callers of this method are the serializers
|
|
114
|
+
# (Marshal 7.1 format and MessagePack); it does not feed the persistence write path.
|
|
115
|
+
def attributes_for_database
|
|
116
|
+
super.merge!("tenant" => tenant)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def encode_with(coder)
|
|
120
|
+
super
|
|
121
|
+
coder["tenant"] = tenant
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def init_with(coder, &block)
|
|
125
|
+
super
|
|
126
|
+
|
|
127
|
+
# Psych::Coder does not implement #key?, but exposes the underlying hash as #map
|
|
128
|
+
@tenant = coder.map["tenant"] if coder.map.key?("tenant")
|
|
129
|
+
|
|
130
|
+
self
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def from_json(json, include_root = include_root_in_json)
|
|
134
|
+
hash = ActiveSupport::JSON.decode(json)
|
|
135
|
+
hash = hash.values.first if include_root
|
|
136
|
+
|
|
137
|
+
@tenant = hash.delete("tenant") if hash.key?("tenant")
|
|
138
|
+
|
|
139
|
+
self.attributes = hash
|
|
140
|
+
self
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def marshal_load(state)
|
|
144
|
+
has_tenant = state[0].key?("tenant")
|
|
145
|
+
tenant_name = state[0].delete("tenant")
|
|
146
|
+
|
|
147
|
+
super
|
|
148
|
+
|
|
149
|
+
@tenant = tenant_name if has_tenant
|
|
150
|
+
end
|
|
151
|
+
|
|
41
152
|
alias to_gid to_global_id
|
|
42
153
|
alias to_sgid to_signed_global_id
|
|
43
154
|
|
|
@@ -50,17 +161,10 @@ module ActiveRecord
|
|
|
50
161
|
super
|
|
51
162
|
end
|
|
52
163
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if current_tenant.nil?
|
|
58
|
-
raise NoTenantError, "Cannot connect to a tenanted database while untenanted (#{self.class})"
|
|
59
|
-
elsif self_tenant != current_tenant
|
|
60
|
-
raise WrongTenantError,
|
|
61
|
-
"#{self.class} model belongs to tenant #{self_tenant.inspect}, " \
|
|
62
|
-
"but current tenant is #{current_tenant.inspect}"
|
|
63
|
-
end
|
|
164
|
+
# Presenting the tenant as one more serializable attribute lets ActiveModel apply the
|
|
165
|
+
# `only:` and `except:` options to it, and read it back through the `tenant` reader.
|
|
166
|
+
def attribute_names_for_serialization
|
|
167
|
+
super + [ "tenant" ]
|
|
64
168
|
end
|
|
65
169
|
end
|
|
66
170
|
|
|
@@ -101,7 +205,7 @@ module ActiveRecord
|
|
|
101
205
|
when UNTENANTED_SENTINEL
|
|
102
206
|
# no-op
|
|
103
207
|
else
|
|
104
|
-
tenant_name = tenant_name
|
|
208
|
+
tenant_name = validated_tenant_name(tenant_name)
|
|
105
209
|
end
|
|
106
210
|
|
|
107
211
|
run_callbacks :set_current_tenant do
|
|
@@ -110,11 +214,14 @@ module ActiveRecord
|
|
|
110
214
|
end
|
|
111
215
|
|
|
112
216
|
def tenant_exist?(tenant_name)
|
|
113
|
-
tenanted_root_config
|
|
217
|
+
root_config = tenanted_root_config
|
|
218
|
+
return false unless root_config.config_adapter.valid_tenant_name?(tenant_name.to_s)
|
|
219
|
+
|
|
220
|
+
root_config.new_tenant_config(tenant_name).config_adapter.database_ready?
|
|
114
221
|
end
|
|
115
222
|
|
|
116
223
|
def with_tenant(tenant_name, prohibit_shard_swapping: true, &block)
|
|
117
|
-
tenant_name = tenant_name
|
|
224
|
+
tenant_name = validated_tenant_name(tenant_name) unless tenant_name == UNTENANTED_SENTINEL
|
|
118
225
|
|
|
119
226
|
if tenant_name == current_tenant
|
|
120
227
|
run_callbacks :with_tenant, &block
|
|
@@ -158,6 +265,8 @@ module ActiveRecord
|
|
|
158
265
|
end
|
|
159
266
|
|
|
160
267
|
def destroy_tenant(tenant_name)
|
|
268
|
+
tenant_name = validated_tenant_name(tenant_name)
|
|
269
|
+
|
|
161
270
|
ActiveRecord::Base.logger.info " DESTROY [tenant=#{tenant_name}] Destroying tenant database"
|
|
162
271
|
|
|
163
272
|
with_tenant(tenant_name, prohibit_shard_swapping: false) do
|
|
@@ -232,6 +341,15 @@ module ActiveRecord
|
|
|
232
341
|
end
|
|
233
342
|
|
|
234
343
|
private
|
|
344
|
+
# A tenant name reaches the filesystem through the Active Storage disk service as well as
|
|
345
|
+
# through the database path, and the disk service reads it from the tenant context rather
|
|
346
|
+
# than from a database configuration. Validate it as the context is set.
|
|
347
|
+
def validated_tenant_name(tenant_name)
|
|
348
|
+
tenant_name = tenant_name.to_s
|
|
349
|
+
tenanted_root_config.config_adapter.validate_tenant_name(tenant_name)
|
|
350
|
+
tenant_name
|
|
351
|
+
end
|
|
352
|
+
|
|
235
353
|
def retrieve_connection_pool(strict:)
|
|
236
354
|
role = current_role
|
|
237
355
|
shard = current_tenant
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activerecord-tenanted
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mike Dalessio
|
|
@@ -69,11 +69,13 @@ files:
|
|
|
69
69
|
- README.md
|
|
70
70
|
- Rakefile
|
|
71
71
|
- lib/active_record/tenanted.rb
|
|
72
|
+
- lib/active_record/tenanted/associations.rb
|
|
72
73
|
- lib/active_record/tenanted/base.rb
|
|
73
74
|
- lib/active_record/tenanted/cable_connection.rb
|
|
74
75
|
- lib/active_record/tenanted/connection_adapter.rb
|
|
75
76
|
- lib/active_record/tenanted/console.rb
|
|
76
77
|
- lib/active_record/tenanted/database_adapter.rb
|
|
78
|
+
- lib/active_record/tenanted/database_adapters/base.rb
|
|
77
79
|
- lib/active_record/tenanted/database_adapters/sqlite.rb
|
|
78
80
|
- lib/active_record/tenanted/database_configurations.rb
|
|
79
81
|
- lib/active_record/tenanted/database_configurations/base_config.rb
|
|
@@ -83,6 +85,7 @@ files:
|
|
|
83
85
|
- lib/active_record/tenanted/job.rb
|
|
84
86
|
- lib/active_record/tenanted/lru.rb
|
|
85
87
|
- lib/active_record/tenanted/mailer.rb
|
|
88
|
+
- lib/active_record/tenanted/message_pack.rb
|
|
86
89
|
- lib/active_record/tenanted/mutex.rb
|
|
87
90
|
- lib/active_record/tenanted/patches.rb
|
|
88
91
|
- lib/active_record/tenanted/railtie.rb
|