ros-apartment 2.6.1 → 2.8.1.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/changelog.yml +63 -0
  3. data/.rubocop.yml +74 -4
  4. data/.rubocop_todo.yml +50 -13
  5. data/.story_branch.yml +1 -0
  6. data/.travis.yml +5 -0
  7. data/CHANGELOG.md +962 -0
  8. data/Gemfile +1 -1
  9. data/Guardfile +0 -15
  10. data/HISTORY.md +159 -68
  11. data/README.md +30 -3
  12. data/Rakefile +4 -2
  13. data/TODO.md +0 -1
  14. data/gemfiles/rails_5_0.gemfile +1 -1
  15. data/gemfiles/rails_5_1.gemfile +1 -1
  16. data/gemfiles/rails_5_2.gemfile +1 -1
  17. data/gemfiles/rails_6_0.gemfile +1 -1
  18. data/gemfiles/rails_master.gemfile +1 -1
  19. data/lib/apartment.rb +13 -12
  20. data/lib/apartment/active_record/connection_handling.rb +3 -0
  21. data/lib/apartment/active_record/internal_metadata.rb +0 -2
  22. data/lib/apartment/active_record/schema_migration.rb +0 -2
  23. data/lib/apartment/adapters/abstract_adapter.rb +3 -4
  24. data/lib/apartment/adapters/abstract_jdbc_adapter.rb +2 -1
  25. data/lib/apartment/adapters/jdbc_postgresql_adapter.rb +2 -1
  26. data/lib/apartment/adapters/mysql2_adapter.rb +5 -0
  27. data/lib/apartment/adapters/postgresql_adapter.rb +34 -7
  28. data/lib/apartment/console.rb +2 -8
  29. data/lib/apartment/custom_console.rb +2 -2
  30. data/lib/apartment/log_subscriber.rb +29 -0
  31. data/lib/apartment/railtie.rb +24 -21
  32. data/lib/apartment/tasks/enhancements.rb +1 -1
  33. data/lib/apartment/tasks/task_helper.rb +40 -0
  34. data/lib/apartment/tenant.rb +3 -16
  35. data/lib/apartment/version.rb +1 -1
  36. data/lib/generators/apartment/install/templates/apartment.rb +7 -1
  37. data/lib/tasks/apartment.rake +18 -46
  38. data/{apartment.gemspec → ros-apartment.gemspec} +3 -4
  39. metadata +17 -14
  40. data/.github/workflows/.rubocop-linter.yml +0 -22
data/README.md CHANGED
@@ -338,6 +338,23 @@ Apartment.configure do |config|
338
338
  end
339
339
  ```
340
340
 
341
+ ### Additional logging information
342
+
343
+ Enabling this configuration will output the database that the process is currently connected to as well as which
344
+ schemas are in the search path. This can be enabled by setting to true the `active_record_log` configuration.
345
+
346
+ Please note that our custom logger inherits from `ActiveRecord::LogSubscriber` so this will be required for the configuration to work.
347
+
348
+ **Example log output:**
349
+
350
+ <img src="documentation/images/log_example.png">
351
+
352
+ ```ruby
353
+ Apartment.configure do |config|
354
+ active_record_log = true
355
+ end
356
+ ```
357
+
341
358
  ### Excluding models
342
359
 
343
360
  If you have some models that should always access the 'public' tenant, you can specify this by configuring Apartment using `Apartment.configure`. This will yield a config object for you. You can set excluded models like so:
@@ -368,12 +385,12 @@ Enable this option with:
368
385
  config.use_sql = true
369
386
  ```
370
387
 
371
- ### Providing a Different default_schema
388
+ ### Providing a Different default_tenant
372
389
 
373
390
  By default, ActiveRecord will use `"$user", public` as the default `schema_search_path`. This can be modified if you wish to use a different default schema be setting:
374
391
 
375
392
  ```ruby
376
- config.default_schema = "some_other_schema"
393
+ config.default_tenant = "some_other_schema"
377
394
  ```
378
395
 
379
396
  With that set, all excluded models will use this schema as the table name prefix instead of `public` and `reset` on `Apartment::Tenant` will return to this schema as well.
@@ -446,7 +463,7 @@ schema_search_path: "public,shared_extensions"
446
463
  ...
447
464
  ```
448
465
 
449
- This would be for a config with `default_schema` set to `public` and `persistent_schemas` set to `['shared_extensions']`. **Note**: This only works on Heroku with [Rails 4.1+](https://devcenter.heroku.com/changelog-items/426). For apps that use older Rails versions hosted on Heroku, the only way to properly setup is to start with a fresh PostgreSQL instance:
466
+ This would be for a config with `default_tenant` set to `public` and `persistent_schemas` set to `['shared_extensions']`. **Note**: This only works on Heroku with [Rails 4.1+](https://devcenter.heroku.com/changelog-items/426). For apps that use older Rails versions hosted on Heroku, the only way to properly setup is to start with a fresh PostgreSQL instance:
450
467
 
451
468
  1. Append `?schema_search_path=public,hstore` to your `DATABASE_URL` environment variable, by this you don't have to revise the `database.yml` file (which is impossible since Heroku regenerates a completely different and immutable `database.yml` of its own on each deploy)
452
469
  2. Run `heroku pg:psql` from your command line
@@ -594,6 +611,16 @@ module Apartment
594
611
  end
595
612
  ```
596
613
 
614
+ ## Running rails console without a connection to the database
615
+
616
+ By default, once apartment starts, it establishes a connection to the database. It is possible to
617
+ disable this initial connection, by running with `APARTMENT_DISABLE_INIT` set to something:
618
+
619
+ ```shell
620
+ $ APARTMENT_DISABLE_INIT=true DATABASE_URL=postgresql://localhost:1234/buk_development bin/rails runner 'puts 1'
621
+ # 1
622
+ ```
623
+
597
624
  ## Contributing
598
625
 
599
626
  * In both `spec/dummy/config` and `spec/config`, you will see `database.yml.sample` files
data/Rakefile CHANGED
@@ -46,8 +46,10 @@ namespace :db do
46
46
  apartment_db_file = 'spec/config/database.yml'
47
47
  rails_db_file = 'spec/dummy/config/database.yml'
48
48
 
49
- FileUtils.copy(apartment_db_file + '.sample', apartment_db_file, verbose: true) unless File.exist?(apartment_db_file)
50
- FileUtils.copy(rails_db_file + '.sample', rails_db_file, verbose: true) unless File.exist?(rails_db_file)
49
+ unless File.exist?(apartment_db_file)
50
+ FileUtils.copy(apartment_db_file + '.sample', apartment_db_file, verbose: true)
51
+ end
52
+ FileUtils.copy(rails_db_file + '.sample', rails_db_file, verbose: true) unless File.exist?(rails_db_file)
51
53
  end
52
54
  end
53
55
 
data/TODO.md CHANGED
@@ -46,6 +46,5 @@
46
46
 
47
47
  Quick TODOs
48
48
 
49
- 1. `default_tenant` should be up to the adapter, not the Apartment class, deprecate `default_schema`
50
49
  2. deprecation.rb rescues everything, we have a hard dependency on ActiveSupport so this is unnecessary
51
50
  3.
@@ -2,8 +2,8 @@
2
2
 
3
3
  source "http://rubygems.org"
4
4
 
5
- gem "perx-rubocop", "~> 0.0.3"
6
5
  gem "rails", "~> 5.0.0"
6
+ gem "rubocop"
7
7
 
8
8
  group :local do
9
9
  gem "guard-rspec", "~> 4.2"
@@ -2,8 +2,8 @@
2
2
 
3
3
  source "http://rubygems.org"
4
4
 
5
- gem "perx-rubocop", "~> 0.0.3"
6
5
  gem "rails", "~> 5.1.0"
6
+ gem "rubocop"
7
7
 
8
8
  group :local do
9
9
  gem "guard-rspec", "~> 4.2"
@@ -2,8 +2,8 @@
2
2
 
3
3
  source "http://rubygems.org"
4
4
 
5
- gem "perx-rubocop", "~> 0.0.3"
6
5
  gem "rails", "~> 5.2.0"
6
+ gem "rubocop"
7
7
 
8
8
  group :local do
9
9
  gem "guard-rspec", "~> 4.2"
@@ -2,8 +2,8 @@
2
2
 
3
3
  source "http://rubygems.org"
4
4
 
5
- gem "perx-rubocop", "~> 0.0.3"
6
5
  gem "rails", "~> 6.0.0"
6
+ gem "rubocop"
7
7
 
8
8
  group :local do
9
9
  gem "guard-rspec", "~> 4.2"
@@ -2,8 +2,8 @@
2
2
 
3
3
  source "http://rubygems.org"
4
4
 
5
- gem "perx-rubocop", "~> 0.0.3"
6
5
  gem "rails", git: "https://github.com/rails/rails.git"
6
+ gem "rubocop"
7
7
 
8
8
  group :local do
9
9
  gem "guard-rspec", "~> 4.2"
@@ -6,25 +6,28 @@ require 'forwardable'
6
6
  require 'active_record'
7
7
  require 'apartment/tenant'
8
8
 
9
- # require_relative 'apartment/arel/visitors/postgresql'
9
+ require_relative 'apartment/log_subscriber'
10
10
 
11
- require_relative 'apartment/active_record/connection_handling' if ActiveRecord.version.release >= Gem::Version.new('6.0')
11
+ if ActiveRecord.version.release >= Gem::Version.new('6.0')
12
+ require_relative 'apartment/active_record/connection_handling'
13
+ end
12
14
 
13
15
  if ActiveRecord.version.release >= Gem::Version.new('6.1')
14
16
  require_relative 'apartment/active_record/schema_migration'
15
17
  require_relative 'apartment/active_record/internal_metadata'
16
18
  end
17
19
 
20
+ # Apartment main definitions
18
21
  module Apartment
19
22
  class << self
20
23
  extend Forwardable
21
24
 
22
- ACCESSOR_METHODS = %i[use_schemas use_sql seed_after_create prepend_environment
23
- append_environment with_multi_server_setup tenant_presence_check].freeze
25
+ ACCESSOR_METHODS = %i[use_schemas use_sql seed_after_create prepend_environment default_tenant
26
+ append_environment with_multi_server_setup tenant_presence_check active_record_log].freeze
24
27
 
25
28
  WRITER_METHODS = %i[tenant_names database_schema_file excluded_models
26
- default_schema persistent_schemas connection_class
27
- tld_length db_migrate_tenants seed_data_file
29
+ persistent_schemas connection_class
30
+ db_migrate_tenants seed_data_file
28
31
  parallel_migration_threads pg_excluded_names].freeze
29
32
 
30
33
  attr_accessor(*ACCESSOR_METHODS)
@@ -53,6 +56,10 @@ module Apartment
53
56
  extract_tenant_config
54
57
  end
55
58
 
59
+ def tld_length=(_)
60
+ Apartment::Deprecation.warn('`config.tld_length` have no effect because it was removed in https://github.com/influitive/apartment/pull/309')
61
+ end
62
+
56
63
  def db_config_for(tenant)
57
64
  (tenants_with_config[tenant] || connection_config)
58
65
  end
@@ -70,15 +77,9 @@ module Apartment
70
77
  @excluded_models || []
71
78
  end
72
79
 
73
- def default_schema
74
- @default_schema || 'public' # TODO: 'public' is postgres specific
75
- end
76
-
77
80
  def parallel_migration_threads
78
81
  @parallel_migration_threads || 0
79
82
  end
80
- alias default_tenant default_schema
81
- alias default_tenant= default_schema=
82
83
 
83
84
  def persistent_schemas
84
85
  @persistent_schemas || []
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
+ # This is monkeypatching activerecord to ensure that whenever a new connection is established it
5
+ # switches to the same tenant as before the connection switching. This problem is more evident when
6
+ # using read replica in Rails 6
4
7
  module ConnectionHandling
5
8
  def connected_to_with_tenant(database: nil, role: nil, prevent_writes: false, &blk)
6
9
  current_tenant = Apartment::Tenant.current
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Rails/ApplicationRecord
4
3
  class InternalMetadata < ActiveRecord::Base # :nodoc:
5
4
  class << self
6
5
  def table_exists?
@@ -8,4 +7,3 @@ class InternalMetadata < ActiveRecord::Base # :nodoc:
8
7
  end
9
8
  end
10
9
  end
11
- # rubocop:enable Rails/ApplicationRecord
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
- # rubocop:disable Rails/ApplicationRecord
5
4
  class SchemaMigration < ActiveRecord::Base # :nodoc:
6
5
  class << self
7
6
  def table_exists?
@@ -9,5 +8,4 @@ module ActiveRecord
9
8
  end
10
9
  end
11
10
  end
12
- # rubocop:enable Rails/ApplicationRecord
13
11
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Apartment
4
4
  module Adapters
5
- # rubocop:disable Metrics/ClassLength
5
+ # Abstract adapter from which all the Apartment DB related adapters will inherit the base logic
6
6
  class AbstractAdapter
7
7
  include ActiveSupport::Callbacks
8
8
  define_callbacks :create, :switch
@@ -54,7 +54,6 @@ module Apartment
54
54
  def default_tenant
55
55
  @default_tenant || Apartment.default_tenant
56
56
  end
57
- alias default_schema default_tenant # TODO: deprecate default_schema
58
57
 
59
58
  # Drop the tenant
60
59
  #
@@ -241,7 +240,8 @@ module Apartment
241
240
  def with_neutral_connection(tenant, &_block)
242
241
  if Apartment.with_multi_server_setup
243
242
  # neutral connection is necessary whenever you need to create/remove a database from a server.
244
- # example: when you use postgresql, you need to connect to the default postgresql database before you create your own.
243
+ # example: when you use postgresql, you need to connect to the default postgresql database before you create
244
+ # your own.
245
245
  SeparateDbConnectionHandler.establish_connection(multi_tenantify(tenant, false))
246
246
  yield(SeparateDbConnectionHandler.connection)
247
247
  SeparateDbConnectionHandler.connection.close
@@ -270,5 +270,4 @@ module Apartment
270
270
  end
271
271
  end
272
272
  end
273
- # rubocop:enable Metrics/ClassLength
274
273
  end
@@ -4,11 +4,12 @@ require 'apartment/adapters/abstract_adapter'
4
4
 
5
5
  module Apartment
6
6
  module Adapters
7
+ # JDBC Abstract adapter
7
8
  class AbstractJDBCAdapter < AbstractAdapter
8
9
  private
9
10
 
10
11
  def multi_tenantify_with_tenant_db_name(config, tenant)
11
- config[:url] = "#{config[:url].gsub(%r{(\S+)\/.+$}, '\1')}/#{environmentify(tenant)}"
12
+ config[:url] = "#{config[:url].gsub(%r{(\S+)/.+$}, '\1')}/#{environmentify(tenant)}"
12
13
  end
13
14
 
14
15
  def rescue_from
@@ -3,6 +3,7 @@
3
3
  require 'apartment/adapters/postgresql_adapter'
4
4
 
5
5
  module Apartment
6
+ # JDBC helper to decide wether to use JDBC Postgresql Adapter or JDBC Postgresql Adapter with Schemas
6
7
  module Tenant
7
8
  def self.jdbc_postgresql_adapter(config)
8
9
  if Apartment.use_schemas
@@ -19,7 +20,7 @@ module Apartment
19
20
  private
20
21
 
21
22
  def multi_tenantify_with_tenant_db_name(config, tenant)
22
- config[:url] = "#{config[:url].gsub(%r{(\S+)\/.+$}, '\1')}/#{environmentify(tenant)}"
23
+ config[:url] = "#{config[:url].gsub(%r{(\S+)/.+$}, '\1')}/#{environmentify(tenant)}"
23
24
  end
24
25
 
25
26
  def create_tenant_command(conn, tenant)
@@ -3,6 +3,7 @@
3
3
  require 'apartment/adapters/abstract_adapter'
4
4
 
5
5
  module Apartment
6
+ # Helper module to decide wether to use mysql2 adapter or mysql2 adapter with schemas
6
7
  module Tenant
7
8
  def self.mysql2_adapter(config)
8
9
  if Apartment.use_schemas
@@ -14,6 +15,7 @@ module Apartment
14
15
  end
15
16
 
16
17
  module Adapters
18
+ # Mysql2 Adapter
17
19
  class Mysql2Adapter < AbstractAdapter
18
20
  def initialize(config)
19
21
  super
@@ -28,6 +30,7 @@ module Apartment
28
30
  end
29
31
  end
30
32
 
33
+ # Mysql2 Schemas Adapter
31
34
  class Mysql2SchemaAdapter < AbstractAdapter
32
35
  def initialize(config)
33
36
  super
@@ -39,6 +42,8 @@ module Apartment
39
42
  # Reset current tenant to the default_tenant
40
43
  #
41
44
  def reset
45
+ return unless default_tenant
46
+
42
47
  Apartment.connection.execute "use `#{default_tenant}`"
43
48
  end
44
49
 
@@ -30,6 +30,10 @@ module Apartment
30
30
  reset
31
31
  end
32
32
 
33
+ def default_tenant
34
+ @default_tenant = Apartment.default_tenant || 'public'
35
+ end
36
+
33
37
  # Reset schema search path to the default schema_search_path
34
38
  #
35
39
  # @return {String} default schema search path
@@ -93,7 +97,23 @@ module Apartment
93
97
  end
94
98
 
95
99
  def create_tenant_command(conn, tenant)
96
- conn.execute(%(CREATE SCHEMA "#{tenant}"))
100
+ # NOTE: This was causing some tests to fail because of the database strategy for rspec
101
+ if ActiveRecord::Base.connection.open_transactions > 0
102
+ conn.execute(%(CREATE SCHEMA "#{tenant}"))
103
+ else
104
+ schema = %(BEGIN;
105
+ CREATE SCHEMA "#{tenant}";
106
+ COMMIT;)
107
+
108
+ conn.execute(schema)
109
+ end
110
+ rescue *rescuable_exceptions => e
111
+ rollback_transaction(conn)
112
+ raise e
113
+ end
114
+
115
+ def rollback_transaction(conn)
116
+ conn.execute("ROLLBACK;")
97
117
  end
98
118
 
99
119
  # Generate the final search path to set including persistent_schemas
@@ -117,12 +137,17 @@ module Apartment
117
137
  # There is `reset_sequence_name`, but that method actually goes to the database
118
138
  # to find out the new name. Therefore, we do this hack to only unset the name,
119
139
  # and it will be dynamically found the next time it is needed
120
- ActiveRecord::Base.descendants
121
- .select { |c| c.instance_variable_defined?(:@sequence_name) }
122
- .reject { |c| c.instance_variable_defined?(:@explicit_sequence_name) && c.instance_variable_get(:@explicit_sequence_name) }
123
- .each do |c|
124
- c.remove_instance_variable :@sequence_name
125
- end
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
126
151
  end
127
152
  end
128
153
 
@@ -191,9 +216,11 @@ module Apartment
191
216
  #
192
217
  # @return {String} raw SQL contaning inserts with data from schema_migrations
193
218
  #
219
+ # rubocop:disable Layout/LineLength
194
220
  def pg_dump_schema_migrations_data
195
221
  with_pg_env { `pg_dump -a --inserts -t #{default_tenant}.schema_migrations -t #{default_tenant}.ar_internal_metadata #{dbname}` }
196
222
  end
223
+ # rubocop:enable Layout/LineLength
197
224
 
198
225
  # Temporary set Postgresql related environment variables if there are in @config
199
226
  #
@@ -5,9 +5,7 @@
5
5
 
6
6
  # reloads the environment
7
7
  def reload!(print = true)
8
- # rubocop:disable Rails/Output
9
8
  puts 'Reloading...' if print
10
- # rubocop:enable Rails/Output
11
9
 
12
10
  # This triggers the to_prepare callbacks
13
11
  ActionDispatch::Callbacks.new(proc {}).call({})
@@ -18,15 +16,13 @@ end
18
16
 
19
17
  def st(schema_name = nil)
20
18
  if schema_name.nil?
21
- # rubocop:disable Rails/Output
22
19
  tenant_list.each { |t| puts t }
23
- # rubocop:enable Rails/Output
20
+
24
21
  elsif tenant_list.include? schema_name
25
22
  Apartment::Tenant.switch!(schema_name)
26
23
  else
27
- # rubocop:disable Rails/Output
28
24
  puts "Tenant #{schema_name} is not part of the tenant list"
29
- # rubocop:enable Rails/Output
25
+
30
26
  end
31
27
  end
32
28
 
@@ -37,8 +33,6 @@ def tenant_list
37
33
  end
38
34
 
39
35
  def tenant_info_msg
40
- # rubocop:disable Rails/Output
41
36
  puts "Available Tenants: #{tenant_list}\n"
42
37
  puts "Use `st 'tenant'` to switch tenants & `tenant_list` to see list\n"
43
- # rubocop:enable Rails/Output
44
38
  end
@@ -7,9 +7,9 @@ module Apartment
7
7
  begin
8
8
  require 'pry-rails'
9
9
  rescue LoadError
10
- # rubocop:disable Rails/Output
10
+ # rubocop:disable Layout/LineLength
11
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 Rails/Output
12
+ # rubocop:enable Layout/LineLength
13
13
  end
14
14
 
15
15
  desc = "Includes the current Rails environment and project folder name.\n" \