ros-apartment 2.3.0.alpha1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/.pryrc +5 -3
  3. data/.rubocop.yml +36 -0
  4. data/.rubocop_todo.yml +439 -0
  5. data/.ruby-version +1 -0
  6. data/Appraisals +31 -52
  7. data/CHANGELOG.md +965 -0
  8. data/Gemfile +2 -7
  9. data/Guardfile +3 -16
  10. data/README.md +121 -44
  11. data/Rakefile +42 -31
  12. data/lib/apartment/active_record/connection_handling.rb +31 -0
  13. data/lib/apartment/active_record/internal_metadata.rb +9 -0
  14. data/lib/apartment/active_record/postgresql_adapter.rb +43 -0
  15. data/lib/apartment/active_record/schema_migration.rb +11 -0
  16. data/lib/apartment/adapters/abstract_adapter.rb +52 -46
  17. data/lib/apartment/adapters/abstract_jdbc_adapter.rb +5 -3
  18. data/lib/apartment/adapters/jdbc_mysql_adapter.rb +3 -3
  19. data/lib/apartment/adapters/jdbc_postgresql_adapter.rb +19 -13
  20. data/lib/apartment/adapters/mysql2_adapter.rb +15 -9
  21. data/lib/apartment/adapters/postgis_adapter.rb +3 -2
  22. data/lib/apartment/adapters/postgresql_adapter.rb +79 -31
  23. data/lib/apartment/adapters/sqlite3_adapter.rb +18 -8
  24. data/lib/apartment/adapters/trilogy_adapter.rb +29 -0
  25. data/lib/apartment/console.rb +23 -11
  26. data/lib/apartment/custom_console.rb +42 -0
  27. data/lib/apartment/deprecation.rb +2 -1
  28. data/lib/apartment/elevators/domain.rb +4 -3
  29. data/lib/apartment/elevators/first_subdomain.rb +3 -2
  30. data/lib/apartment/elevators/generic.rb +4 -3
  31. data/lib/apartment/elevators/host.rb +6 -1
  32. data/lib/apartment/elevators/host_hash.rb +6 -2
  33. data/lib/apartment/elevators/subdomain.rb +9 -5
  34. data/lib/apartment/log_subscriber.rb +45 -0
  35. data/lib/apartment/migrator.rb +7 -24
  36. data/lib/apartment/model.rb +29 -0
  37. data/lib/apartment/railtie.rb +21 -20
  38. data/lib/apartment/tasks/enhancements.rb +4 -6
  39. data/lib/apartment/tasks/task_helper.rb +52 -0
  40. data/lib/apartment/tenant.rb +7 -10
  41. data/lib/apartment/version.rb +3 -1
  42. data/lib/apartment.rb +46 -15
  43. data/lib/generators/apartment/install/install_generator.rb +4 -3
  44. data/lib/generators/apartment/install/templates/apartment.rb +10 -3
  45. data/lib/tasks/apartment.rake +48 -87
  46. data/ros-apartment.gemspec +65 -0
  47. metadata +181 -264
  48. data/.github/ISSUE_TEMPLATE.md +0 -21
  49. data/.travis.yml +0 -65
  50. data/HISTORY.md +0 -398
  51. data/TODO.md +0 -51
  52. data/apartment.gemspec +0 -46
  53. data/docker-compose.yml +0 -33
  54. data/gemfiles/rails_4_2.gemfile +0 -23
  55. data/gemfiles/rails_5_0.gemfile +0 -22
  56. data/gemfiles/rails_5_1.gemfile +0 -22
  57. data/gemfiles/rails_5_2.gemfile +0 -18
  58. data/gemfiles/rails_6_0.gemfile +0 -22
  59. data/gemfiles/rails_master.gemfile +0 -22
  60. data/lib/apartment/reloader.rb +0 -21
  61. data/spec/adapters/jdbc_mysql_adapter_spec.rb +0 -19
  62. data/spec/adapters/jdbc_postgresql_adapter_spec.rb +0 -41
  63. data/spec/adapters/mysql2_adapter_spec.rb +0 -59
  64. data/spec/adapters/postgresql_adapter_spec.rb +0 -61
  65. data/spec/adapters/sqlite3_adapter_spec.rb +0 -83
  66. data/spec/apartment_spec.rb +0 -11
  67. data/spec/config/database.yml.sample +0 -49
  68. data/spec/dummy/Rakefile +0 -7
  69. data/spec/dummy/app/controllers/application_controller.rb +0 -6
  70. data/spec/dummy/app/helpers/application_helper.rb +0 -2
  71. data/spec/dummy/app/models/company.rb +0 -3
  72. data/spec/dummy/app/models/user.rb +0 -3
  73. data/spec/dummy/app/views/application/index.html.erb +0 -1
  74. data/spec/dummy/app/views/layouts/application.html.erb +0 -14
  75. data/spec/dummy/config/application.rb +0 -49
  76. data/spec/dummy/config/boot.rb +0 -11
  77. data/spec/dummy/config/database.yml.sample +0 -44
  78. data/spec/dummy/config/environment.rb +0 -5
  79. data/spec/dummy/config/environments/development.rb +0 -28
  80. data/spec/dummy/config/environments/production.rb +0 -51
  81. data/spec/dummy/config/environments/test.rb +0 -34
  82. data/spec/dummy/config/initializers/apartment.rb +0 -4
  83. data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
  84. data/spec/dummy/config/initializers/inflections.rb +0 -10
  85. data/spec/dummy/config/initializers/mime_types.rb +0 -5
  86. data/spec/dummy/config/initializers/secret_token.rb +0 -7
  87. data/spec/dummy/config/initializers/session_store.rb +0 -8
  88. data/spec/dummy/config/locales/en.yml +0 -5
  89. data/spec/dummy/config/routes.rb +0 -3
  90. data/spec/dummy/config.ru +0 -4
  91. data/spec/dummy/db/migrate/20110613152810_create_dummy_models.rb +0 -39
  92. data/spec/dummy/db/migrate/20111202022214_create_table_books.rb +0 -14
  93. data/spec/dummy/db/migrate/20180415260934_create_public_tokens.rb +0 -13
  94. data/spec/dummy/db/schema.rb +0 -55
  95. data/spec/dummy/db/seeds/import.rb +0 -5
  96. data/spec/dummy/db/seeds.rb +0 -5
  97. data/spec/dummy/db/test.sqlite3 +0 -0
  98. data/spec/dummy/public/404.html +0 -26
  99. data/spec/dummy/public/422.html +0 -26
  100. data/spec/dummy/public/500.html +0 -26
  101. data/spec/dummy/public/favicon.ico +0 -0
  102. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  103. data/spec/dummy/script/rails +0 -6
  104. data/spec/dummy_engine/.gitignore +0 -8
  105. data/spec/dummy_engine/Gemfile +0 -15
  106. data/spec/dummy_engine/Rakefile +0 -34
  107. data/spec/dummy_engine/bin/rails +0 -12
  108. data/spec/dummy_engine/config/initializers/apartment.rb +0 -51
  109. data/spec/dummy_engine/dummy_engine.gemspec +0 -24
  110. data/spec/dummy_engine/lib/dummy_engine/engine.rb +0 -4
  111. data/spec/dummy_engine/lib/dummy_engine/version.rb +0 -3
  112. data/spec/dummy_engine/lib/dummy_engine.rb +0 -4
  113. data/spec/dummy_engine/test/dummy/Rakefile +0 -6
  114. data/spec/dummy_engine/test/dummy/config/application.rb +0 -22
  115. data/spec/dummy_engine/test/dummy/config/boot.rb +0 -5
  116. data/spec/dummy_engine/test/dummy/config/database.yml +0 -25
  117. data/spec/dummy_engine/test/dummy/config/environment.rb +0 -5
  118. data/spec/dummy_engine/test/dummy/config/environments/development.rb +0 -37
  119. data/spec/dummy_engine/test/dummy/config/environments/production.rb +0 -78
  120. data/spec/dummy_engine/test/dummy/config/environments/test.rb +0 -39
  121. data/spec/dummy_engine/test/dummy/config/initializers/assets.rb +0 -8
  122. data/spec/dummy_engine/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  123. data/spec/dummy_engine/test/dummy/config/initializers/cookies_serializer.rb +0 -3
  124. data/spec/dummy_engine/test/dummy/config/initializers/filter_parameter_logging.rb +0 -4
  125. data/spec/dummy_engine/test/dummy/config/initializers/inflections.rb +0 -16
  126. data/spec/dummy_engine/test/dummy/config/initializers/mime_types.rb +0 -4
  127. data/spec/dummy_engine/test/dummy/config/initializers/session_store.rb +0 -3
  128. data/spec/dummy_engine/test/dummy/config/initializers/wrap_parameters.rb +0 -14
  129. data/spec/dummy_engine/test/dummy/config/locales/en.yml +0 -23
  130. data/spec/dummy_engine/test/dummy/config/routes.rb +0 -56
  131. data/spec/dummy_engine/test/dummy/config/secrets.yml +0 -22
  132. data/spec/dummy_engine/test/dummy/config.ru +0 -4
  133. data/spec/examples/connection_adapter_examples.rb +0 -42
  134. data/spec/examples/generic_adapter_custom_configuration_example.rb +0 -95
  135. data/spec/examples/generic_adapter_examples.rb +0 -163
  136. data/spec/examples/schema_adapter_examples.rb +0 -234
  137. data/spec/integration/apartment_rake_integration_spec.rb +0 -107
  138. data/spec/integration/query_caching_spec.rb +0 -81
  139. data/spec/integration/use_within_an_engine_spec.rb +0 -28
  140. data/spec/schemas/v1.rb +0 -16
  141. data/spec/schemas/v2.rb +0 -43
  142. data/spec/schemas/v3.rb +0 -49
  143. data/spec/spec_helper.rb +0 -61
  144. data/spec/support/apartment_helpers.rb +0 -43
  145. data/spec/support/capybara_sessions.rb +0 -15
  146. data/spec/support/config.rb +0 -10
  147. data/spec/support/contexts.rb +0 -52
  148. data/spec/support/requirements.rb +0 -35
  149. data/spec/support/setup.rb +0 -46
  150. data/spec/tasks/apartment_rake_spec.rb +0 -129
  151. data/spec/tenant_spec.rb +0 -190
  152. data/spec/unit/config_spec.rb +0 -112
  153. data/spec/unit/elevators/domain_spec.rb +0 -32
  154. data/spec/unit/elevators/first_subdomain_spec.rb +0 -24
  155. data/spec/unit/elevators/generic_spec.rb +0 -54
  156. data/spec/unit/elevators/host_hash_spec.rb +0 -32
  157. data/spec/unit/elevators/host_spec.rb +0 -89
  158. data/spec/unit/elevators/subdomain_spec.rb +0 -76
  159. data/spec/unit/migrator_spec.rb +0 -77
  160. 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"
@@ -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,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,45 @@
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
12
+ end
13
+ # rubocop:enable Lint/UselessMethodDefinition
14
+
15
+ private
16
+
17
+ def debug(progname = nil, &blk)
18
+ progname = " #{apartment_log}#{progname}" unless progname.nil?
19
+
20
+ super
21
+ end
22
+
23
+ def apartment_log
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?
27
+ "#{database}#{schema}"
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
44
+ end
45
+ end
@@ -1,51 +1,34 @@
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
- if activerecord_below_5_2?
16
- ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, version, &migration_scope_block)
17
- else
18
- ActiveRecord::Base.connection.migration_context.migrate(version, &migration_scope_block)
19
- end
16
+ ActiveRecord::Base.connection.migration_context.migrate(version, &migration_scope_block)
20
17
  end
21
18
  end
22
19
 
23
20
  # Migrate up/down to a specific version
24
21
  def run(direction, database, version)
25
22
  Tenant.switch(database) do
26
- if activerecord_below_5_2?
27
- ActiveRecord::Migrator.run(direction, ActiveRecord::Migrator.migrations_paths, version)
28
- else
29
- ActiveRecord::Base.connection.migration_context.run(direction, version)
30
- end
23
+ ActiveRecord::Base.connection.migration_context.run(direction, version)
31
24
  end
32
25
  end
33
26
 
34
27
  # rollback latest migration `step` number of times
35
28
  def rollback(database, step = 1)
36
29
  Tenant.switch(database) do
37
- if activerecord_below_5_2?
38
- ActiveRecord::Migrator.rollback(ActiveRecord::Migrator.migrations_paths, step)
39
- else
40
- ActiveRecord::Base.connection.migration_context.rollback(step)
41
- end
30
+ ActiveRecord::Base.connection.migration_context.rollback(step)
42
31
  end
43
32
  end
44
-
45
- private
46
-
47
- def activerecord_below_5_2?
48
- ActiveRecord.version.release < Gem::Version.new('5.2.0')
49
- end
50
33
  end
51
34
  end
@@ -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,19 @@ 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.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
+
56
+ Apartment::LogSubscriber.attach_to :active_record
57
+ end
58
+ end
59
+
42
60
  #
43
61
  # Ensure rake tasks are loaded
44
62
  #
@@ -46,22 +64,5 @@ module Apartment
46
64
  load 'tasks/apartment.rake'
47
65
  require 'apartment/tasks/enhancements' if Apartment.db_migrate_tenants
48
66
  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
67
  end
67
68
  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.alpha1"
4
+ VERSION = '3.1.0'
3
5
  end