apartment 1.2.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|