activerecord-tenanted 0.5.0 → 0.6.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/connection_adapter.rb +15 -6
- data/lib/active_record/tenanted/console.rb +5 -1
- data/lib/active_record/tenanted/database_adapter.rb +11 -6
- data/lib/active_record/tenanted/database_configurations/tenant_config.rb +3 -3
- data/lib/active_record/tenanted/database_tasks.rb +80 -29
- data/lib/active_record/tenanted/railtie.rb +4 -0
- data/lib/active_record/tenanted/tenant.rb +3 -2
- data/lib/active_record/tenanted/untenanted_connection_pool.rb +4 -0
- data/lib/active_record/tenanted/version.rb +1 -1
- data/lib/active_record/tenanted.rb +9 -0
- data/lib/tasks/active_record/tenanted_tasks.rake +13 -71
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 62c3b9d7f37db44de60c7559e4b10e1407171ab29aa874e2687853e8acc38bb1
|
|
4
|
+
data.tar.gz: 27b2ee871ff4ce67b60480e83694cf6a2aeac504b3ab39f96ea0eb243b5f3a47
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7eff54730cbc865dd8ecb1169e146194b0edceaed0527059ed110537aedd57901a5a5af5b85c522bfebcf50714c468984f91ac4f5c8e40077b2e737497eac550
|
|
7
|
+
data.tar.gz: fafa6f206173a7057e090407f5cf6698bd7f1d34885509793568f1e76baedbe61b7eeea980493b5af0df30c3f5bd20400c799eebecf62785f458e925afbee57f
|
|
@@ -2,18 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
module Tenanted
|
|
5
|
-
|
|
5
|
+
#
|
|
6
|
+
# Extends ActiveRecord::ConnectionAdapters::AbstractAdapter with a `tenant` attribute.
|
|
7
|
+
#
|
|
8
|
+
# This is useful in conjunction with the `:tenant` query log tag, which configures logging of
|
|
9
|
+
# the tenant in SQL query logs (when `config.active_record.query_log_tags_enabled` is set to
|
|
10
|
+
# `true`). For example:
|
|
11
|
+
#
|
|
12
|
+
# Rails.application.config.active_record.query_log_tags_enabled = true
|
|
13
|
+
# Rails.application.config.active_record.query_log_tags = [ :tenant ]
|
|
14
|
+
#
|
|
15
|
+
# will cause the application to emit logs like:
|
|
16
|
+
#
|
|
17
|
+
# User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1 /*tenant='foo'*/
|
|
18
|
+
#
|
|
19
|
+
module ConnectionAdapter
|
|
6
20
|
extend ActiveSupport::Concern
|
|
7
21
|
|
|
8
22
|
prepended do
|
|
9
23
|
attr_accessor :tenant
|
|
10
24
|
end
|
|
11
25
|
|
|
12
|
-
def log(sql, name = "SQL", binds = [], type_casted_binds = [], async: false, allow_retry: false, &block)
|
|
13
|
-
name = [ name, "[tenant=#{tenant}]" ].compact.join(" ") if tenanted?
|
|
14
|
-
super
|
|
15
|
-
end
|
|
16
|
-
|
|
17
26
|
def tenanted?
|
|
18
27
|
tenant.present?
|
|
19
28
|
end
|
|
@@ -5,7 +5,11 @@ module ActiveRecord
|
|
|
5
5
|
module Console # :nodoc:
|
|
6
6
|
module IRBConsole
|
|
7
7
|
def start
|
|
8
|
-
|
|
8
|
+
# TODO: we could be setting the current tenant for all tenanted configs.
|
|
9
|
+
if Rails.env.local? && ActiveRecord::Tenanted.connection_class
|
|
10
|
+
config = ActiveRecord::Tenanted.connection_class.connection_pool.db_config
|
|
11
|
+
ActiveRecord::Tenanted::DatabaseTasks.new(config).set_current_tenant
|
|
12
|
+
end
|
|
9
13
|
super
|
|
10
14
|
end
|
|
11
15
|
end
|
|
@@ -2,24 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
module Tenanted
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}.freeze
|
|
5
|
+
module DatabaseAdapter # :nodoc:
|
|
6
|
+
# Hash of registered database configuration adapters
|
|
7
|
+
@adapters = {}
|
|
9
8
|
|
|
10
9
|
class << self
|
|
10
|
+
def register(name, class_name)
|
|
11
|
+
@adapters[name.to_s] = class_name
|
|
12
|
+
end
|
|
13
|
+
|
|
11
14
|
def new(db_config)
|
|
12
|
-
adapter_class_name =
|
|
15
|
+
adapter_class_name = @adapters[db_config.adapter]
|
|
13
16
|
|
|
14
17
|
if adapter_class_name.nil?
|
|
15
18
|
raise ActiveRecord::Tenanted::UnsupportedDatabaseError,
|
|
16
19
|
"Unsupported database adapter for tenanting: #{db_config.adapter}. " \
|
|
17
|
-
"Supported adapters: #{
|
|
20
|
+
"Supported adapters: #{@adapters.keys.join(', ')}"
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
adapter_class_name.constantize.new(db_config)
|
|
21
24
|
end
|
|
22
25
|
end
|
|
26
|
+
|
|
27
|
+
register "sqlite3", "ActiveRecord::Tenanted::DatabaseAdapters::SQLite"
|
|
23
28
|
end
|
|
24
29
|
end
|
|
25
30
|
end
|
|
@@ -18,11 +18,11 @@ module ActiveRecord
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def new_connection
|
|
21
|
-
# TODO:
|
|
22
|
-
#
|
|
21
|
+
# TODO: This line can be removed once rails/rails@f1f60dc1 is in a released version of
|
|
22
|
+
# Rails, and this gem's dependency has been bumped to require that version or later.
|
|
23
23
|
config_adapter.ensure_database_directory_exists
|
|
24
24
|
|
|
25
|
-
super.tap { |
|
|
25
|
+
super.tap { |connection| connection.tenant = tenant }
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def tenanted_config_name
|
|
@@ -1,54 +1,63 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "rake"
|
|
4
|
+
|
|
3
5
|
module ActiveRecord
|
|
4
6
|
module Tenanted
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def migrate_all
|
|
9
|
-
raise ArgumentError, "Could not find a tenanted database" unless config = base_config
|
|
7
|
+
class DatabaseTasks # :nodoc:
|
|
8
|
+
include Rake::DSL
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
migrate(tenant_config)
|
|
10
|
+
class << self
|
|
11
|
+
def verbose?
|
|
12
|
+
ActiveRecord::Tasks::DatabaseTasks.send(:verbose?)
|
|
15
13
|
end
|
|
16
14
|
end
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
attr_reader :config
|
|
17
|
+
|
|
18
|
+
def initialize(config)
|
|
19
|
+
unless config.is_a?(ActiveRecord::Tenanted::DatabaseConfigurations::BaseConfig)
|
|
20
|
+
raise TypeError, "Argument must be an instance of ActiveRecord::Tenanted::DatabaseConfigurations::BaseConfig"
|
|
21
|
+
end
|
|
22
|
+
@config = config
|
|
23
|
+
end
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
def migrate_all
|
|
26
|
+
tenants.each do |tenant|
|
|
27
|
+
migrate_tenant(tenant)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
22
30
|
|
|
23
|
-
|
|
31
|
+
def migrate_tenant(tenant = set_current_tenant)
|
|
32
|
+
db_config = config.new_tenant_config(tenant)
|
|
33
|
+
migrate(db_config)
|
|
24
34
|
end
|
|
25
35
|
|
|
26
36
|
def drop_all
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
config.tenants.each do |tenant|
|
|
30
|
-
db_config = config.new_tenant_config(tenant)
|
|
31
|
-
db_config.config_adapter.drop_database
|
|
32
|
-
$stdout.puts "Dropped database '#{db_config.database}'" if verbose?
|
|
37
|
+
tenants.each do |tenant|
|
|
38
|
+
drop_tenant(tenant)
|
|
33
39
|
end
|
|
34
40
|
end
|
|
35
41
|
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
def drop_tenant(tenant = set_current_tenant)
|
|
43
|
+
db_config = config.new_tenant_config(tenant)
|
|
44
|
+
db_config.config_adapter.drop_database
|
|
45
|
+
$stdout.puts "Dropped database '#{db_config.database}'" if verbose?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def tenants
|
|
49
|
+
config.tenants.presence || [ get_default_tenant ].compact
|
|
42
50
|
end
|
|
43
51
|
|
|
44
|
-
def
|
|
52
|
+
def get_default_tenant
|
|
53
|
+
# TODO: needs to work with multiple tenanted configs, maybe using ENV["ARTENANT_#{config.name}"]
|
|
45
54
|
tenant = ENV["ARTENANT"]
|
|
46
55
|
|
|
47
56
|
if tenant.present?
|
|
48
57
|
$stdout.puts "Setting current tenant to #{tenant.inspect}" if verbose?
|
|
49
58
|
elsif Rails.env.local?
|
|
50
59
|
tenant = Rails.application.config.active_record_tenanted.default_tenant
|
|
51
|
-
$stdout.puts "Defaulting current tenant to #{tenant.inspect}" if verbose?
|
|
60
|
+
$stdout.puts "Defaulting current tenant for #{config.name.inspect} to #{tenant.inspect}" if verbose?
|
|
52
61
|
else
|
|
53
62
|
tenant = nil
|
|
54
63
|
$stdout.puts "Cannot determine an implicit tenant: ARTENANT not set, and Rails.env is not local." if verbose?
|
|
@@ -64,7 +73,7 @@ module ActiveRecord
|
|
|
64
73
|
end
|
|
65
74
|
|
|
66
75
|
if connection_class.current_tenant.nil?
|
|
67
|
-
connection_class.current_tenant =
|
|
76
|
+
connection_class.current_tenant = get_default_tenant
|
|
68
77
|
else
|
|
69
78
|
connection_class.current_tenant
|
|
70
79
|
end
|
|
@@ -107,7 +116,49 @@ module ActiveRecord
|
|
|
107
116
|
end
|
|
108
117
|
|
|
109
118
|
def verbose?
|
|
110
|
-
|
|
119
|
+
self.class.verbose?
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def register_rake_tasks
|
|
123
|
+
name = config.name
|
|
124
|
+
|
|
125
|
+
desc "Migrate tenanted #{name} databases for current environment"
|
|
126
|
+
task "db:migrate:#{name}" => "load_config" do
|
|
127
|
+
verbose_was = ActiveRecord::Migration.verbose
|
|
128
|
+
ActiveRecord::Migration.verbose = ActiveRecord::Tenanted::DatabaseTasks.verbose?
|
|
129
|
+
|
|
130
|
+
tenant = ENV["ARTENANT"]
|
|
131
|
+
if tenant.present?
|
|
132
|
+
migrate_tenant(tenant)
|
|
133
|
+
else
|
|
134
|
+
migrate_all
|
|
135
|
+
end
|
|
136
|
+
ensure
|
|
137
|
+
ActiveRecord::Migration.verbose = verbose_was
|
|
138
|
+
end
|
|
139
|
+
task "db:migrate" => "db:migrate:#{name}"
|
|
140
|
+
task "db:prepare" => "db:migrate:#{name}"
|
|
141
|
+
|
|
142
|
+
desc "Drop tenanted #{name} databases for current environment"
|
|
143
|
+
task "db:drop:#{name}" => "load_config" do
|
|
144
|
+
verbose_was = ActiveRecord::Migration.verbose
|
|
145
|
+
ActiveRecord::Migration.verbose = ActiveRecord::Tenanted::DatabaseTasks.verbose?
|
|
146
|
+
|
|
147
|
+
tenant = ENV["ARTENANT"]
|
|
148
|
+
if tenant.present?
|
|
149
|
+
drop_tenant(tenant)
|
|
150
|
+
else
|
|
151
|
+
drop_all
|
|
152
|
+
end
|
|
153
|
+
ensure
|
|
154
|
+
ActiveRecord::Migration.verbose = verbose_was
|
|
155
|
+
end
|
|
156
|
+
task "db:drop" => "db:drop:#{name}"
|
|
157
|
+
|
|
158
|
+
# TODO: Rails' database tasks include "db:seed" in the tasks that "db:reset" runs.
|
|
159
|
+
desc "Drop and recreate tenanted #{name} database from its schema for the current environment"
|
|
160
|
+
task "db:reset:#{name}" => [ "db:drop:#{name}", "db:migrate:#{name}" ]
|
|
161
|
+
task "db:reset" => "db:reset:#{name}"
|
|
111
162
|
end
|
|
112
163
|
end
|
|
113
164
|
end
|
|
@@ -148,6 +148,10 @@ module ActiveRecord
|
|
|
148
148
|
end
|
|
149
149
|
|
|
150
150
|
config.after_initialize do
|
|
151
|
+
ActiveRecord::QueryLogs.taggings = ActiveRecord::QueryLogs.taggings.merge(
|
|
152
|
+
tenant: ->(context) { context[:connection].tenant }
|
|
153
|
+
)
|
|
154
|
+
|
|
151
155
|
if defined?(Rails::Console)
|
|
152
156
|
require "rails/commands/console/irb_console"
|
|
153
157
|
Rails::Console::IRBConsole.prepend ActiveRecord::Tenanted::Console::IRBConsole
|
|
@@ -120,7 +120,8 @@ module ActiveRecord
|
|
|
120
120
|
|
|
121
121
|
def create_tenant(tenant_name, if_not_exists: false, &block)
|
|
122
122
|
created_db = false
|
|
123
|
-
|
|
123
|
+
base_config = tenanted_root_config
|
|
124
|
+
adapter = base_config.new_tenant_config(tenant_name).config_adapter
|
|
124
125
|
|
|
125
126
|
adapter.acquire_ready_lock do
|
|
126
127
|
unless adapter.database_exist?
|
|
@@ -128,7 +129,7 @@ module ActiveRecord
|
|
|
128
129
|
|
|
129
130
|
with_tenant(tenant_name) do
|
|
130
131
|
connection_pool(schema_version_check: false)
|
|
131
|
-
ActiveRecord::Tenanted::DatabaseTasks.migrate_tenant(tenant_name)
|
|
132
|
+
ActiveRecord::Tenanted::DatabaseTasks.new(base_config).migrate_tenant(tenant_name)
|
|
132
133
|
end
|
|
133
134
|
|
|
134
135
|
created_db = true
|
|
@@ -28,6 +28,10 @@ module ActiveRecord
|
|
|
28
28
|
ActiveRecord::ConnectionAdapters::BoundSchemaReflection.new(schema_reflection, self)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
def size
|
|
32
|
+
db_config.max_connections
|
|
33
|
+
end
|
|
34
|
+
|
|
31
35
|
def lease_connection(...)
|
|
32
36
|
raise Tenanted::NoTenantError, "Cannot connect to a tenanted database while untenanted (#{@model})."
|
|
33
37
|
end
|
|
@@ -41,10 +41,19 @@ module ActiveRecord
|
|
|
41
41
|
# Raised when an unsupported database adapter is used.
|
|
42
42
|
class UnsupportedDatabaseError < Error; end
|
|
43
43
|
|
|
44
|
+
# Return the constantized connection class configured in `config.active_record_tenanted.connection_class`,
|
|
45
|
+
# or nil if none is configured.
|
|
44
46
|
def self.connection_class
|
|
45
47
|
# TODO: cache this / speed this up
|
|
46
48
|
Rails.application.config.active_record_tenanted.connection_class&.constantize
|
|
47
49
|
end
|
|
50
|
+
|
|
51
|
+
# Return an Array of the tenanted database configurations.
|
|
52
|
+
def self.base_configs(configurations = ActiveRecord::Base.configurations)
|
|
53
|
+
configurations
|
|
54
|
+
.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env, include_hidden: true)
|
|
55
|
+
.select { |c| c.configuration_hash[:tenanted] }
|
|
56
|
+
end
|
|
48
57
|
end
|
|
49
58
|
end
|
|
50
59
|
|
|
@@ -1,78 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
unless ActiveRecord::Tenanted::DatabaseTasks.base_config
|
|
12
|
-
warn "WARNING: No tenanted database found, skipping tenanted migration"
|
|
13
|
-
else
|
|
14
|
-
begin
|
|
15
|
-
verbose_was = ActiveRecord::Migration.verbose
|
|
16
|
-
ActiveRecord::Migration.verbose = ActiveRecord::Tenanted::DatabaseTasks.verbose?
|
|
17
|
-
|
|
18
|
-
ActiveRecord::Tenanted::DatabaseTasks.migrate_tenant
|
|
19
|
-
ensure
|
|
20
|
-
ActiveRecord::Migration.verbose = verbose_was
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
desc "Migrate the database for all existing tenants"
|
|
26
|
-
task "migrate:tenant:all" => "load_config" do
|
|
27
|
-
unless ActiveRecord::Tenanted.connection_class
|
|
28
|
-
warn "ActiveRecord::Tenanted integration is not configured via connection_class"
|
|
29
|
-
next
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
verbose_was = ActiveRecord::Migration.verbose
|
|
33
|
-
ActiveRecord::Migration.verbose = ActiveRecord::Tenanted::DatabaseTasks.verbose?
|
|
34
|
-
|
|
35
|
-
ActiveRecord::Tenanted::DatabaseTasks.migrate_all
|
|
36
|
-
ensure
|
|
37
|
-
ActiveRecord::Migration.verbose = verbose_was
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
desc "Drop and recreate all tenant databases from their schema for the current environment"
|
|
41
|
-
task "reset:tenant" => [ "db:drop:tenant", "db:migrate:tenant" ]
|
|
42
|
-
|
|
43
|
-
desc "Drop all tenanted databases for the current environment"
|
|
44
|
-
task "drop:tenant" => "load_config" do
|
|
45
|
-
unless ActiveRecord::Tenanted::DatabaseTasks.base_config
|
|
46
|
-
warn "WARNING: No tenanted database found, skipping tenanted reset"
|
|
47
|
-
else
|
|
48
|
-
begin
|
|
49
|
-
verbose_was = ActiveRecord::Migration.verbose
|
|
50
|
-
ActiveRecord::Migration.verbose = ActiveRecord::Tenanted::DatabaseTasks.verbose?
|
|
51
|
-
|
|
52
|
-
ActiveRecord::Tenanted::DatabaseTasks.drop_all
|
|
53
|
-
ensure
|
|
54
|
-
ActiveRecord::Migration.verbose = verbose_was
|
|
55
|
-
end
|
|
56
|
-
end
|
|
3
|
+
# Ensure a default tenant is set for database tasks that may need it.
|
|
4
|
+
desc "Set the current tenant to ARTENANT if present, else the environment default"
|
|
5
|
+
task "db:tenant" => "load_config" do
|
|
6
|
+
unless ActiveRecord::Tenanted.connection_class
|
|
7
|
+
warn "ActiveRecord::Tenanted integration is not configured via connection_class"
|
|
8
|
+
next
|
|
57
9
|
end
|
|
58
10
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
unless ActiveRecord::Tenanted.connection_class
|
|
62
|
-
warn "ActiveRecord::Tenanted integration is not configured via connection_class"
|
|
63
|
-
next
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
ActiveRecord::Tenanted::DatabaseTasks.set_current_tenant
|
|
67
|
-
end
|
|
11
|
+
config = ActiveRecord::Tenanted.connection_class.connection_pool.db_config
|
|
12
|
+
ActiveRecord::Tenanted::DatabaseTasks.new(config).set_current_tenant
|
|
68
13
|
end
|
|
69
|
-
|
|
70
|
-
# Decorate database tasks with the tenanted version.
|
|
71
|
-
task "db:migrate" => "db:migrate:tenant:all"
|
|
72
|
-
task "db:prepare" => "db:migrate:tenant:all"
|
|
73
|
-
task "db:reset" => "db:reset:tenant"
|
|
74
|
-
task "db:drop" => "db:drop:tenant"
|
|
75
|
-
|
|
76
|
-
# Ensure a default tenant is set for database tasks that may need it.
|
|
77
14
|
task "db:fixtures:load" => "db:tenant"
|
|
78
15
|
task "db:seed" => "db:tenant"
|
|
16
|
+
|
|
17
|
+
# Create tenanted rake tasks
|
|
18
|
+
ActiveRecord::Tenanted.base_configs(ActiveRecord::DatabaseConfigurations.new(ActiveRecord::Tasks::DatabaseTasks.setup_initial_database_yaml)).each do |config|
|
|
19
|
+
ActiveRecord::Tenanted::DatabaseTasks.new(config).register_rake_tasks
|
|
20
|
+
end
|