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.
- 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 +962 -0
- data/Gemfile +1 -1
- data/Guardfile +0 -15
- data/HISTORY.md +159 -68
- data/README.md +30 -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 +3 -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 +34 -7
- data/lib/apartment/console.rb +2 -8
- data/lib/apartment/custom_console.rb +2 -2
- data/lib/apartment/log_subscriber.rb +29 -0
- data/lib/apartment/railtie.rb +24 -21
- data/lib/apartment/tasks/enhancements.rb +1 -1
- data/lib/apartment/tasks/task_helper.rb +40 -0
- data/lib/apartment/tenant.rb +3 -16
- data/lib/apartment/version.rb +1 -1
- data/lib/generators/apartment/install/templates/apartment.rb +7 -1
- data/lib/tasks/apartment.rake +18 -46
- data/{apartment.gemspec → ros-apartment.gemspec} +3 -4
- metadata +17 -14
- 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
|
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.
|
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 `
|
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
|
-
|
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
|
@@ -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
|
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+)
|
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
|
@@ -93,7 +97,23 @@ module Apartment
|
|
93
97
|
end
|
94
98
|
|
95
99
|
def create_tenant_command(conn, tenant)
|
96
|
-
|
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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
#
|
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
|
@@ -7,9 +7,9 @@ module Apartment
|
|
7
7
|
begin
|
8
8
|
require 'pry-rails'
|
9
9
|
rescue LoadError
|
10
|
-
# rubocop:disable
|
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
|
12
|
+
# rubocop:enable Layout/LineLength
|
13
13
|
end
|
14
14
|
|
15
15
|
desc = "Includes the current Rails environment and project folder name.\n" \
|