ros-apartment 2.6.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) 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 +949 -0
  8. data/Gemfile +1 -1
  9. data/Guardfile +0 -15
  10. data/HISTORY.md +159 -68
  11. data/README.md +13 -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 +9 -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 +48 -1
  28. data/lib/apartment/console.rb +2 -8
  29. data/lib/apartment/custom_console.rb +4 -2
  30. data/lib/apartment/log_subscriber.rb +27 -0
  31. data/lib/apartment/railtie.rb +15 -10
  32. data/lib/apartment/tasks/task_helper.rb +40 -0
  33. data/lib/apartment/tenant.rb +4 -8
  34. data/lib/apartment/version.rb +1 -1
  35. data/lib/generators/apartment/install/templates/apartment.rb +7 -1
  36. data/lib/tasks/apartment.rake +18 -42
  37. data/{apartment.gemspec → ros-apartment.gemspec} +2 -4
  38. metadata +15 -12
  39. data/.github/workflows/.rubocop-linter.yml +0 -22
data/README.md CHANGED
@@ -368,12 +368,12 @@ Enable this option with:
368
368
  config.use_sql = true
369
369
  ```
370
370
 
371
- ### Providing a Different default_schema
371
+ ### Providing a Different default_tenant
372
372
 
373
373
  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
374
 
375
375
  ```ruby
376
- config.default_schema = "some_other_schema"
376
+ config.default_tenant = "some_other_schema"
377
377
  ```
378
378
 
379
379
  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 +446,7 @@ schema_search_path: "public,shared_extensions"
446
446
  ...
447
447
  ```
448
448
 
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:
449
+ 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
450
 
451
451
  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
452
  2. Run `heroku pg:psql` from your command line
@@ -594,6 +594,16 @@ module Apartment
594
594
  end
595
595
  ```
596
596
 
597
+ ## Running rails console without a connection to the database
598
+
599
+ By default, once apartment starts, it establishes a connection to the database. It is possible to
600
+ disable this initial connection, by running with `APARTMENT_DISABLE_INIT` set to something:
601
+
602
+ ```shell
603
+ $ APARTMENT_DISABLE_INIT=true DATABASE_URL=postgresql://localhost:1234/buk_development bin/rails runner 'puts 1'
604
+ # 1
605
+ ```
606
+
597
607
  ## Contributing
598
608
 
599
609
  * 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
@@ -35,6 +35,12 @@ module Apartment
35
35
  end
36
36
  end
37
37
 
38
+ # Initialize Apartment config options such as excluded_models
39
+ #
40
+ def init
41
+ process_excluded_models
42
+ end
43
+
38
44
  # Note alias_method here doesn't work with inheritence apparently ??
39
45
  #
40
46
  def current
@@ -48,7 +54,6 @@ module Apartment
48
54
  def default_tenant
49
55
  @default_tenant || Apartment.default_tenant
50
56
  end
51
- alias default_schema default_tenant # TODO: deprecate default_schema
52
57
 
53
58
  # Drop the tenant
54
59
  #
@@ -235,7 +240,8 @@ module Apartment
235
240
  def with_neutral_connection(tenant, &_block)
236
241
  if Apartment.with_multi_server_setup
237
242
  # neutral connection is necessary whenever you need to create/remove a database from a server.
238
- # 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.
239
245
  SeparateDbConnectionHandler.establish_connection(multi_tenantify(tenant, false))
240
246
  yield(SeparateDbConnectionHandler.connection)
241
247
  SeparateDbConnectionHandler.connection.close
@@ -264,5 +270,4 @@ module Apartment
264
270
  end
265
271
  end
266
272
  end
267
- # rubocop:enable Metrics/ClassLength
268
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
@@ -37,6 +41,12 @@ module Apartment
37
41
  def reset
38
42
  @current = default_tenant
39
43
  Apartment.connection.schema_search_path = full_search_path
44
+ reset_sequence_names
45
+ end
46
+
47
+ def init
48
+ super
49
+ Apartment.connection.schema_search_path = full_search_path
40
50
  end
41
51
 
42
52
  def current
@@ -73,6 +83,7 @@ module Apartment
73
83
  # there is a issue for prepared statement with changing search_path.
74
84
  # https://www.postgresql.org/docs/9.3/static/sql-prepare.html
75
85
  Apartment.connection.clear_cache! if postgresql_version < 90_300
86
+ reset_sequence_names
76
87
  rescue *rescuable_exceptions
77
88
  raise TenantNotFound, "One of the following schema(s) is invalid: \"#{tenant}\" #{full_search_path}"
78
89
  end
@@ -86,7 +97,23 @@ module Apartment
86
97
  end
87
98
 
88
99
  def create_tenant_command(conn, tenant)
89
- 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;")
90
117
  end
91
118
 
92
119
  # Generate the final search path to set including persistent_schemas
@@ -104,6 +131,24 @@ module Apartment
104
131
  # public from Rails 5.0.
105
132
  Apartment.connection.send(:postgresql_version)
106
133
  end
134
+
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
151
+ end
107
152
  end
108
153
 
109
154
  # Another Adapter for Postgresql when using schemas and SQL
@@ -171,9 +216,11 @@ module Apartment
171
216
  #
172
217
  # @return {String} raw SQL contaning inserts with data from schema_migrations
173
218
  #
219
+ # rubocop:disable Layout/LineLength
174
220
  def pg_dump_schema_migrations_data
175
221
  with_pg_env { `pg_dump -a --inserts -t #{default_tenant}.schema_migrations -t #{default_tenant}.ar_internal_metadata #{dbname}` }
176
222
  end
223
+ # rubocop:enable Layout/LineLength
177
224
 
178
225
  # Temporary set Postgresql related environment variables if there are in @config
179
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