ros-apartment 2.3.0 → 2.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (163) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +78 -0
  3. data/.github/workflows/changelog.yml +63 -0
  4. data/.github/workflows/reviewdog.yml +22 -0
  5. data/.pryrc +5 -3
  6. data/.rubocop.yml +32 -0
  7. data/.rubocop_todo.yml +233 -0
  8. data/.ruby-version +1 -0
  9. data/.story_branch.yml +5 -0
  10. data/Appraisals +29 -42
  11. data/CHANGELOG.md +963 -0
  12. data/Gemfile +2 -7
  13. data/Guardfile +3 -16
  14. data/HISTORY.md +159 -68
  15. data/README.md +101 -21
  16. data/Rakefile +39 -22
  17. data/TODO.md +0 -1
  18. data/gemfiles/rails_5_2.gemfile +0 -5
  19. data/gemfiles/rails_6_0.gemfile +4 -9
  20. data/gemfiles/rails_6_1.gemfile +17 -0
  21. data/gemfiles/rails_7_0.gemfile +17 -0
  22. data/gemfiles/rails_master.gemfile +3 -8
  23. data/lib/apartment/active_record/connection_handling.rb +20 -0
  24. data/lib/apartment/active_record/internal_metadata.rb +9 -0
  25. data/lib/apartment/active_record/postgresql_adapter.rb +39 -0
  26. data/lib/apartment/active_record/schema_migration.rb +11 -0
  27. data/lib/apartment/adapters/abstract_adapter.rb +52 -46
  28. data/lib/apartment/adapters/abstract_jdbc_adapter.rb +5 -3
  29. data/lib/apartment/adapters/jdbc_mysql_adapter.rb +3 -3
  30. data/lib/apartment/adapters/jdbc_postgresql_adapter.rb +19 -13
  31. data/lib/apartment/adapters/mysql2_adapter.rb +15 -9
  32. data/lib/apartment/adapters/postgis_adapter.rb +3 -2
  33. data/lib/apartment/adapters/postgresql_adapter.rb +79 -31
  34. data/lib/apartment/adapters/sqlite3_adapter.rb +18 -8
  35. data/lib/apartment/console.rb +23 -11
  36. data/lib/apartment/custom_console.rb +42 -0
  37. data/lib/apartment/deprecation.rb +2 -1
  38. data/lib/apartment/elevators/domain.rb +4 -3
  39. data/lib/apartment/elevators/first_subdomain.rb +3 -2
  40. data/lib/apartment/elevators/generic.rb +4 -3
  41. data/lib/apartment/elevators/host.rb +6 -1
  42. data/lib/apartment/elevators/host_hash.rb +6 -2
  43. data/lib/apartment/elevators/subdomain.rb +9 -5
  44. data/lib/apartment/log_subscriber.rb +33 -0
  45. data/lib/apartment/migrator.rb +4 -3
  46. data/lib/apartment/model.rb +29 -0
  47. data/lib/apartment/railtie.rb +16 -20
  48. data/lib/apartment/tasks/enhancements.rb +4 -6
  49. data/lib/apartment/tasks/task_helper.rb +52 -0
  50. data/lib/apartment/tenant.rb +7 -10
  51. data/lib/apartment/version.rb +3 -1
  52. data/lib/apartment.rb +56 -15
  53. data/lib/generators/apartment/install/install_generator.rb +4 -3
  54. data/lib/generators/apartment/install/templates/apartment.rb +10 -3
  55. data/lib/tasks/apartment.rake +48 -87
  56. data/ros-apartment.gemspec +59 -0
  57. metadata +148 -240
  58. data/.travis.yml +0 -65
  59. data/apartment.gemspec +0 -47
  60. data/gemfiles/rails_4_2.gemfile +0 -23
  61. data/gemfiles/rails_5_0.gemfile +0 -22
  62. data/gemfiles/rails_5_1.gemfile +0 -22
  63. data/lib/apartment/reloader.rb +0 -21
  64. data/spec/adapters/jdbc_mysql_adapter_spec.rb +0 -19
  65. data/spec/adapters/jdbc_postgresql_adapter_spec.rb +0 -41
  66. data/spec/adapters/mysql2_adapter_spec.rb +0 -59
  67. data/spec/adapters/postgresql_adapter_spec.rb +0 -61
  68. data/spec/adapters/sqlite3_adapter_spec.rb +0 -83
  69. data/spec/apartment_spec.rb +0 -11
  70. data/spec/config/database.yml.sample +0 -49
  71. data/spec/dummy/Rakefile +0 -7
  72. data/spec/dummy/app/controllers/application_controller.rb +0 -6
  73. data/spec/dummy/app/helpers/application_helper.rb +0 -2
  74. data/spec/dummy/app/models/company.rb +0 -3
  75. data/spec/dummy/app/models/user.rb +0 -3
  76. data/spec/dummy/app/views/application/index.html.erb +0 -1
  77. data/spec/dummy/app/views/layouts/application.html.erb +0 -14
  78. data/spec/dummy/config/application.rb +0 -49
  79. data/spec/dummy/config/boot.rb +0 -11
  80. data/spec/dummy/config/database.yml.sample +0 -44
  81. data/spec/dummy/config/environment.rb +0 -5
  82. data/spec/dummy/config/environments/development.rb +0 -28
  83. data/spec/dummy/config/environments/production.rb +0 -51
  84. data/spec/dummy/config/environments/test.rb +0 -34
  85. data/spec/dummy/config/initializers/apartment.rb +0 -4
  86. data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
  87. data/spec/dummy/config/initializers/inflections.rb +0 -10
  88. data/spec/dummy/config/initializers/mime_types.rb +0 -5
  89. data/spec/dummy/config/initializers/secret_token.rb +0 -7
  90. data/spec/dummy/config/initializers/session_store.rb +0 -8
  91. data/spec/dummy/config/locales/en.yml +0 -5
  92. data/spec/dummy/config/routes.rb +0 -3
  93. data/spec/dummy/config.ru +0 -4
  94. data/spec/dummy/db/migrate/20110613152810_create_dummy_models.rb +0 -39
  95. data/spec/dummy/db/migrate/20111202022214_create_table_books.rb +0 -14
  96. data/spec/dummy/db/migrate/20180415260934_create_public_tokens.rb +0 -13
  97. data/spec/dummy/db/schema.rb +0 -55
  98. data/spec/dummy/db/seeds/import.rb +0 -5
  99. data/spec/dummy/db/seeds.rb +0 -5
  100. data/spec/dummy/db/test.sqlite3 +0 -0
  101. data/spec/dummy/public/404.html +0 -26
  102. data/spec/dummy/public/422.html +0 -26
  103. data/spec/dummy/public/500.html +0 -26
  104. data/spec/dummy/public/favicon.ico +0 -0
  105. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  106. data/spec/dummy/script/rails +0 -6
  107. data/spec/dummy_engine/.gitignore +0 -8
  108. data/spec/dummy_engine/Gemfile +0 -15
  109. data/spec/dummy_engine/Rakefile +0 -34
  110. data/spec/dummy_engine/bin/rails +0 -12
  111. data/spec/dummy_engine/config/initializers/apartment.rb +0 -51
  112. data/spec/dummy_engine/dummy_engine.gemspec +0 -24
  113. data/spec/dummy_engine/lib/dummy_engine/engine.rb +0 -4
  114. data/spec/dummy_engine/lib/dummy_engine/version.rb +0 -3
  115. data/spec/dummy_engine/lib/dummy_engine.rb +0 -4
  116. data/spec/dummy_engine/test/dummy/Rakefile +0 -6
  117. data/spec/dummy_engine/test/dummy/config/application.rb +0 -22
  118. data/spec/dummy_engine/test/dummy/config/boot.rb +0 -5
  119. data/spec/dummy_engine/test/dummy/config/database.yml +0 -25
  120. data/spec/dummy_engine/test/dummy/config/environment.rb +0 -5
  121. data/spec/dummy_engine/test/dummy/config/environments/development.rb +0 -37
  122. data/spec/dummy_engine/test/dummy/config/environments/production.rb +0 -78
  123. data/spec/dummy_engine/test/dummy/config/environments/test.rb +0 -39
  124. data/spec/dummy_engine/test/dummy/config/initializers/assets.rb +0 -8
  125. data/spec/dummy_engine/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  126. data/spec/dummy_engine/test/dummy/config/initializers/cookies_serializer.rb +0 -3
  127. data/spec/dummy_engine/test/dummy/config/initializers/filter_parameter_logging.rb +0 -4
  128. data/spec/dummy_engine/test/dummy/config/initializers/inflections.rb +0 -16
  129. data/spec/dummy_engine/test/dummy/config/initializers/mime_types.rb +0 -4
  130. data/spec/dummy_engine/test/dummy/config/initializers/session_store.rb +0 -3
  131. data/spec/dummy_engine/test/dummy/config/initializers/wrap_parameters.rb +0 -14
  132. data/spec/dummy_engine/test/dummy/config/locales/en.yml +0 -23
  133. data/spec/dummy_engine/test/dummy/config/routes.rb +0 -56
  134. data/spec/dummy_engine/test/dummy/config/secrets.yml +0 -22
  135. data/spec/dummy_engine/test/dummy/config.ru +0 -4
  136. data/spec/examples/connection_adapter_examples.rb +0 -42
  137. data/spec/examples/generic_adapter_custom_configuration_example.rb +0 -95
  138. data/spec/examples/generic_adapter_examples.rb +0 -163
  139. data/spec/examples/schema_adapter_examples.rb +0 -234
  140. data/spec/integration/apartment_rake_integration_spec.rb +0 -107
  141. data/spec/integration/query_caching_spec.rb +0 -81
  142. data/spec/integration/use_within_an_engine_spec.rb +0 -28
  143. data/spec/schemas/v1.rb +0 -16
  144. data/spec/schemas/v2.rb +0 -43
  145. data/spec/schemas/v3.rb +0 -49
  146. data/spec/spec_helper.rb +0 -61
  147. data/spec/support/apartment_helpers.rb +0 -43
  148. data/spec/support/capybara_sessions.rb +0 -15
  149. data/spec/support/config.rb +0 -10
  150. data/spec/support/contexts.rb +0 -52
  151. data/spec/support/requirements.rb +0 -35
  152. data/spec/support/setup.rb +0 -46
  153. data/spec/tasks/apartment_rake_spec.rb +0 -129
  154. data/spec/tenant_spec.rb +0 -190
  155. data/spec/unit/config_spec.rb +0 -112
  156. data/spec/unit/elevators/domain_spec.rb +0 -32
  157. data/spec/unit/elevators/first_subdomain_spec.rb +0 -24
  158. data/spec/unit/elevators/generic_spec.rb +0 -54
  159. data/spec/unit/elevators/host_hash_spec.rb +0 -32
  160. data/spec/unit/elevators/host_spec.rb +0 -89
  161. data/spec/unit/elevators/subdomain_spec.rb +0 -76
  162. data/spec/unit/migrator_spec.rb +0 -77
  163. data/spec/unit/reloader_spec.rb +0 -24
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'apartment/adapters/abstract_adapter'
2
4
 
3
5
  module Apartment
@@ -16,8 +18,10 @@ module Apartment
16
18
  end
17
19
 
18
20
  def drop(tenant)
19
- raise TenantNotFound,
20
- "The tenant #{environmentify(tenant)} cannot be found." unless File.exists?(database_file(tenant))
21
+ unless File.exist?(database_file(tenant))
22
+ raise TenantNotFound,
23
+ "The tenant #{environmentify(tenant)} cannot be found."
24
+ end
21
25
 
22
26
  File.delete(database_file(tenant))
23
27
  end
@@ -26,18 +30,24 @@ module Apartment
26
30
  File.basename(Apartment.connection.instance_variable_get(:@config)[:database], '.sqlite3')
27
31
  end
28
32
 
29
- protected
33
+ protected
30
34
 
31
35
  def connect_to_new(tenant)
32
- raise TenantNotFound,
33
- "The tenant #{environmentify(tenant)} cannot be found." unless File.exists?(database_file(tenant))
36
+ return reset if tenant.nil?
37
+
38
+ unless File.exist?(database_file(tenant))
39
+ raise TenantNotFound,
40
+ "The tenant #{environmentify(tenant)} cannot be found."
41
+ end
34
42
 
35
43
  super database_file(tenant)
36
44
  end
37
45
 
38
46
  def create_tenant(tenant)
39
- raise TenantExists,
40
- "The tenant #{environmentify(tenant)} already exists." if File.exists?(database_file(tenant))
47
+ if File.exist?(database_file(tenant))
48
+ raise TenantExists,
49
+ "The tenant #{environmentify(tenant)} already exists."
50
+ end
41
51
 
42
52
  begin
43
53
  f = File.new(database_file(tenant), File::CREAT)
@@ -46,7 +56,7 @@ module Apartment
46
56
  end
47
57
  end
48
58
 
49
- private
59
+ private
50
60
 
51
61
  def database_file(tenant)
52
62
  "#{@default_dir}/#{environmentify(tenant)}.sqlite3"
@@ -1,12 +1,24 @@
1
- # A workaraound to get `reload!` to also call Apartment::Tenant.init
2
- # This is unfortunate, but I haven't figured out how to hook into the reload process *after* files are reloaded
3
-
4
- # reloads the environment
5
- def reload!(print=true)
6
- puts "Reloading..." if print
7
- # This triggers the to_prepare callbacks
8
- ActionDispatch::Callbacks.new(Proc.new {}).call({})
9
- # Manually init Apartment again once classes are reloaded
10
- Apartment::Tenant.init
11
- true
1
+ # frozen_string_literal: true
2
+
3
+ def st(schema_name = nil)
4
+ if schema_name.nil?
5
+ tenant_list.each { |t| puts t }
6
+
7
+ elsif tenant_list.include? schema_name
8
+ Apartment::Tenant.switch!(schema_name)
9
+ else
10
+ puts "Tenant #{schema_name} is not part of the tenant list"
11
+
12
+ end
13
+ end
14
+
15
+ def tenant_list
16
+ tenant_list = [Apartment.default_tenant]
17
+ tenant_list += Apartment.tenant_names
18
+ tenant_list.uniq
19
+ end
20
+
21
+ def tenant_info_msg
22
+ puts "Available Tenants: #{tenant_list}\n"
23
+ puts "Use `st 'tenant'` to switch tenants & `tenant_list` to see list\n"
12
24
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'console'
4
+
5
+ module Apartment
6
+ module CustomConsole
7
+ begin
8
+ require 'pry-rails'
9
+ rescue LoadError
10
+ # rubocop:disable Layout/LineLength
11
+ puts '[Failed to load pry-rails] If you want to use Apartment custom prompt you need to add pry-rails to your gemfile'
12
+ # rubocop:enable Layout/LineLength
13
+ end
14
+
15
+ desc = "Includes the current Rails environment and project folder name.\n" \
16
+ '[1] [project_name][Rails.env][Apartment::Tenant.current] pry(main)>'
17
+
18
+ prompt_procs = [
19
+ proc { |target_self, nest_level, pry| prompt_contents(pry, target_self, nest_level, '>') },
20
+ proc { |target_self, nest_level, pry| prompt_contents(pry, target_self, nest_level, '*') }
21
+ ]
22
+
23
+ if Gem::Version.new(Pry::VERSION) >= Gem::Version.new('0.13')
24
+ Pry.config.prompt = Pry::Prompt.new 'ros', desc, prompt_procs
25
+ else
26
+ Pry::Prompt.add 'ros', desc, %w[> *] do |target_self, nest_level, pry, sep|
27
+ prompt_contents(pry, target_self, nest_level, sep)
28
+ end
29
+ Pry.config.prompt = Pry::Prompt[:ros][:value]
30
+ end
31
+
32
+ Pry.config.hooks.add_hook(:when_started, 'startup message') do
33
+ tenant_info_msg
34
+ end
35
+
36
+ def self.prompt_contents(pry, target_self, nest_level, sep)
37
+ "[#{pry.input_ring.size}] [#{PryRails::Prompt.formatted_env}][#{Apartment::Tenant.current}] " \
38
+ "#{pry.config.prompt_name}(#{Pry.view_clip(target_self)})" \
39
+ "#{":#{nest_level}" unless nest_level.zero?}#{sep} "
40
+ end
41
+ end
42
+ end
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/deprecation'
2
4
 
3
5
  module Apartment
4
6
  module Deprecation
5
-
6
7
  def self.warn(message)
7
8
  ActiveSupport::Deprecation.warn message
8
9
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'apartment/elevators/generic'
2
4
 
3
5
  module Apartment
@@ -8,14 +10,13 @@ module Apartment
8
10
  # eg. example.com => example
9
11
  # www.example.bc.ca => example
10
12
  # a.example.bc.ca => a
11
- #
13
+ #
12
14
  #
13
15
  class Domain < Generic
14
-
15
16
  def parse_tenant_name(request)
16
17
  return nil if request.host.blank?
17
18
 
18
- request.host.match(/(www\.)?(?<sld>[^.]*)/)["sld"]
19
+ request.host.match(/(www\.)?(?<sld>[^.]*)/)['sld']
19
20
  end
20
21
  end
21
22
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'apartment/elevators/subdomain'
2
4
 
3
5
  module Apartment
@@ -8,10 +10,9 @@ module Apartment
8
10
  # - example1.domain.com => example1
9
11
  # - example2.something.domain.com => example2
10
12
  class FirstSubdomain < Subdomain
11
-
12
13
  def parse_tenant_name(request)
13
14
  super.split('.')[0] unless super.nil?
14
15
  end
15
16
  end
16
17
  end
17
- end
18
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/request'
2
4
  require 'apartment/tenant'
3
5
 
@@ -6,7 +8,6 @@ module Apartment
6
8
  # Provides a rack based tenant switching solution based on request
7
9
  #
8
10
  class Generic
9
-
10
11
  def initialize(app, processor = nil)
11
12
  @app = app
12
13
  @processor = processor || method(:parse_tenant_name)
@@ -24,8 +25,8 @@ module Apartment
24
25
  end
25
26
  end
26
27
 
27
- def parse_tenant_name(request)
28
- raise "Override"
28
+ def parse_tenant_name(_request)
29
+ raise 'Override'
29
30
  end
30
31
  end
31
32
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'apartment/elevators/generic'
2
4
 
3
5
  module Apartment
@@ -16,15 +18,18 @@ module Apartment
16
18
  @ignored_first_subdomains ||= []
17
19
  end
18
20
 
21
+ # rubocop:disable Style/TrivialAccessors
19
22
  def self.ignored_first_subdomains=(arg)
20
23
  @ignored_first_subdomains = arg
21
24
  end
25
+ # rubocop:enable Style/TrivialAccessors
22
26
 
23
27
  def parse_tenant_name(request)
24
28
  return nil if request.host.blank?
29
+
25
30
  parts = request.host.split('.')
26
31
  self.class.ignored_first_subdomains.include?(parts[0]) ? parts.drop(1).join('.') : request.host
27
32
  end
28
33
  end
29
34
  end
30
- end
35
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'apartment/elevators/generic'
2
4
 
3
5
  module Apartment
@@ -12,8 +14,10 @@ module Apartment
12
14
  end
13
15
 
14
16
  def parse_tenant_name(request)
15
- raise TenantNotFound,
16
- "Cannot find tenant for host #{request.host}" unless @hash.has_key?(request.host)
17
+ unless @hash.key?(request.host)
18
+ raise TenantNotFound,
19
+ "Cannot find tenant for host #{request.host}"
20
+ end
17
21
 
18
22
  @hash[request.host]
19
23
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'apartment/elevators/generic'
2
4
  require 'public_suffix'
3
5
 
@@ -11,9 +13,11 @@ module Apartment
11
13
  @excluded_subdomains ||= []
12
14
  end
13
15
 
16
+ # rubocop:disable Style/TrivialAccessors
14
17
  def self.excluded_subdomains=(arg)
15
18
  @excluded_subdomains = arg
16
19
  end
20
+ # rubocop:enable Style/TrivialAccessors
17
21
 
18
22
  def parse_tenant_name(request)
19
23
  request_subdomain = subdomain(request.host)
@@ -21,15 +25,15 @@ module Apartment
21
25
  # If the domain acquired is set to be excluded, set the tenant to whatever is currently
22
26
  # next in line in the schema search path.
23
27
  tenant = if self.class.excluded_subdomains.include?(request_subdomain)
24
- nil
25
- else
26
- request_subdomain
27
- end
28
+ nil
29
+ else
30
+ request_subdomain
31
+ end
28
32
 
29
33
  tenant.presence
30
34
  end
31
35
 
32
- protected
36
+ protected
33
37
 
34
38
  # *Almost* a direct ripoff of ActionDispatch::Request subdomain methods
35
39
 
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record/log_subscriber'
4
+
5
+ module Apartment
6
+ # Custom Log subscriber to include database name and schema name in sql logs
7
+ class LogSubscriber < ActiveRecord::LogSubscriber
8
+ # NOTE: for some reason, if the method definition is not here, then the custom debug method is not called
9
+ # rubocop:disable Lint/UselessMethodDefinition
10
+ def sql(event)
11
+ super(event)
12
+ end
13
+ # rubocop:enable Lint/UselessMethodDefinition
14
+
15
+ private
16
+
17
+ def debug(progname = nil, &block)
18
+ progname = " #{apartment_log}#{progname}" unless progname.nil?
19
+
20
+ super(progname, &block)
21
+ end
22
+
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
30
+ "#{database}#{schema}"
31
+ end
32
+ end
33
+ end
@@ -1,16 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'apartment/tenant'
2
4
 
3
5
  module Apartment
4
6
  module Migrator
5
-
6
7
  extend self
7
8
 
8
9
  # Migrate to latest
9
10
  def migrate(database)
10
11
  Tenant.switch(database) do
11
- version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
12
+ version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
12
13
 
13
- migration_scope_block = -> (migration) { ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.scope) }
14
+ migration_scope_block = ->(migration) { ENV['SCOPE'].blank? || (ENV['SCOPE'] == migration.scope) }
14
15
 
15
16
  if activerecord_below_5_2?
16
17
  ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, version, &migration_scope_block)
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apartment
4
+ module Model
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ # NOTE: key can either be an array of symbols or a single value.
9
+ # E.g. If we run the following query:
10
+ # `Setting.find_by(key: 'something', value: 'amazing')` key will have an array of symbols: `[:key, :something]`
11
+ # while if we run:
12
+ # `Setting.find(10)` key will have the value 'id'
13
+ def cached_find_by_statement(key, &block)
14
+ # Modifying the cache key to have a reference to the current tenant,
15
+ # so the cached statement is referring only to the tenant in which we've
16
+ # executed this
17
+ cache_key = if key.is_a? String
18
+ "#{Apartment::Tenant.current}_#{key}"
19
+ else
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)
23
+ end
24
+ cache = @find_by_statement_cache[connection.prepared_statements]
25
+ cache.compute_if_absent(cache_key) { ActiveRecord::StatementCache.create(connection, &block) }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,10 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rails'
2
4
  require 'apartment/tenant'
3
- require 'apartment/reloader'
4
5
 
5
6
  module Apartment
6
7
  class Railtie < Rails::Railtie
7
-
8
8
  #
9
9
  # Set up our default config options
10
10
  # Do this before the app initializers run so we don't override custom settings
@@ -17,17 +17,22 @@ module Apartment
17
17
  config.seed_after_create = false
18
18
  config.prepend_environment = false
19
19
  config.append_environment = false
20
+ config.tenant_presence_check = true
21
+ config.active_record_log = false
20
22
  end
21
23
 
22
24
  ActiveRecord::Migrator.migrations_paths = Rails.application.paths['db/migrate'].to_a
23
25
  end
24
26
 
25
27
  # Hook into ActionDispatch::Reloader to ensure Apartment is properly initialized
26
- # Note that this doens't entirely work as expected in Development, because this is called before classes are reloaded
28
+ # Note that this doesn't entirely work as expected in Development,
29
+ # because this is called before classes are reloaded
27
30
  # See the middleware/console declarations below to help with this. Hope to fix that soon.
28
31
  #
29
32
  config.to_prepare do
30
33
  next if ARGV.any? { |arg| arg =~ /\Aassets:(?:precompile|clean)\z/ }
34
+ next if ARGV.any?('webpacker:compile')
35
+ next if ENV['APARTMENT_DISABLE_INIT']
31
36
 
32
37
  begin
33
38
  Apartment.connection_class.connection_pool.with_connection do
@@ -39,6 +44,14 @@ module Apartment
39
44
  end
40
45
  end
41
46
 
47
+ config.after_initialize do
48
+ # NOTE: Load the custom log subscriber if enabled
49
+ if Apartment.active_record_log
50
+ ActiveSupport::Notifications.unsubscribe 'sql.active_record'
51
+ Apartment::LogSubscriber.attach_to :active_record
52
+ end
53
+ end
54
+
42
55
  #
43
56
  # Ensure rake tasks are loaded
44
57
  #
@@ -46,22 +59,5 @@ module Apartment
46
59
  load 'tasks/apartment.rake'
47
60
  require 'apartment/tasks/enhancements' if Apartment.db_migrate_tenants
48
61
  end
49
-
50
- #
51
- # The following initializers are a workaround to the fact that I can't properly hook into the rails reloader
52
- # Note this is technically valid for any environment where cache_classes is false, for us, it's just development
53
- #
54
- if Rails.env.development?
55
-
56
- # Apartment::Reloader is middleware to initialize things properly on each request to dev
57
- initializer 'apartment.init' do |app|
58
- app.config.middleware.use Apartment::Reloader
59
- end
60
-
61
- # Overrides reload! to also call Apartment::Tenant.init as well so that the reloaded classes have the proper table_names
62
- console do
63
- require 'apartment/console'
64
- end
65
- end
66
62
  end
67
63
  end
@@ -1,12 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Require this file to append Apartment rake tasks to ActiveRecord db rake tasks
2
4
  # Enabled by default in the initializer
3
5
 
4
6
  module Apartment
5
7
  class RakeTaskEnhancer
6
-
7
8
  module TASKS
8
- ENHANCE_BEFORE = %w(db:drop)
9
- ENHANCE_AFTER = %w(db:migrate db:rollback db:migrate:up db:migrate:down db:migrate:redo db:seed)
9
+ ENHANCE_BEFORE = %w[db:drop].freeze
10
+ ENHANCE_AFTER = %w[db:migrate db:rollback db:migrate:up db:migrate:down db:migrate:redo db:seed].freeze
10
11
  freeze
11
12
  end
12
13
 
@@ -28,7 +29,6 @@ module Apartment
28
29
  task = Rake::Task[name]
29
30
  enhance_after_task(task)
30
31
  end
31
-
32
32
  end
33
33
 
34
34
  def should_enhance?
@@ -48,9 +48,7 @@ module Apartment
48
48
  def inserted_task_name(task)
49
49
  task.name.sub(/db:/, 'apartment:')
50
50
  end
51
-
52
51
  end
53
-
54
52
  end
55
53
  end
56
54
 
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apartment
4
+ module TaskHelper
5
+ def self.each_tenant(&block)
6
+ Parallel.each(tenants_without_default, in_threads: Apartment.parallel_migration_threads) do |tenant|
7
+ block.call(tenant)
8
+ end
9
+ end
10
+
11
+ def self.tenants_without_default
12
+ tenants - [Apartment.default_tenant]
13
+ end
14
+
15
+ def self.tenants
16
+ ENV['DB'] ? ENV['DB'].split(',').map(&:strip) : Apartment.tenant_names || []
17
+ end
18
+
19
+ def self.warn_if_tenants_empty
20
+ return unless tenants.empty? && ENV['IGNORE_EMPTY_TENANTS'] != 'true'
21
+
22
+ puts <<-WARNING
23
+ [WARNING] - The list of tenants to migrate appears to be empty. This could mean a few things:
24
+
25
+ 1. You may not have created any, in which case you can ignore this message
26
+ 2. You've run `apartment:migrate` directly without loading the Rails environment
27
+ * `apartment:migrate` is now deprecated. Tenants will automatically be migrated with `db:migrate`
28
+
29
+ Note that your tenants currently haven't been migrated. You'll need to run `db:migrate` to rectify this.
30
+ WARNING
31
+ end
32
+
33
+ def self.create_tenant(tenant_name)
34
+ puts("Creating #{tenant_name} tenant")
35
+ Apartment::Tenant.create(tenant_name)
36
+ rescue Apartment::TenantExists => e
37
+ puts "Tried to create already existing tenant: #{e}"
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
51
+ end
52
+ end
@@ -1,23 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
4
 
3
5
  module Apartment
4
6
  # The main entry point to Apartment functions
5
7
  #
6
8
  module Tenant
7
-
8
9
  extend self
9
10
  extend Forwardable
10
11
 
11
- def_delegators :adapter, :create, :drop, :switch, :switch!, :current, :each, :reset, :set_callback, :seed, :current_tenant, :default_tenant, :environmentify
12
+ def_delegators :adapter, :create, :drop, :switch, :switch!, :current, :each,
13
+ :reset, :init, :set_callback, :seed, :default_tenant, :environmentify
12
14
 
13
15
  attr_writer :config
14
16
 
15
- # Initialize Apartment config options such as excluded_models
16
- #
17
- def init
18
- adapter.process_excluded_models
19
- end
20
-
21
17
  # Fetch the proper multi-tenant adapter based on Rails config
22
18
  #
23
19
  # @return {subclass of Apartment::AbstractAdapter}
@@ -27,9 +23,10 @@ module Apartment
27
23
  adapter_method = "#{config[:adapter]}_adapter"
28
24
 
29
25
  if defined?(JRUBY_VERSION)
30
- if config[:adapter] =~ /mysql/
26
+ case config[:adapter]
27
+ when /mysql/
31
28
  adapter_method = 'jdbc_mysql_adapter'
32
- elsif config[:adapter] =~ /postgresql/
29
+ when /postgresql/
33
30
  adapter_method = 'jdbc_postgresql_adapter'
34
31
  end
35
32
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Apartment
2
- VERSION = "2.3.0"
4
+ VERSION = '2.11.0'
3
5
  end