apartment 1.2.0 → 2.0.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/.travis.yml +45 -4
- data/Appraisals +12 -9
- data/HISTORY.md +18 -0
- data/README.md +69 -42
- data/Rakefile +24 -5
- data/apartment.gemspec +6 -16
- data/docker-compose.yml +14 -0
- data/gemfiles/rails_4_0.gemfile +1 -1
- data/gemfiles/rails_4_1.gemfile +1 -1
- data/gemfiles/rails_4_2.gemfile +1 -1
- data/gemfiles/rails_5_0.gemfile +1 -1
- data/gemfiles/{rails_3_2.gemfile → rails_5_1.gemfile} +2 -3
- data/gemfiles/rails_master.gemfile +12 -0
- data/lib/apartment.rb +3 -25
- data/lib/apartment/adapters/abstract_adapter.rb +19 -44
- data/lib/apartment/adapters/postgresql_adapter.rb +17 -3
- data/lib/apartment/elevators/generic.rb +1 -20
- data/lib/apartment/elevators/subdomain.rb +15 -4
- data/lib/apartment/railtie.rb +5 -2
- data/lib/apartment/tenant.rb +0 -10
- data/lib/apartment/version.rb +1 -1
- data/lib/generators/apartment/install/templates/apartment.rb +13 -8
- data/lib/tasks/apartment.rake +2 -2
- data/spec/adapters/mysql2_adapter_spec.rb +8 -0
- data/spec/apartment_spec.rb +1 -5
- data/spec/dummy/config/application.rb +1 -1
- data/spec/dummy/config/database.yml.sample +3 -1
- data/spec/dummy/db/migrate/20110613152810_create_dummy_models.rb +2 -1
- data/spec/dummy/db/migrate/20111202022214_create_table_books.rb +2 -1
- data/spec/dummy_engine/config/initializers/apartment.rb +3 -3
- data/spec/examples/connection_adapter_examples.rb +8 -0
- data/spec/examples/generic_adapter_examples.rb +35 -17
- data/spec/examples/schema_adapter_examples.rb +8 -0
- data/spec/integration/query_caching_spec.rb +63 -23
- data/spec/spec_helper.rb +12 -0
- data/spec/tenant_spec.rb +10 -3
- data/spec/unit/config_spec.rb +0 -7
- data/spec/unit/elevators/subdomain_spec.rb +28 -8
- metadata +20 -17
data/apartment.gemspec
CHANGED
@@ -18,25 +18,10 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.homepage = %q{https://github.com/influitive/apartment}
|
19
19
|
s.licenses = ["MIT"]
|
20
20
|
|
21
|
-
s.post_install_message = <<-MSG
|
22
|
-
********************************
|
23
|
-
|
24
|
-
Apartment Deprecation Warning
|
25
|
-
|
26
|
-
`Apartment::Tenant.process` has been deprecated in favour of `Apartment::Tenant.switch`.
|
27
|
-
You must now always pass a block to `switch`.
|
28
|
-
|
29
|
-
To get the previous `switch` behaviour where you can switch to a tenant
|
30
|
-
without a block, use `Apartment::Tenant.switch!`.
|
31
|
-
This is to indicate that your call actually has a side affect of changing
|
32
|
-
the scope of your queries to that tenant.
|
33
|
-
|
34
|
-
********************************
|
35
|
-
MSG
|
36
|
-
|
37
21
|
# must be >= 3.1.2 due to bug in prepared_statements
|
38
22
|
s.add_dependency 'activerecord', '>= 3.1.2', '< 6.0'
|
39
23
|
s.add_dependency 'rack', '>= 1.3.6'
|
24
|
+
s.add_dependency 'public_suffix', '~> 2.0.5'
|
40
25
|
|
41
26
|
s.add_development_dependency 'appraisal'
|
42
27
|
s.add_development_dependency 'rake', '~> 0.9'
|
@@ -56,4 +41,9 @@ Gem::Specification.new do |s|
|
|
56
41
|
s.add_development_dependency 'pg', '>= 0.11.0'
|
57
42
|
s.add_development_dependency 'sqlite3'
|
58
43
|
end
|
44
|
+
|
45
|
+
if RUBY_VERSION < '2.1.0'
|
46
|
+
# capybara depends on xpath depends on nokogiri
|
47
|
+
s.add_development_dependency 'nokogiri', '< 1.7.0'
|
48
|
+
end
|
59
49
|
end
|
data/docker-compose.yml
ADDED
data/gemfiles/rails_4_0.gemfile
CHANGED
data/gemfiles/rails_4_1.gemfile
CHANGED
data/gemfiles/rails_4_2.gemfile
CHANGED
data/gemfiles/rails_5_0.gemfile
CHANGED
data/lib/apartment.rb
CHANGED
@@ -3,7 +3,6 @@ require 'active_support/core_ext/object/blank'
|
|
3
3
|
require 'forwardable'
|
4
4
|
require 'active_record'
|
5
5
|
require 'apartment/tenant'
|
6
|
-
require 'apartment/deprecation'
|
7
6
|
|
8
7
|
module Apartment
|
9
8
|
|
@@ -75,35 +74,11 @@ module Apartment
|
|
75
74
|
@seed_data_file = "#{Rails.root}/db/seeds.rb"
|
76
75
|
end
|
77
76
|
|
78
|
-
def tld_length
|
79
|
-
@tld_length || 1
|
80
|
-
end
|
81
|
-
|
82
77
|
# Reset all the config for Apartment
|
83
78
|
def reset
|
84
79
|
(ACCESSOR_METHODS + WRITER_METHODS).each{|method| remove_instance_variable(:"@#{method}") if instance_variable_defined?(:"@#{method}") }
|
85
80
|
end
|
86
81
|
|
87
|
-
def database_names
|
88
|
-
Apartment::Deprecation.warn "[Deprecation Warning] `database_names` is now deprecated, please use `tenant_names`"
|
89
|
-
tenant_names
|
90
|
-
end
|
91
|
-
|
92
|
-
def database_names=(names)
|
93
|
-
Apartment::Deprecation.warn "[Deprecation Warning] `database_names=` is now deprecated, please use `tenant_names=`"
|
94
|
-
self.tenant_names=(names)
|
95
|
-
end
|
96
|
-
|
97
|
-
def use_postgres_schemas
|
98
|
-
Apartment::Deprecation.warn "[Deprecation Warning] `use_postgresql_schemas` is now deprecated, please use `use_schemas`"
|
99
|
-
use_schemas
|
100
|
-
end
|
101
|
-
|
102
|
-
def use_postgres_schemas=(to_use_or_not_to_use)
|
103
|
-
Apartment::Deprecation.warn "[Deprecation Warning] `use_postgresql_schemas=` is now deprecated, please use `use_schemas=`"
|
104
|
-
self.use_schemas = to_use_or_not_to_use
|
105
|
-
end
|
106
|
-
|
107
82
|
def extract_tenant_config
|
108
83
|
return {} unless @tenant_names
|
109
84
|
values = @tenant_names.respond_to?(:call) ? @tenant_names.call : @tenant_names
|
@@ -124,6 +99,9 @@ module Apartment
|
|
124
99
|
# Raised when apartment cannot find the adapter specified in <tt>config/database.yml</tt>
|
125
100
|
AdapterNotFound = Class.new(ApartmentError)
|
126
101
|
|
102
|
+
# Raised when apartment cannot find the file to be loaded
|
103
|
+
FileNotFound = Class.new(ApartmentError)
|
104
|
+
|
127
105
|
# Tenant specified is unknown
|
128
106
|
TenantNotFound = Class.new(ApartmentError)
|
129
107
|
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'apartment/deprecation'
|
2
|
-
|
3
1
|
module Apartment
|
4
2
|
module Adapters
|
5
3
|
class AbstractAdapter
|
@@ -34,24 +32,6 @@ module Apartment
|
|
34
32
|
end
|
35
33
|
end
|
36
34
|
|
37
|
-
# Get the current tenant name
|
38
|
-
#
|
39
|
-
# @return {String} current tenant name
|
40
|
-
#
|
41
|
-
def current_database
|
42
|
-
Apartment::Deprecation.warn "[Deprecation Warning] `current_database` is now deprecated, please use `current`"
|
43
|
-
current
|
44
|
-
end
|
45
|
-
|
46
|
-
# Get the current tenant name
|
47
|
-
#
|
48
|
-
# @return {String} current tenant name
|
49
|
-
#
|
50
|
-
def current_tenant
|
51
|
-
Apartment::Deprecation.warn "[Deprecation Warning] `current_tenant` is now deprecated, please use `current`"
|
52
|
-
current
|
53
|
-
end
|
54
|
-
|
55
35
|
# Note alias_method here doesn't work with inheritence apparently ??
|
56
36
|
#
|
57
37
|
def current
|
@@ -99,25 +79,14 @@ module Apartment
|
|
99
79
|
# @param {String?} tenant to connect to
|
100
80
|
#
|
101
81
|
def switch(tenant = nil)
|
102
|
-
|
103
|
-
|
104
|
-
previous_tenant = current
|
105
|
-
switch!(tenant)
|
106
|
-
yield
|
107
|
-
|
108
|
-
ensure
|
109
|
-
switch!(previous_tenant) rescue reset
|
110
|
-
end
|
111
|
-
else
|
112
|
-
Apartment::Deprecation.warn("[Deprecation Warning] `switch` now requires a block reset to the default tenant after the block. Please use `switch!` instead if you don't want this")
|
82
|
+
begin
|
83
|
+
previous_tenant = current
|
113
84
|
switch!(tenant)
|
114
|
-
|
115
|
-
end
|
85
|
+
yield
|
116
86
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
switch(tenant, &block)
|
87
|
+
ensure
|
88
|
+
switch!(previous_tenant) rescue reset
|
89
|
+
end
|
121
90
|
end
|
122
91
|
|
123
92
|
# Iterate over all tenants, switch to tenant and yield tenant name
|
@@ -147,7 +116,7 @@ module Apartment
|
|
147
116
|
#
|
148
117
|
def seed_data
|
149
118
|
# Don't log the output of seeding the db
|
150
|
-
silence_warnings{
|
119
|
+
silence_warnings{ load_or_raise(Apartment.seed_data_file) } if Apartment.seed_data_file
|
151
120
|
end
|
152
121
|
alias_method :seed, :seed_data
|
153
122
|
|
@@ -159,7 +128,7 @@ module Apartment
|
|
159
128
|
|
160
129
|
def drop_command(conn, tenant)
|
161
130
|
# connection.drop_database note that drop_database will not throw an exception, so manually execute
|
162
|
-
conn.execute("DROP DATABASE #{environmentify(tenant)}")
|
131
|
+
conn.execute("DROP DATABASE #{conn.quote_table_name(environmentify(tenant))}")
|
163
132
|
end
|
164
133
|
|
165
134
|
# Create the tenant
|
@@ -175,7 +144,7 @@ module Apartment
|
|
175
144
|
end
|
176
145
|
|
177
146
|
def create_tenant_command(conn, tenant)
|
178
|
-
conn.create_database(environmentify(tenant))
|
147
|
+
conn.create_database(environmentify(tenant), @config)
|
179
148
|
end
|
180
149
|
|
181
150
|
# Connect to new tenant
|
@@ -183,8 +152,12 @@ module Apartment
|
|
183
152
|
# @param {String} tenant Database name
|
184
153
|
#
|
185
154
|
def connect_to_new(tenant)
|
155
|
+
query_cache_enabled = ActiveRecord::Base.connection.query_cache_enabled
|
156
|
+
|
186
157
|
Apartment.establish_connection multi_tenantify(tenant)
|
187
158
|
Apartment.connection.active? # call active? to manually check if this connection is valid
|
159
|
+
|
160
|
+
Apartment.connection.enable_query_cache! if query_cache_enabled
|
188
161
|
rescue *rescuable_exceptions => exception
|
189
162
|
Apartment::Tenant.reset if reset_on_connection_exception?
|
190
163
|
raise_connect_error!(tenant, exception)
|
@@ -214,7 +187,7 @@ module Apartment
|
|
214
187
|
def import_database_schema
|
215
188
|
ActiveRecord::Schema.verbose = false # do not log schema load output.
|
216
189
|
|
217
|
-
|
190
|
+
load_or_raise(Apartment.database_schema_file) if Apartment.database_schema_file
|
218
191
|
end
|
219
192
|
|
220
193
|
# Return a new config that is multi-tenanted
|
@@ -233,15 +206,17 @@ module Apartment
|
|
233
206
|
config[:database] = environmentify(tenant)
|
234
207
|
end
|
235
208
|
|
236
|
-
# Load a file or
|
209
|
+
# Load a file or raise error if it doesn't exists
|
237
210
|
#
|
238
|
-
def
|
211
|
+
def load_or_raise(file)
|
239
212
|
if File.exists?(file)
|
240
213
|
load(file)
|
241
214
|
else
|
242
|
-
|
215
|
+
raise FileNotFound, "#{file} doesn't exist yet"
|
243
216
|
end
|
244
217
|
end
|
218
|
+
# Backward compatibility
|
219
|
+
alias_method :load_or_abort, :load_or_raise
|
245
220
|
|
246
221
|
# Exceptions to rescue from on db operations
|
247
222
|
#
|
@@ -18,7 +18,7 @@ module Apartment
|
|
18
18
|
private
|
19
19
|
|
20
20
|
def rescue_from
|
21
|
-
|
21
|
+
PG::Error
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
@@ -63,11 +63,18 @@ module Apartment
|
|
63
63
|
#
|
64
64
|
def connect_to_new(tenant = nil)
|
65
65
|
return reset if tenant.nil?
|
66
|
-
raise ActiveRecord::StatementInvalid.new("Could not find schema #{tenant}") unless Apartment.connection.schema_exists?
|
66
|
+
raise ActiveRecord::StatementInvalid.new("Could not find schema #{tenant}") unless Apartment.connection.schema_exists?(tenant.to_s)
|
67
67
|
|
68
68
|
@current = tenant.to_s
|
69
69
|
Apartment.connection.schema_search_path = full_search_path
|
70
70
|
|
71
|
+
# When the PostgreSQL version is < 9.3,
|
72
|
+
# there is a issue for prepared statement with changing search_path.
|
73
|
+
# https://www.postgresql.org/docs/9.3/static/sql-prepare.html
|
74
|
+
if postgresql_version < 90300
|
75
|
+
Apartment.connection.clear_cache!
|
76
|
+
end
|
77
|
+
|
71
78
|
rescue *rescuable_exceptions
|
72
79
|
raise TenantNotFound, "One of the following schema(s) is invalid: \"#{tenant}\" #{full_search_path}"
|
73
80
|
end
|
@@ -87,6 +94,12 @@ module Apartment
|
|
87
94
|
def persistent_schemas
|
88
95
|
[@current, Apartment.persistent_schemas].flatten
|
89
96
|
end
|
97
|
+
|
98
|
+
def postgresql_version
|
99
|
+
# ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#postgresql_version is
|
100
|
+
# public from Rails 5.0.
|
101
|
+
Apartment.connection.send(:postgresql_version)
|
102
|
+
end
|
90
103
|
end
|
91
104
|
|
92
105
|
# Another Adapter for Postgresql when using schemas and SQL
|
@@ -94,7 +107,8 @@ module Apartment
|
|
94
107
|
|
95
108
|
PSQL_DUMP_BLACKLISTED_STATEMENTS= [
|
96
109
|
/SET search_path/i, # overridden later
|
97
|
-
/SET lock_timeout/i # new in postgresql 9.3
|
110
|
+
/SET lock_timeout/i, # new in postgresql 9.3
|
111
|
+
/SET idle_in_transaction_session_timeout/i, # new in postgresql 9.6
|
98
112
|
]
|
99
113
|
|
100
114
|
def import_database_schema
|
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'rack/request'
|
2
2
|
require 'apartment/tenant'
|
3
|
-
require 'apartment/deprecation'
|
4
3
|
|
5
4
|
module Apartment
|
6
5
|
module Elevators
|
@@ -10,7 +9,7 @@ module Apartment
|
|
10
9
|
|
11
10
|
def initialize(app, processor = nil)
|
12
11
|
@app = app
|
13
|
-
@processor = processor ||
|
12
|
+
@processor = processor || method(:parse_tenant_name)
|
14
13
|
end
|
15
14
|
|
16
15
|
def call(env)
|
@@ -25,27 +24,9 @@ module Apartment
|
|
25
24
|
end
|
26
25
|
end
|
27
26
|
|
28
|
-
def parse_database_name(request)
|
29
|
-
deprecation_warning
|
30
|
-
parse_tenant_name(request)
|
31
|
-
end
|
32
|
-
|
33
27
|
def parse_tenant_name(request)
|
34
28
|
raise "Override"
|
35
29
|
end
|
36
|
-
|
37
|
-
def parse_method
|
38
|
-
if self.class.instance_methods(false).include? :parse_database_name
|
39
|
-
deprecation_warning
|
40
|
-
method(:parse_database_name)
|
41
|
-
else
|
42
|
-
method(:parse_tenant_name)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def deprecation_warning
|
47
|
-
Apartment::Deprecation.warn "[DEPRECATED::Apartment] Use #parse_tenant_name instead of #parse_database_name -> #{self.class.name}"
|
48
|
-
end
|
49
30
|
end
|
50
31
|
end
|
51
32
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'apartment/elevators/generic'
|
2
|
+
require 'public_suffix'
|
2
3
|
|
3
4
|
module Apartment
|
4
5
|
module Elevators
|
@@ -38,13 +39,23 @@ module Apartment
|
|
38
39
|
end
|
39
40
|
|
40
41
|
def subdomains(host)
|
41
|
-
|
42
|
+
host_valid?(host) ? parse_host(host) : []
|
43
|
+
end
|
44
|
+
|
45
|
+
def host_valid?(host)
|
46
|
+
!ip_host?(host) && domain_valid?(host)
|
47
|
+
end
|
48
|
+
|
49
|
+
def ip_host?(host)
|
50
|
+
!/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/.match(host).nil?
|
51
|
+
end
|
42
52
|
|
43
|
-
|
53
|
+
def domain_valid?(host)
|
54
|
+
PublicSuffix.valid?(host, ignore_private: true)
|
44
55
|
end
|
45
56
|
|
46
|
-
def
|
47
|
-
|
57
|
+
def parse_host(host)
|
58
|
+
(PublicSuffix.parse(host).trd || '').split('.')
|
48
59
|
end
|
49
60
|
end
|
50
61
|
end
|
data/lib/apartment/railtie.rb
CHANGED
@@ -17,7 +17,6 @@ module Apartment
|
|
17
17
|
config.seed_after_create = false
|
18
18
|
config.prepend_environment = false
|
19
19
|
config.append_environment = false
|
20
|
-
config.tld_length = 1
|
21
20
|
end
|
22
21
|
|
23
22
|
ActiveRecord::Migrator.migrations_paths = Rails.application.paths['db/migrate'].to_a
|
@@ -28,7 +27,11 @@ module Apartment
|
|
28
27
|
# See the middleware/console declarations below to help with this. Hope to fix that soon.
|
29
28
|
#
|
30
29
|
config.to_prepare do
|
31
|
-
|
30
|
+
unless ARGV.any? { |arg| arg =~ /\Aassets:(?:precompile|clean)\z/ }
|
31
|
+
Apartment.connection_class.connection_pool.with_connection do
|
32
|
+
Apartment::Tenant.init
|
33
|
+
end
|
34
|
+
end
|
32
35
|
end
|
33
36
|
|
34
37
|
#
|
data/lib/apartment/tenant.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'forwardable'
|
2
|
-
require 'apartment/deprecation'
|
3
2
|
|
4
3
|
module Apartment
|
5
4
|
# The main entry point to Apartment functions
|
@@ -64,13 +63,4 @@ module Apartment
|
|
64
63
|
@config ||= Apartment.connection_config
|
65
64
|
end
|
66
65
|
end
|
67
|
-
|
68
|
-
def self.const_missing(const_name)
|
69
|
-
if const_name == :Database
|
70
|
-
Apartment::Deprecation.warn "`Apartment::Database` has been deprecated. Use `Apartment::Tenant` instead."
|
71
|
-
Tenant
|
72
|
-
else
|
73
|
-
super
|
74
|
-
end
|
75
|
-
end
|
76
66
|
end
|