apartment 1.2.0 → 2.0.0

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