ros-apartment 2.8.1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -3,11 +3,3 @@
3
3
  source 'http://rubygems.org'
4
4
 
5
5
  gemspec
6
-
7
- gem 'rails', '>= 3.1.2'
8
- gem 'rubocop'
9
-
10
- group :local do
11
- gem 'guard-rspec', '~> 4.2'
12
- gem 'pry'
13
- end
data/README.md CHANGED
@@ -1,8 +1,7 @@
1
1
  # Apartment
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/ros-apartment.svg)](https://badge.fury.io/rb/apartment)
3
+ [![Gem Version](https://badge.fury.io/rb/ros-apartment.svg)](https://badge.fury.io/rb/ros-apartment)
4
4
  [![Code Climate](https://api.codeclimate.com/v1/badges/b0dc327380bb8438f991/maintainability)](https://codeclimate.com/github/rails-on-services/apartment/maintainability)
5
- [![Build Status](https://travis-ci.org/rails-on-services/apartment.svg?branch=development)](https://travis-ci.org/rails-on-services/apartment)
6
5
 
7
6
  *Multitenancy for Rails and ActiveRecord*
8
7
 
@@ -68,7 +67,7 @@ you need to create a new tenant, you can run the following command:
68
67
  Apartment::Tenant.create('tenant_name')
69
68
  ```
70
69
 
71
- If you're using the [prepend environment](https://github.com/influitive/apartment#handling-environments) config option or you AREN'T using Postgresql Schemas, this will create a tenant in the following format: "#{environment}\_tenant_name".
70
+ If you're using the [prepend environment](https://github.com/rails-on-services/apartment#handling-environments) config option or you AREN'T using Postgresql Schemas, this will create a tenant in the following format: "#{environment}\_tenant_name".
72
71
  In the case of a sqlite database, this will be created in your 'db/' folder. With
73
72
  other databases, the tenant will be created as a new DB within the system.
74
73
 
@@ -101,6 +100,16 @@ switched back at the end of the block to what it was before.
101
100
  There is also `switch!` which doesn't take a block, but it's recommended to use `switch`.
102
101
  To return to the default tenant, you can call `switch` with no arguments.
103
102
 
103
+ #### Multiple Tenants
104
+
105
+ When using schemas, you can also pass in a list of schemas if desired. Any tables defined in a schema earlier in the chain will be referenced first, so this is only useful if you have a schema with only some of the tables defined:
106
+
107
+ ```ruby
108
+ Apartment::Tenant.switch(['tenant_1', 'tenant_2']) do
109
+ # ...
110
+ end
111
+ ```
112
+
104
113
  ### Switching Tenants per request
105
114
 
106
115
  You can have Apartment route to the appropriate tenant by adding some Rack middleware.
@@ -265,7 +274,7 @@ In the examples above, we show the Apartment middleware being appended to the Ra
265
274
  Rails.application.config.middleware.use Apartment::Elevators::Subdomain
266
275
  ```
267
276
 
268
- By default, the Subdomain middleware switches into a Tenant based on the subdomain at the beginning of the request, and when the request is finished, it switches back to the "public" Tenant. This happens in the [Generic](https://github.com/influitive/apartment/blob/development/lib/apartment/elevators/generic.rb#L22) elevator, so all elevators that inherit from this elevator will operate as such.
277
+ By default, the Subdomain middleware switches into a Tenant based on the subdomain at the beginning of the request, and when the request is finished, it switches back to the "public" Tenant. This happens in the [Generic](https://github.com/rails-on-services/apartment/blob/development/lib/apartment/elevators/generic.rb#L22) elevator, so all elevators that inherit from this elevator will operate as such.
269
278
 
270
279
  It's also good to note that Apartment switches back to the "public" tenant any time an error is raised in your application.
271
280
 
@@ -334,7 +343,7 @@ Setting this configuration value to `false` will disable the schema presence che
334
343
 
335
344
  ```ruby
336
345
  Apartment.configure do |config|
337
- tenant_presence_check = false
346
+ config.tenant_presence_check = false
338
347
  end
339
348
  ```
340
349
 
@@ -521,7 +530,7 @@ You can then migrate your tenants using the normal rake task:
521
530
  rake db:migrate
522
531
  ```
523
532
 
524
- This just invokes `Apartment::Tenant.migrate(#{tenant_name})` for each tenant name supplied
533
+ This just invokes `Apartment::Migrator.migrate(#{tenant_name})` for each tenant name supplied
525
534
  from `Apartment.tenant_names`
526
535
 
527
536
  Note that you can disable the default migrating of all tenants with `db:migrate` by setting
data/Rakefile CHANGED
@@ -132,22 +132,8 @@ def my_config
132
132
  config['mysql']
133
133
  end
134
134
 
135
- def activerecord_below_5_2?
136
- ActiveRecord.version.release < Gem::Version.new('5.2.0')
137
- end
138
-
139
- def activerecord_below_6_0?
140
- ActiveRecord.version.release < Gem::Version.new('6.0.0')
141
- end
142
-
143
135
  def migrate
144
- if activerecord_below_5_2?
145
- ActiveRecord::Migrator.migrate('spec/dummy/db/migrate')
146
- elsif activerecord_below_6_0?
147
- ActiveRecord::MigrationContext.new('spec/dummy/db/migrate').migrate
148
- else
149
- # TODO: Figure out if there is any other possibility that can/should be
150
- # passed here as the second argument for the migration context
151
- ActiveRecord::MigrationContext.new('spec/dummy/db/migrate', ActiveRecord::SchemaMigration).migrate
152
- end
136
+ # TODO: Figure out if there is any other possibility that can/should be
137
+ # passed here as the second argument for the migration context
138
+ ActiveRecord::MigrationContext.new('spec/dummy/db/migrate', ActiveRecord::SchemaMigration).migrate
153
139
  end
@@ -1,16 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActiveRecord
4
- # This is monkeypatching activerecord to ensure that whenever a new connection is established it
3
+ module ActiveRecord # :nodoc:
4
+ # This is monkeypatching Active Record to ensure that whenever a new connection is established it
5
5
  # switches to the same tenant as before the connection switching. This problem is more evident when
6
6
  # using read replica in Rails 6
7
7
  module ConnectionHandling
8
- def connected_to_with_tenant(database: nil, role: nil, prevent_writes: false, &blk)
9
- current_tenant = Apartment::Tenant.current
8
+ if ActiveRecord.version.release <= Gem::Version.new('6.2')
9
+ def connected_to_with_tenant(database: nil, role: nil, prevent_writes: false, &blk)
10
+ current_tenant = Apartment::Tenant.current
10
11
 
11
- connected_to_without_tenant(database: database, role: role, prevent_writes: prevent_writes) do
12
- Apartment::Tenant.switch!(current_tenant)
13
- yield(blk)
12
+ connected_to_without_tenant(database: database, role: role, prevent_writes: prevent_writes) do
13
+ Apartment::Tenant.switch!(current_tenant)
14
+ yield(blk)
15
+ end
16
+ end
17
+ else
18
+ def connected_to_with_tenant(role: nil, prevent_writes: false, &blk)
19
+ current_tenant = Apartment::Tenant.current
20
+
21
+ connected_to_without_tenant(role: role, prevent_writes: prevent_writes) do
22
+ Apartment::Tenant.switch!(current_tenant)
23
+ yield(blk)
24
+ end
14
25
  end
15
26
  end
16
27
 
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Style/ClassAndModuleChildren
4
+
5
+ # NOTE: This patch is meant to remove any schema_prefix appart from the ones for
6
+ # excluded models. The schema_prefix would be resolved by apartment's setting
7
+ # of search path
8
+ module Apartment::PostgreSqlAdapterPatch
9
+ def default_sequence_name(table, _column)
10
+ res = super
11
+
12
+ # for JDBC driver, if rescued in super_method, trim leading and trailing quotes
13
+ res.delete!('"') if defined?(JRUBY_VERSION)
14
+
15
+ schema_prefix = "#{Apartment::Tenant.current}."
16
+ default_tenant_prefix = "#{Apartment::Tenant.default_tenant}."
17
+
18
+ # NOTE: Excluded models should always access the sequence from the default
19
+ # tenant schema
20
+ if excluded_model?(table)
21
+ res.sub!(schema_prefix, default_tenant_prefix) if schema_prefix != default_tenant_prefix
22
+ return res
23
+ end
24
+
25
+ res.delete_prefix!(schema_prefix) if res&.starts_with?(schema_prefix)
26
+
27
+ res
28
+ end
29
+
30
+ private
31
+
32
+ def excluded_model?(table)
33
+ Apartment.excluded_models.any? { |m| m.constantize.table_name == table }
34
+ end
35
+ end
36
+
37
+ require 'active_record/connection_adapters/postgresql_adapter'
38
+
39
+ # NOTE: inject this into postgresql adapters
40
+ class ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
41
+ include Apartment::PostgreSqlAdapterPatch
42
+ end
43
+ # rubocop:enable Style/ClassAndModuleChildren
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
- class SchemaMigration < ActiveRecord::Base # :nodoc:
4
+ class SchemaMigration # :nodoc:
5
5
  class << self
6
6
  def table_exists?
7
7
  connection.table_exists?(table_name)
@@ -38,11 +38,9 @@ module Apartment
38
38
  #
39
39
  def connect_to_new(tenant = nil)
40
40
  return reset if tenant.nil?
41
+ raise ActiveRecord::StatementInvalid, "Could not find schema #{tenant}" unless schema_exists?(tenant)
41
42
 
42
- tenant = tenant.to_s
43
- raise ActiveRecord::StatementInvalid, "Could not find schema #{tenant}" unless tenant_exists?(tenant)
44
-
45
- @current = tenant
43
+ @current = tenant.is_a?(Array) ? tenant.map(&:to_s) : tenant.to_s
46
44
  Apartment.connection.schema_search_path = full_search_path
47
45
  rescue ActiveRecord::StatementInvalid, ActiveRecord::JDBCError
48
46
  raise TenantNotFound, "One of the following schema(s) is invalid: #{full_search_path}"
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'apartment/adapters/abstract_adapter'
4
+ require 'apartment/active_record/postgresql_adapter'
4
5
 
5
6
  module Apartment
6
7
  module Tenant
@@ -41,7 +42,6 @@ module Apartment
41
42
  def reset
42
43
  @current = default_tenant
43
44
  Apartment.connection.schema_search_path = full_search_path
44
- reset_sequence_names
45
45
  end
46
46
 
47
47
  def init
@@ -72,20 +72,17 @@ module Apartment
72
72
  #
73
73
  def connect_to_new(tenant = nil)
74
74
  return reset if tenant.nil?
75
+ raise ActiveRecord::StatementInvalid, "Could not find schema #{tenant}" unless schema_exists?(tenant)
75
76
 
76
- tenant = tenant.to_s
77
- raise ActiveRecord::StatementInvalid, "Could not find schema #{tenant}" unless tenant_exists?(tenant)
78
-
79
- @current = tenant
77
+ @current = tenant.is_a?(Array) ? tenant.map(&:to_s) : tenant.to_s
80
78
  Apartment.connection.schema_search_path = full_search_path
81
79
 
82
80
  # When the PostgreSQL version is < 9.3,
83
81
  # there is a issue for prepared statement with changing search_path.
84
82
  # https://www.postgresql.org/docs/9.3/static/sql-prepare.html
85
83
  Apartment.connection.clear_cache! if postgresql_version < 90_300
86
- reset_sequence_names
87
- rescue *rescuable_exceptions
88
- raise TenantNotFound, "One of the following schema(s) is invalid: \"#{tenant}\" #{full_search_path}"
84
+ rescue *rescuable_exceptions => e
85
+ raise_schema_connect_to_new(tenant, e)
89
86
  end
90
87
 
91
88
  private
@@ -132,22 +129,17 @@ module Apartment
132
129
  Apartment.connection.send(:postgresql_version)
133
130
  end
134
131
 
135
- def reset_sequence_names
136
- # sequence_name contains the schema, so it must be reset after switch
137
- # There is `reset_sequence_name`, but that method actually goes to the database
138
- # to find out the new name. Therefore, we do this hack to only unset the name,
139
- # and it will be dynamically found the next time it is needed
140
- descendants_to_unset = ActiveRecord::Base.descendants
141
- .select { |c| c.instance_variable_defined?(:@sequence_name) }
142
- .reject do |c|
143
- c.instance_variable_defined?(:@explicit_sequence_name) &&
144
- c.instance_variable_get(:@explicit_sequence_name)
145
- end
146
- descendants_to_unset.each do |c|
147
- # NOTE: due to this https://github.com/rails-on-services/apartment/issues/81
148
- # unreproduceable error we're checking before trying to remove it
149
- c.remove_instance_variable :@sequence_name if c.instance_variable_defined?(:@sequence_name)
150
- end
132
+ def schema_exists?(schemas)
133
+ return true unless Apartment.tenant_presence_check
134
+
135
+ Array(schemas).all? { |schema| Apartment.connection.schema_exists?(schema.to_s) }
136
+ end
137
+
138
+ def raise_schema_connect_to_new(tenant, exception)
139
+ raise TenantNotFound, <<~EXCEPTION_MESSAGE
140
+ Could not set search path to schemas, they may be invalid: "#{tenant}" #{full_search_path}.
141
+ Original error: #{exception.class}: #{exception}
142
+ EXCEPTION_MESSAGE
151
143
  end
152
144
  end
153
145
 
@@ -158,6 +150,7 @@ module Apartment
158
150
  /SET lock_timeout/i, # new in postgresql 9.3
159
151
  /SET row_security/i, # new in postgresql 9.5
160
152
  /SET idle_in_transaction_session_timeout/i, # new in postgresql 9.6
153
+ /SET default_table_access_method/i, # new in postgresql 12
161
154
  /CREATE SCHEMA public/i,
162
155
  /COMMENT ON SCHEMA public/i
163
156
 
@@ -224,7 +217,7 @@ module Apartment
224
217
 
225
218
  # Temporary set Postgresql related environment variables if there are in @config
226
219
  #
227
- def with_pg_env(&block)
220
+ def with_pg_env
228
221
  pghost = ENV['PGHOST']
229
222
  pgport = ENV['PGPORT']
230
223
  pguser = ENV['PGUSER']
@@ -235,7 +228,7 @@ module Apartment
235
228
  ENV['PGUSER'] = @config[:username].to_s if @config[:username]
236
229
  ENV['PGPASSWORD'] = @config[:password].to_s if @config[:password]
237
230
 
238
- block.call
231
+ yield
239
232
  ensure
240
233
  ENV['PGHOST'] = pghost
241
234
  ENV['PGPORT'] = pgport
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apartment/adapters/mysql2_adapter'
4
+
5
+ module Apartment
6
+ # Helper module to decide wether to use trilogy adapter or trilogy adapter with schemas
7
+ module Tenant
8
+ def self.trilogy_adapter(config)
9
+ if Apartment.use_schemas
10
+ Adapters::TrilogySchemaAdapter.new(config)
11
+ else
12
+ Adapters::TrilogyAdapter.new(config)
13
+ end
14
+ end
15
+ end
16
+
17
+ module Adapters
18
+ class TrilogyAdapter < Mysql2Adapter
19
+ protected
20
+
21
+ def rescue_from
22
+ Trilogy::Error
23
+ end
24
+ end
25
+
26
+ class TrilogySchemaAdapter < Mysql2SchemaAdapter
27
+ end
28
+ end
29
+ end
@@ -1,21 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # A workaround to get `reload!` to also call Apartment::Tenant.init
4
- # This is unfortunate, but I haven't figured out how to hook into the reload process *after* files are reloaded
5
-
6
- # reloads the environment
7
- # rubocop:disable Style/OptionalBooleanParameter
8
- def reload!(print = true)
9
- puts 'Reloading...' if print
10
-
11
- # This triggers the to_prepare callbacks
12
- ActionDispatch::Callbacks.new(proc {}).call({})
13
- # Manually init Apartment again once classes are reloaded
14
- Apartment::Tenant.init
15
- true
16
- end
17
- # rubocop:enable Style/OptionalBooleanParameter
18
-
19
3
  def st(schema_name = nil)
20
4
  if schema_name.nil?
21
5
  tenant_list.each { |t| puts t }
@@ -8,26 +8,38 @@ module Apartment
8
8
  # NOTE: for some reason, if the method definition is not here, then the custom debug method is not called
9
9
  # rubocop:disable Lint/UselessMethodDefinition
10
10
  def sql(event)
11
- super(event)
11
+ super
12
12
  end
13
13
  # rubocop:enable Lint/UselessMethodDefinition
14
14
 
15
15
  private
16
16
 
17
- def debug(progname = nil, &block)
17
+ def debug(progname = nil, &blk)
18
18
  progname = " #{apartment_log}#{progname}" unless progname.nil?
19
19
 
20
- super(progname, &block)
20
+ super
21
21
  end
22
22
 
23
23
  def apartment_log
24
- database = color("[#{Apartment.connection.raw_connection.db}] ", ActiveSupport::LogSubscriber::MAGENTA, true)
25
- schema = nil
26
- unless Apartment.connection.schema_search_path.nil?
27
- schema = color("[#{Apartment.connection.schema_search_path.tr('"', '')}] ",
28
- ActiveSupport::LogSubscriber::YELLOW, true)
29
- end
24
+ database = color("[#{database_name}] ", ActiveSupport::LogSubscriber::MAGENTA, bold: true)
25
+ schema = current_search_path
26
+ schema = color("[#{schema.tr('"', '')}] ", ActiveSupport::LogSubscriber::YELLOW, bold: true) unless schema.nil?
30
27
  "#{database}#{schema}"
31
28
  end
29
+
30
+ def current_search_path
31
+ if Apartment.connection.respond_to?(:schema_search_path)
32
+ Apartment.connection.schema_search_path
33
+ else
34
+ Apartment::Tenant.current # all others
35
+ end
36
+ end
37
+
38
+ def database_name
39
+ db_name = Apartment.connection.raw_connection.try(:db) # PostgreSQL, PostGIS
40
+ db_name ||= Apartment.connection.raw_connection.try(:query_options)&.dig(:database) # Mysql
41
+ db_name ||= Apartment.connection.current_database # Failover
42
+ db_name
43
+ end
32
44
  end
33
45
  end
@@ -13,40 +13,22 @@ module Apartment
13
13
 
14
14
  migration_scope_block = ->(migration) { ENV['SCOPE'].blank? || (ENV['SCOPE'] == migration.scope) }
15
15
 
16
- if activerecord_below_5_2?
17
- ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, version, &migration_scope_block)
18
- else
19
- ActiveRecord::Base.connection.migration_context.migrate(version, &migration_scope_block)
20
- end
16
+ ActiveRecord::Base.connection.migration_context.migrate(version, &migration_scope_block)
21
17
  end
22
18
  end
23
19
 
24
20
  # Migrate up/down to a specific version
25
21
  def run(direction, database, version)
26
22
  Tenant.switch(database) do
27
- if activerecord_below_5_2?
28
- ActiveRecord::Migrator.run(direction, ActiveRecord::Migrator.migrations_paths, version)
29
- else
30
- ActiveRecord::Base.connection.migration_context.run(direction, version)
31
- end
23
+ ActiveRecord::Base.connection.migration_context.run(direction, version)
32
24
  end
33
25
  end
34
26
 
35
27
  # rollback latest migration `step` number of times
36
28
  def rollback(database, step = 1)
37
29
  Tenant.switch(database) do
38
- if activerecord_below_5_2?
39
- ActiveRecord::Migrator.rollback(ActiveRecord::Migrator.migrations_paths, step)
40
- else
41
- ActiveRecord::Base.connection.migration_context.rollback(step)
42
- end
30
+ ActiveRecord::Base.connection.migration_context.rollback(step)
43
31
  end
44
32
  end
45
-
46
- private
47
-
48
- def activerecord_below_5_2?
49
- ActiveRecord.version.release < Gem::Version.new('5.2.0')
50
- end
51
33
  end
52
34
  end
@@ -17,7 +17,9 @@ module Apartment
17
17
  cache_key = if key.is_a? String
18
18
  "#{Apartment::Tenant.current}_#{key}"
19
19
  else
20
- [Apartment::Tenant.current] + key
20
+ # NOTE: In Rails 6.0.4 we start receiving an ActiveRecord::Reflection::BelongsToReflection
21
+ # as the key, which wouldn't work well with an array.
22
+ [Apartment::Tenant.current] + Array.wrap(key)
21
23
  end
22
24
  cache = @find_by_statement_cache[connection.prepared_statements]
23
25
  cache.compute_if_absent(cache_key) { ActiveRecord::StatementCache.create(connection, &block) }
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'rails'
4
4
  require 'apartment/tenant'
5
- require 'apartment/reloader'
6
5
 
7
6
  module Apartment
8
7
  class Railtie < Rails::Railtie
@@ -32,7 +31,7 @@ module Apartment
32
31
  #
33
32
  config.to_prepare do
34
33
  next if ARGV.any? { |arg| arg =~ /\Aassets:(?:precompile|clean)\z/ }
35
- next if ARGV.any? { |arg| arg == 'webpacker:compile' }
34
+ next if ARGV.any?('webpacker:compile')
36
35
  next if ENV['APARTMENT_DISABLE_INIT']
37
36
 
38
37
  begin
@@ -48,7 +47,12 @@ module Apartment
48
47
  config.after_initialize do
49
48
  # NOTE: Load the custom log subscriber if enabled
50
49
  if Apartment.active_record_log
51
- ActiveSupport::Notifications.unsubscribe 'sql.active_record'
50
+ ActiveSupport::Notifications.notifier.listeners_for('sql.active_record').each do |listener|
51
+ next unless listener.instance_variable_get('@delegate').is_a?(ActiveRecord::LogSubscriber)
52
+
53
+ ActiveSupport::Notifications.unsubscribe listener
54
+ end
55
+
52
56
  Apartment::LogSubscriber.attach_to :active_record
53
57
  end
54
58
  end
@@ -60,25 +64,5 @@ module Apartment
60
64
  load 'tasks/apartment.rake'
61
65
  require 'apartment/tasks/enhancements' if Apartment.db_migrate_tenants
62
66
  end
63
-
64
- #
65
- # The following initializers are a workaround to the fact that I can't properly hook into the rails reloader
66
- # Note this is technically valid for any environment where cache_classes is false, for us, it's just development
67
- #
68
- if Rails.env.development?
69
-
70
- # Apartment::Reloader is middleware to initialize things properly on each request to dev
71
- initializer 'apartment.init' do |app|
72
- app.config.middleware.use Apartment::Reloader
73
- end
74
-
75
- # Overrides reload! to also call Apartment::Tenant.init as well
76
- # so that the reloaded classes have the proper table_names
77
- # rubocop:disable Lint/Debugger
78
- console do
79
- require 'apartment/console'
80
- end
81
- # rubocop:enable Lint/Debugger
82
- end
83
67
  end
84
68
  end
@@ -36,5 +36,17 @@ module Apartment
36
36
  rescue Apartment::TenantExists => e
37
37
  puts "Tried to create already existing tenant: #{e}"
38
38
  end
39
+
40
+ def self.migrate_tenant(tenant_name)
41
+ strategy = Apartment.db_migrate_tenant_missing_strategy
42
+ create_tenant(tenant_name) if strategy == :create_tenant
43
+
44
+ puts("Migrating #{tenant_name} tenant")
45
+ Apartment::Migrator.migrate tenant_name
46
+ rescue Apartment::TenantNotFound => e
47
+ raise e if strategy == :raise_exception
48
+
49
+ puts e.message
50
+ end
39
51
  end
40
52
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Apartment
4
- VERSION = '2.8.1'
4
+ VERSION = '3.1.0'
5
5
  end
data/lib/apartment.rb CHANGED
@@ -7,15 +7,9 @@ require 'active_record'
7
7
  require 'apartment/tenant'
8
8
 
9
9
  require_relative 'apartment/log_subscriber'
10
-
11
- if ActiveRecord.version.release >= Gem::Version.new('6.0')
12
- require_relative 'apartment/active_record/connection_handling'
13
- end
14
-
15
- if ActiveRecord.version.release >= Gem::Version.new('6.1')
16
- require_relative 'apartment/active_record/schema_migration'
17
- require_relative 'apartment/active_record/internal_metadata'
18
- end
10
+ require_relative 'apartment/active_record/connection_handling'
11
+ require_relative 'apartment/active_record/schema_migration'
12
+ require_relative 'apartment/active_record/internal_metadata'
19
13
 
20
14
  # Apartment main definitions
21
15
  module Apartment
@@ -27,20 +21,16 @@ module Apartment
27
21
 
28
22
  WRITER_METHODS = %i[tenant_names database_schema_file excluded_models
29
23
  persistent_schemas connection_class
30
- db_migrate_tenants seed_data_file
24
+ db_migrate_tenants db_migrate_tenant_missing_strategy seed_data_file
31
25
  parallel_migration_threads pg_excluded_names].freeze
32
26
 
33
27
  attr_accessor(*ACCESSOR_METHODS)
34
28
  attr_writer(*WRITER_METHODS)
35
29
 
36
- if ActiveRecord.version.release >= Gem::Version.new('6.1')
37
- def_delegators :connection_class, :connection, :connection_db_config, :establish_connection
30
+ def_delegators :connection_class, :connection, :connection_db_config, :establish_connection
38
31
 
39
- def connection_config
40
- connection_db_config.configuration_hash
41
- end
42
- else
43
- def_delegators :connection_class, :connection, :connection_config, :establish_connection
32
+ def connection_config
33
+ connection_db_config.configuration_hash
44
34
  end
45
35
 
46
36
  # configure apartment with available options
@@ -72,6 +62,21 @@ module Apartment
72
62
  @db_migrate_tenants = true
73
63
  end
74
64
 
65
+ # How to handle tenant missing on db:migrate
66
+ # defaults to :rescue_exception
67
+ # available options: rescue_exception, raise_exception, create_tenant
68
+ def db_migrate_tenant_missing_strategy
69
+ valid = %i[rescue_exception raise_exception create_tenant]
70
+ value = @db_migrate_tenant_missing_strategy || :rescue_exception
71
+
72
+ return value if valid.include?(value)
73
+
74
+ key_name = 'config.db_migrate_tenant_missing_strategy'
75
+ opt_names = valid.join(', ')
76
+
77
+ raise ApartmentError, "Option #{value} not valid for `#{key_name}`. Use one of #{opt_names}"
78
+ end
79
+
75
80
  # Default to empty array
76
81
  def excluded_models
77
82
  @excluded_models || []
@@ -92,13 +97,13 @@ module Apartment
92
97
  def database_schema_file
93
98
  return @database_schema_file if defined?(@database_schema_file)
94
99
 
95
- @database_schema_file = Rails.root.join('db', 'schema.rb')
100
+ @database_schema_file = Rails.root.join('db/schema.rb')
96
101
  end
97
102
 
98
103
  def seed_data_file
99
104
  return @seed_data_file if defined?(@seed_data_file)
100
105
 
101
- @seed_data_file = Rails.root.join('db', 'seeds.rb')
106
+ @seed_data_file = Rails.root.join('db/seeds.rb')
102
107
  end
103
108
 
104
109
  def pg_excluded_names