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
@@ -1,3 +1,3 @@
1
1
  module Apartment
2
- VERSION = "1.2.0"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -49,16 +49,21 @@ Apartment.configure do |config|
49
49
  #
50
50
  config.tenant_names = lambda { ToDo_Tenant_Or_User_Model.pluck :database }
51
51
 
52
+ # PostgreSQL:
53
+ # Specifies whether to use PostgreSQL schemas or create a new database per Tenant.
54
+ #
55
+ # MySQL:
56
+ # Specifies whether to switch databases by using `use` statement or re-establish connection.
52
57
  #
53
- # ==> PostgreSQL only options
54
-
55
- # Specifies whether to use PostgreSQL schemas or create a new database per Tenant.
56
58
  # The default behaviour is true.
57
59
  #
58
60
  # config.use_schemas = true
59
61
 
62
+ #
63
+ # ==> PostgreSQL only options
64
+
60
65
  # Apartment can be forced to use raw SQL dumps instead of schema.rb for creating new schemas.
61
- # Use this when you are using some extra features in PostgreSQL that can't be respresented in
66
+ # Use this when you are using some extra features in PostgreSQL that can't be represented in
62
67
  # schema.rb, like materialized views etc. (only applies with use_schemas set to true).
63
68
  # (Note: this option doesn't use db/structure.sql, it creates SQL dump by executing pg_dump)
64
69
  #
@@ -83,10 +88,10 @@ end
83
88
 
84
89
  # Setup a custom Tenant switching middleware. The Proc should return the name of the Tenant that
85
90
  # you want to switch to.
86
- # Rails.application.config.middleware.use 'Apartment::Elevators::Generic', lambda { |request|
91
+ # Rails.application.config.middleware.use Apartment::Elevators::Generic, lambda { |request|
87
92
  # request.host.split('.').first
88
93
  # }
89
94
 
90
- # Rails.application.config.middleware.use 'Apartment::Elevators::Domain'
91
- Rails.application.config.middleware.use 'Apartment::Elevators::Subdomain'
92
- # Rails.application.config.middleware.use 'Apartment::Elevators::FirstSubdomain'
95
+ # Rails.application.config.middleware.use Apartment::Elevators::Domain
96
+ Rails.application.config.middleware.use Apartment::Elevators::Subdomain
97
+ # Rails.application.config.middleware.use Apartment::Elevators::FirstSubdomain
@@ -3,11 +3,11 @@ require 'apartment/migrator'
3
3
  apartment_namespace = namespace :apartment do
4
4
 
5
5
  desc "Create all tenants"
6
- task create: 'db:migrate' do
6
+ task :create do
7
7
  tenants.each do |tenant|
8
8
  begin
9
9
  puts("Creating #{tenant} tenant")
10
- quietly { Apartment::Tenant.create(tenant) }
10
+ Apartment::Tenant.create(tenant)
11
11
  rescue Apartment::TenantExists => e
12
12
  puts e.message
13
13
  end
@@ -32,6 +32,14 @@ describe Apartment::Adapters::Mysql2Adapter, database: :mysql do
32
32
  end
33
33
  end
34
34
 
35
+ after do
36
+ # Apartment::Tenant.init creates per model connection.
37
+ # Remove the connection after testing not to unintentionally keep the connection across tests.
38
+ Apartment.excluded_models.each do |excluded_model|
39
+ excluded_model.constantize.remove_connection
40
+ end
41
+ end
42
+
35
43
  it "should process model exclusions" do
36
44
  Apartment::Tenant.init
37
45
 
@@ -8,8 +8,4 @@ describe Apartment do
8
8
  it "should be a valid app" do
9
9
  expect(::Rails.application).to be_a(Dummy::Application)
10
10
  end
11
-
12
- it "should deprecate Apartment::Database in favor of Apartment::Tenant" do
13
- expect(Apartment::Database).to eq(Apartment::Tenant)
14
- end
15
- end
11
+ end
@@ -17,7 +17,7 @@ module Dummy
17
17
  require 'apartment/elevators/subdomain'
18
18
  require 'apartment/elevators/domain'
19
19
 
20
- config.middleware.use 'Apartment::Elevators::Subdomain'
20
+ config.middleware.use Apartment::Elevators::Subdomain
21
21
 
22
22
  # Custom directories with classes and modules you want to be autoloadable.
23
23
  config.autoload_paths += %W(#{config.root}/lib)
@@ -25,6 +25,7 @@ development:
25
25
  test:
26
26
  adapter: postgresql
27
27
  database: apartment_postgresql_test
28
+ username: postgres
28
29
  min_messages: WARNING
29
30
  pool: 5
30
31
  timeout: 5000
@@ -32,7 +33,8 @@ test:
32
33
  development:
33
34
  adapter: postgresql
34
35
  database: apartment_postgresql_development
36
+ username: postgres
35
37
  min_messages: WARNING
36
38
  pool: 5
37
39
  timeout: 5000
38
- <% end %>
40
+ <% end %>
@@ -1,4 +1,5 @@
1
- class CreateDummyModels < ActiveRecord::Migration
1
+ migration_class = (ActiveRecord::VERSION::MAJOR >= 5) ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration
2
+ class CreateDummyModels < migration_class
2
3
  def self.up
3
4
  create_table :companies do |t|
4
5
  t.boolean :dummy
@@ -1,4 +1,5 @@
1
- class CreateTableBooks < ActiveRecord::Migration
1
+ migration_class = (ActiveRecord::VERSION::MAJOR >= 5) ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration
2
+ class CreateTableBooks < migration_class
2
3
  def up
3
4
  create_table :books do |t|
4
5
  t.string :name
@@ -42,10 +42,10 @@ end
42
42
  ##
43
43
  # Elevator Configuration
44
44
 
45
- # Rails.application.config.middleware.use 'Apartment::Elevators::Generic', lambda { |request|
45
+ # Rails.application.config.middleware.use Apartment::Elevators::Generic, lambda { |request|
46
46
  # # TODO: supply generic implementation
47
47
  # }
48
48
 
49
- # Rails.application.config.middleware.use 'Apartment::Elevators::Domain'
49
+ # Rails.application.config.middleware.use Apartment::Elevators::Domain
50
50
 
51
- Rails.application.config.middleware.use 'Apartment::Elevators::Subdomain'
51
+ Rails.application.config.middleware.use Apartment::Elevators::Subdomain
@@ -6,6 +6,14 @@ shared_examples_for "a connection based apartment adapter" do
6
6
  let(:default_tenant){ subject.switch{ ActiveRecord::Base.connection.current_database } }
7
7
 
8
8
  describe "#init" do
9
+ after do
10
+ # Apartment::Tenant.init creates per model connection.
11
+ # Remove the connection after testing not to unintentionally keep the connection across tests.
12
+ Apartment.excluded_models.each do |excluded_model|
13
+ excluded_model.constantize.remove_connection
14
+ end
15
+ end
16
+
9
17
  it "should process model exclusions" do
10
18
  Apartment.configure do |config|
11
19
  config.excluded_models = ["Company"]
@@ -8,6 +8,25 @@ shared_examples_for "a generic apartment adapter" do
8
8
  Apartment.append_environment = false
9
9
  }
10
10
 
11
+ describe "#init" do
12
+ it "should not retain a connection after railtie" do
13
+ # this test should work on rails >= 4, the connection pool code is
14
+ # completely different for 3.2 so we'd have to have a messy conditional..
15
+ unless Rails::VERSION::MAJOR < 4
16
+ ActiveRecord::Base.connection_pool.disconnect!
17
+
18
+ Apartment::Railtie.config.to_prepare_blocks.map(&:call)
19
+
20
+ num_available_connections = Apartment.connection_class.connection_pool
21
+ .instance_variable_get(:@available)
22
+ .instance_variable_get(:@queue)
23
+ .size
24
+
25
+ expect(num_available_connections).to eq(1)
26
+ end
27
+ end
28
+ end
29
+
11
30
  #
12
31
  # Creates happen already in our before_filter
13
32
  #
@@ -39,6 +58,22 @@ shared_examples_for "a generic apartment adapter" do
39
58
 
40
59
  subject.switch(db2){ expect(User.count).to eq(@count + 1) }
41
60
  end
61
+
62
+ it "should raise error when the schema.rb is missing unless Apartment.use_sql is set to true" do
63
+ next if Apartment.use_sql
64
+
65
+ subject.drop(db1)
66
+ begin
67
+ Dir.mktmpdir do |tmpdir|
68
+ Apartment.database_schema_file = "#{tmpdir}/schema.rb"
69
+ expect {
70
+ subject.create(db1)
71
+ }.to raise_error(Apartment::FileNotFound)
72
+ end
73
+ ensure
74
+ Apartment.remove_instance_variable(:@database_schema_file)
75
+ end
76
+ end
42
77
  end
43
78
 
44
79
  describe "#drop" do
@@ -83,23 +118,6 @@ shared_examples_for "a generic apartment adapter" do
83
118
  subject.switch(db1){ subject.drop(db2) }
84
119
  }.to_not raise_error
85
120
  end
86
-
87
- it "warns if no block is given, but calls switch!" do
88
- expect(Apartment::Deprecation).to receive(:warn)
89
-
90
- subject.switch(db1)
91
- expect(subject.current).to eq(db1)
92
- end
93
- end
94
-
95
- describe "#process" do
96
- it "is deprecated" do
97
- expect(Apartment::Deprecation).to receive(:warn)
98
-
99
- subject.process(db1) do
100
- expect(subject.current).to eq(db1)
101
- end
102
- end
103
121
  end
104
122
 
105
123
  describe "#reset" do
@@ -15,6 +15,14 @@ shared_examples_for "a schema based apartment adapter" do
15
15
  end
16
16
  end
17
17
 
18
+ after do
19
+ # Apartment::Tenant.init creates per model connection.
20
+ # Remove the connection after testing not to unintentionally keep the connection across tests.
21
+ Apartment.excluded_models.each do |excluded_model|
22
+ excluded_model.constantize.remove_connection
23
+ end
24
+ end
25
+
18
26
  it "should process model exclusions" do
19
27
  Apartment::Tenant.init
20
28
 
@@ -1,41 +1,81 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe 'query caching' do
4
- let(:db_names) { [db1, db2] }
4
+ describe 'when use_schemas = true' do
5
+ let(:db_names) { [db1, db2] }
5
6
 
6
- before do
7
- Apartment.configure do |config|
8
- config.excluded_models = ["Company"]
9
- config.tenant_names = lambda{ Company.pluck(:database) }
10
- config.use_schemas = true
7
+ before do
8
+ Apartment.configure do |config|
9
+ config.excluded_models = ["Company"]
10
+ config.tenant_names = lambda{ Company.pluck(:database) }
11
+ config.use_schemas = true
12
+ end
13
+
14
+ Apartment::Tenant.reload!(config)
15
+
16
+ db_names.each do |db_name|
17
+ Apartment::Tenant.create(db_name)
18
+ Company.create database: db_name
19
+ end
20
+ end
21
+
22
+ after do
23
+ db_names.each{ |db| Apartment::Tenant.drop(db) }
24
+ Apartment::Tenant.reset
25
+ Company.delete_all
26
+ end
27
+
28
+ it 'clears the ActiveRecord::QueryCache after switching databases' do
29
+ db_names.each do |db_name|
30
+ Apartment::Tenant.switch! db_name
31
+ User.create! name: db_name
32
+ end
33
+
34
+ ActiveRecord::Base.connection.enable_query_cache!
35
+
36
+ Apartment::Tenant.switch! db_names.first
37
+ expect(User.find_by_name(db_names.first).name).to eq(db_names.first)
38
+
39
+ Apartment::Tenant.switch! db_names.last
40
+ expect(User.find_by_name(db_names.first)).to be_nil
11
41
  end
42
+ end
12
43
 
13
- Apartment::Tenant.reload!(config)
44
+ describe 'when use_schemas = false' do
45
+ let(:db_name) { db1 }
46
+
47
+ before do
48
+ Apartment.configure do |config|
49
+ config.excluded_models = ["Company"]
50
+ config.tenant_names = lambda{ Company.pluck(:database) }
51
+ config.use_schemas = false
52
+ end
53
+
54
+ Apartment::Tenant.reload!(config)
14
55
 
15
- db_names.each do |db_name|
16
56
  Apartment::Tenant.create(db_name)
17
57
  Company.create database: db_name
18
58
  end
19
- end
20
59
 
21
- after do
22
- db_names.each{ |db| Apartment::Tenant.drop(db) }
23
- Apartment::Tenant.reset
24
- Company.delete_all
25
- end
60
+ after do
61
+ # Avoid cannot drop the currently open database. Maybe there is a better way to handle this.
62
+ Apartment::Tenant.switch! 'template1'
26
63
 
27
- it 'clears the ActiveRecord::QueryCache after switching databases' do
28
- db_names.each do |db_name|
29
- Apartment::Tenant.switch! db_name
30
- User.create! name: db_name
64
+ Apartment::Tenant.drop(db_name)
65
+ Apartment::Tenant.reset
66
+ Company.delete_all
31
67
  end
32
68
 
33
- ActiveRecord::Base.connection.enable_query_cache!
69
+ it "configuration value is kept after switching databases" do
70
+ ActiveRecord::Base.connection.enable_query_cache!
34
71
 
35
- Apartment::Tenant.switch! db_names.first
36
- expect(User.find_by_name(db_names.first).name).to eq(db_names.first)
72
+ Apartment::Tenant.switch! db_name
73
+ expect(Apartment.connection.query_cache_enabled).to be true
74
+
75
+ ActiveRecord::Base.connection.disable_query_cache!
37
76
 
38
- Apartment::Tenant.switch! db_names.last
39
- expect(User.find_by_name(db_names.first)).to be_nil
77
+ Apartment::Tenant.switch! db_name
78
+ expect(Apartment.connection.query_cache_enabled).to be false
79
+ end
40
80
  end
41
81
  end
data/spec/spec_helper.rb CHANGED
@@ -4,6 +4,18 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
4
  ENV["RAILS_ENV"] = "test"
5
5
 
6
6
  require File.expand_path("../dummy/config/environment.rb", __FILE__)
7
+
8
+ # Loading dummy applications affects table_name of each excluded models
9
+ # defined in `spec/dummy/config/initializers/apartment.rb`.
10
+ # To make them pristine, we need to execute below lines.
11
+ Apartment.excluded_models.each do |model|
12
+ klass = model.constantize
13
+
14
+ Apartment.connection_class.remove_connection(klass)
15
+ klass.clear_all_connections!
16
+ klass.reset_table_name
17
+ end
18
+
7
19
  require "rspec/rails"
8
20
  require 'capybara/rspec'
9
21
  require 'capybara/rails'
data/spec/tenant_spec.rb CHANGED
@@ -59,8 +59,7 @@ describe Apartment::Tenant do
59
59
 
60
60
  describe "#adapter" do
61
61
  it "should load postgresql adapter" do
62
- subject.adapter
63
- expect(Apartment::Adapters::PostgresqlAdapter).to be_a(Class)
62
+ expect(subject.adapter).to be_a(Apartment::Adapters::PostgresqlSchemaAdapter)
64
63
  end
65
64
 
66
65
  it "raises exception with invalid adapter specified" do
@@ -68,7 +67,7 @@ describe Apartment::Tenant do
68
67
 
69
68
  expect {
70
69
  Apartment::Tenant.adapter
71
- }.to raise_error
70
+ }.to raise_error(RuntimeError)
72
71
  end
73
72
 
74
73
  context "threadsafety" do
@@ -137,6 +136,14 @@ describe Apartment::Tenant do
137
136
  subject.init
138
137
  end
139
138
 
139
+ after do
140
+ # Apartment::Tenant.init creates per model connection.
141
+ # Remove the connection after testing not to unintentionally keep the connection across tests.
142
+ Apartment.excluded_models.each do |excluded_model|
143
+ excluded_model.constantize.remove_connection
144
+ end
145
+ end
146
+
140
147
  it "should create excluded models in public schema" do
141
148
  subject.reset # ensure we're on public schema
142
149
  count = Company.count + x.times{ Company.create }
@@ -50,13 +50,6 @@ describe Apartment do
50
50
  expect(Apartment.seed_after_create).to be true
51
51
  end
52
52
 
53
- it "should set tld_length" do
54
- Apartment.configure do |config|
55
- config.tld_length = 2
56
- end
57
- expect(Apartment.tld_length).to eq(2)
58
- end
59
-
60
53
  context "databases" do
61
54
  let(:users_conf_hash) { { port: 5444 } }
62
55
 
@@ -6,7 +6,7 @@ describe Apartment::Elevators::Subdomain do
6
6
  subject(:elevator){ described_class.new(Proc.new{}) }
7
7
 
8
8
  describe "#parse_tenant_name" do
9
- context "assuming tld_length of 1" do
9
+ context "assuming one tld" do
10
10
  it "should parse subdomain" do
11
11
  request = ActionDispatch::Request.new('HTTP_HOST' => 'foo.bar.com')
12
12
  expect(elevator.parse_tenant_name(request)).to eq('foo')
@@ -18,13 +18,7 @@ describe Apartment::Elevators::Subdomain do
18
18
  end
19
19
  end
20
20
 
21
- context "assuming tld_length of 2" do
22
- before do
23
- Apartment.configure do |config|
24
- config.tld_length = 2
25
- end
26
- end
27
-
21
+ context "assuming two tlds" do
28
22
  it "should parse subdomain in the third level domain" do
29
23
  request = ActionDispatch::Request.new('HTTP_HOST' => 'foo.bar.co.uk')
30
24
  expect(elevator.parse_tenant_name(request)).to eq("foo")
@@ -35,6 +29,32 @@ describe Apartment::Elevators::Subdomain do
35
29
  expect(elevator.parse_tenant_name(request)).to be_nil
36
30
  end
37
31
  end
32
+
33
+ context "assuming two subdomains" do
34
+ it "should parse two subdomains in the two level domain" do
35
+ request = ActionDispatch::Request.new('HTTP_HOST' => 'foo.xyz.bar.com')
36
+ expect(elevator.parse_tenant_name(request)).to eq("foo")
37
+ end
38
+
39
+ it "should parse two subdomains in the third level domain" do
40
+ request = ActionDispatch::Request.new('HTTP_HOST' => 'foo.xyz.bar.co.uk')
41
+ expect(elevator.parse_tenant_name(request)).to eq("foo")
42
+ end
43
+ end
44
+
45
+ context "assuming localhost" do
46
+ it "should return nil for localhost" do
47
+ request = ActionDispatch::Request.new('HTTP_HOST' => 'localhost')
48
+ expect(elevator.parse_tenant_name(request)).to be_nil
49
+ end
50
+ end
51
+
52
+ context "assuming ip address" do
53
+ it "should return nil for an ip address" do
54
+ request = ActionDispatch::Request.new('HTTP_HOST' => '127.0.0.1')
55
+ expect(elevator.parse_tenant_name(request)).to be_nil
56
+ end
57
+ end
38
58
  end
39
59
 
40
60
  describe "#call" do