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
@@ -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