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.
- checksums.yaml +4 -4
- data/.github/workflows/changelog.yml +63 -0
- data/.rubocop.yml +74 -4
- data/.rubocop_todo.yml +50 -13
- data/.story_branch.yml +1 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +949 -0
- data/Gemfile +1 -1
- data/Guardfile +0 -15
- data/HISTORY.md +159 -68
- data/README.md +13 -3
- data/Rakefile +4 -2
- data/TODO.md +0 -1
- data/gemfiles/rails_5_0.gemfile +1 -1
- data/gemfiles/rails_5_1.gemfile +1 -1
- data/gemfiles/rails_5_2.gemfile +1 -1
- data/gemfiles/rails_6_0.gemfile +1 -1
- data/gemfiles/rails_master.gemfile +1 -1
- data/lib/apartment.rb +13 -12
- data/lib/apartment/active_record/connection_handling.rb +3 -0
- data/lib/apartment/active_record/internal_metadata.rb +0 -2
- data/lib/apartment/active_record/schema_migration.rb +0 -2
- data/lib/apartment/adapters/abstract_adapter.rb +9 -4
- data/lib/apartment/adapters/abstract_jdbc_adapter.rb +2 -1
- data/lib/apartment/adapters/jdbc_postgresql_adapter.rb +2 -1
- data/lib/apartment/adapters/mysql2_adapter.rb +5 -0
- data/lib/apartment/adapters/postgresql_adapter.rb +48 -1
- data/lib/apartment/console.rb +2 -8
- data/lib/apartment/custom_console.rb +4 -2
- data/lib/apartment/log_subscriber.rb +27 -0
- data/lib/apartment/railtie.rb +15 -10
- data/lib/apartment/tasks/task_helper.rb +40 -0
- data/lib/apartment/tenant.rb +4 -8
- data/lib/apartment/version.rb +1 -1
- data/lib/generators/apartment/install/templates/apartment.rb +7 -1
- data/lib/tasks/apartment.rake +18 -42
- data/{apartment.gemspec → ros-apartment.gemspec} +2 -4
- metadata +15 -12
- 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
|
|
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.
|
|
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 `
|
|
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
|
-
|
|
50
|
-
|
|
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
data/gemfiles/rails_5_0.gemfile
CHANGED
data/gemfiles/rails_5_1.gemfile
CHANGED
data/gemfiles/rails_5_2.gemfile
CHANGED
data/gemfiles/rails_6_0.gemfile
CHANGED
data/lib/apartment.rb
CHANGED
|
@@ -6,25 +6,28 @@ require 'forwardable'
|
|
|
6
6
|
require 'active_record'
|
|
7
7
|
require 'apartment/tenant'
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
require_relative 'apartment/log_subscriber'
|
|
10
10
|
|
|
11
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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
|
-
#
|
|
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
|
|
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+)
|
|
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+)
|
|
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
|
-
|
|
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
|
#
|
data/lib/apartment/console.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|