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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +45 -4
  3. data/Appraisals +12 -9
  4. data/HISTORY.md +18 -0
  5. data/README.md +69 -42
  6. data/Rakefile +24 -5
  7. data/apartment.gemspec +6 -16
  8. data/docker-compose.yml +14 -0
  9. data/gemfiles/rails_4_0.gemfile +1 -1
  10. data/gemfiles/rails_4_1.gemfile +1 -1
  11. data/gemfiles/rails_4_2.gemfile +1 -1
  12. data/gemfiles/rails_5_0.gemfile +1 -1
  13. data/gemfiles/{rails_3_2.gemfile → rails_5_1.gemfile} +2 -3
  14. data/gemfiles/rails_master.gemfile +12 -0
  15. data/lib/apartment.rb +3 -25
  16. data/lib/apartment/adapters/abstract_adapter.rb +19 -44
  17. data/lib/apartment/adapters/postgresql_adapter.rb +17 -3
  18. data/lib/apartment/elevators/generic.rb +1 -20
  19. data/lib/apartment/elevators/subdomain.rb +15 -4
  20. data/lib/apartment/railtie.rb +5 -2
  21. data/lib/apartment/tenant.rb +0 -10
  22. data/lib/apartment/version.rb +1 -1
  23. data/lib/generators/apartment/install/templates/apartment.rb +13 -8
  24. data/lib/tasks/apartment.rake +2 -2
  25. data/spec/adapters/mysql2_adapter_spec.rb +8 -0
  26. data/spec/apartment_spec.rb +1 -5
  27. data/spec/dummy/config/application.rb +1 -1
  28. data/spec/dummy/config/database.yml.sample +3 -1
  29. data/spec/dummy/db/migrate/20110613152810_create_dummy_models.rb +2 -1
  30. data/spec/dummy/db/migrate/20111202022214_create_table_books.rb +2 -1
  31. data/spec/dummy_engine/config/initializers/apartment.rb +3 -3
  32. data/spec/examples/connection_adapter_examples.rb +8 -0
  33. data/spec/examples/generic_adapter_examples.rb +35 -17
  34. data/spec/examples/schema_adapter_examples.rb +8 -0
  35. data/spec/integration/query_caching_spec.rb +63 -23
  36. data/spec/spec_helper.rb +12 -0
  37. data/spec/tenant_spec.rb +10 -3
  38. data/spec/unit/config_spec.rb +0 -7
  39. data/spec/unit/elevators/subdomain_spec.rb +28 -8
  40. 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
@@ -0,0 +1,14 @@
1
+ version: '2'
2
+ services:
3
+ postgresql:
4
+ image: postgres:9.2
5
+ environment:
6
+ POSTGRES_PASSWORD: ""
7
+ ports:
8
+ - "5432:5432"
9
+ mysql:
10
+ image: mysql:5.7
11
+ environment:
12
+ MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
13
+ ports:
14
+ - "3306:3306"
@@ -9,4 +9,4 @@ group :local do
9
9
  gem "guard-rspec", "~> 4.2"
10
10
  end
11
11
 
12
- gemspec :path => "../"
12
+ gemspec path: "../"
@@ -9,4 +9,4 @@ group :local do
9
9
  gem "guard-rspec", "~> 4.2"
10
10
  end
11
11
 
12
- gemspec :path => "../"
12
+ gemspec path: "../"
@@ -9,4 +9,4 @@ group :local do
9
9
  gem "guard-rspec", "~> 4.2"
10
10
  end
11
11
 
12
- gemspec :path => "../"
12
+ gemspec path: "../"
@@ -9,4 +9,4 @@ group :local do
9
9
  gem "guard-rspec", "~> 4.2"
10
10
  end
11
11
 
12
- gemspec :path => "../"
12
+ gemspec path: "../"
@@ -2,12 +2,11 @@
2
2
 
3
3
  source "http://rubygems.org"
4
4
 
5
- gem "rails", "~> 3.2.0"
6
- gem "test-unit", "~> 3.0"
5
+ gem "rails", "5.1.1"
7
6
 
8
7
  group :local do
9
8
  gem "pry"
10
9
  gem "guard-rspec", "~> 4.2"
11
10
  end
12
11
 
13
- gemspec :path => "../"
12
+ gemspec path: "../"
@@ -0,0 +1,12 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "http://rubygems.org"
4
+
5
+ gem "rails", git: "https://github.com/rails/rails.git"
6
+
7
+ group :local do
8
+ gem "pry"
9
+ gem "guard-rspec", "~> 4.2"
10
+ end
11
+
12
+ gemspec path: "../"
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
- if block_given?
103
- begin
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
- end
115
- end
85
+ yield
116
86
 
117
- # [Deprecated]
118
- def process(tenant = nil, &block)
119
- Apartment::Deprecation.warn("[Deprecation Warning] `process` is now deprecated. Please use `switch`")
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{ load_or_abort(Apartment.seed_data_file) } if Apartment.seed_data_file
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
- load_or_abort(Apartment.database_schema_file) if Apartment.database_schema_file
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 abort if it doesn't exists
209
+ # Load a file or raise error if it doesn't exists
237
210
  #
238
- def load_or_abort(file)
211
+ def load_or_raise(file)
239
212
  if File.exists?(file)
240
213
  load(file)
241
214
  else
242
- abort %{#{file} doesn't exist yet}
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
- PGError
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? tenant
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 || parse_method
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
- return [] unless named_host?(host)
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
- host.split('.')[0..-(Apartment.tld_length + 2)]
53
+ def domain_valid?(host)
54
+ PublicSuffix.valid?(host, ignore_private: true)
44
55
  end
45
56
 
46
- def named_host?(host)
47
- !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
57
+ def parse_host(host)
58
+ (PublicSuffix.parse(host).trd || '').split('.')
48
59
  end
49
60
  end
50
61
  end
@@ -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
- Apartment::Tenant.init unless ARGV.include? 'assets:precompile'
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
  #
@@ -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