apartment 0.1.3 → 0.5.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.
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +5 -12
- data/HISTORY.md +31 -0
- data/{README.markdown → README.md} +39 -21
- data/Rakefile +44 -47
- data/apartment.gemspec +19 -58
- data/lib/apartment.rb +57 -6
- data/lib/apartment/adapters/abstract_adapter.rb +106 -0
- data/lib/apartment/adapters/mysql_adapter.rb +11 -0
- data/lib/apartment/adapters/postgresql_adapter.rb +55 -0
- data/lib/apartment/database.rb +58 -74
- data/lib/apartment/elevators/subdomain.rb +27 -0
- data/lib/apartment/migrator.rb +23 -0
- data/lib/apartment/railtie.rb +1 -2
- data/lib/apartment/version.rb +3 -0
- data/lib/tasks/apartment.rake +36 -0
- data/spec/apartment_spec.rb +7 -0
- data/spec/config/database.yml +10 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +6 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/company.rb +3 -0
- data/spec/dummy/app/models/user.rb +3 -0
- data/spec/dummy/app/views/application/index.html.erb +1 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +47 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +8 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +26 -0
- data/spec/dummy/config/environments/production.rb +49 -0
- data/spec/dummy/config/environments/test.rb +35 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/db/migrate/20110613152810_create_dummy_models.rb +20 -0
- data/spec/dummy/db/schema.rb +26 -0
- data/spec/dummy/db/seeds.rb +8 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/{lib/apartment/associations/multi_tenant_association.rb → spec/dummy/public/favicon.ico} +0 -0
- data/spec/dummy/public/stylesheets/.gitkeep +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/integration/adapters/postgresql_integration_spec.rb +73 -0
- data/spec/integration/apartment_rake_integration_spec.rb +62 -0
- data/spec/integration/database_integration_spec.rb +107 -0
- data/spec/integration/middleware/subdomain_elevator_spec.rb +63 -0
- data/spec/integration/setup_spec.rb +8 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/apartment_helpers.rb +24 -0
- data/spec/support/capybara_sessions.rb +15 -0
- data/spec/support/config.rb +11 -0
- data/spec/support/migrate.rb +9 -0
- data/spec/tasks/apartment_rake_spec.rb +110 -0
- data/spec/unit/config_spec.rb +84 -0
- data/spec/unit/middleware/subdomain_elevator_spec.rb +20 -0
- data/spec/unit/migrator_spec.rb +87 -0
- metadata +112 -62
- data/.bundle/config +0 -2
- data/.project +0 -11
- data/Gemfile.lock +0 -22
- data/VERSION +0 -1
- data/lib/apartment/config.rb +0 -10
- data/lib/apartment/config/default_config.yml +0 -17
- data/lib/tasks/multi_tenant_migrate.rake +0 -14
- data/pkg/apartment-0.1.3.gem +0 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Apartment::Database do
|
4
|
+
|
5
|
+
context "using postgresql" do
|
6
|
+
|
7
|
+
# See apartment.yml file in dummy app config
|
8
|
+
|
9
|
+
let(:config){ Apartment::Test.config['connections']['postgresql'].symbolize_keys }
|
10
|
+
let(:database){ "some_new_database" }
|
11
|
+
|
12
|
+
before do
|
13
|
+
ActiveRecord::Base.establish_connection config
|
14
|
+
Apartment::Database.stub(:config).and_return config # Use postgresql database config for this test
|
15
|
+
@schema_search_path = ActiveRecord::Base.connection.schema_search_path
|
16
|
+
end
|
17
|
+
|
18
|
+
after do
|
19
|
+
Apartment::Test.reset
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#init" do
|
23
|
+
|
24
|
+
it "should process model exclusions" do
|
25
|
+
Company.should_receive(:establish_connection).with( config )
|
26
|
+
|
27
|
+
Apartment.configure do |config|
|
28
|
+
config.excluded_models = [Company]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#adapter" do
|
35
|
+
before do
|
36
|
+
Apartment::Database.reload!
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should load postgresql adapter" do
|
40
|
+
Apartment::Database.adapter
|
41
|
+
Apartment::Adapters::PostgresqlAdapter.should be_a(Class)
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should raise exception with invalid adapter specified" do
|
46
|
+
Apartment::Database.stub(:config).and_return config.merge(:adapter => 'unkown')
|
47
|
+
|
48
|
+
expect {
|
49
|
+
Apartment::Database.adapter
|
50
|
+
}.to raise_error
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
context "with schemas" do
|
56
|
+
|
57
|
+
before do
|
58
|
+
Apartment.configure do |config|
|
59
|
+
config.excluded_models = []
|
60
|
+
config.use_postgres_schemas = true
|
61
|
+
config.seed_after_create = true
|
62
|
+
end
|
63
|
+
Apartment::Database.create database
|
64
|
+
end
|
65
|
+
|
66
|
+
after do
|
67
|
+
Apartment::Test.drop_schema(database)
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#create" do
|
71
|
+
it "should create new postgres schema" do
|
72
|
+
ActiveRecord::Base.connection.execute("SELECT nspname FROM pg_namespace;").collect{|row| row['nspname']}.should include(database)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should seed data" do
|
76
|
+
Apartment::Database.switch database
|
77
|
+
User.count.should be > 0
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "#switch" do
|
82
|
+
|
83
|
+
it "should connect to new schema" do
|
84
|
+
Apartment::Database.switch database
|
85
|
+
ActiveRecord::Base.connection.schema_search_path.should == database
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should ignore excluded models" do
|
89
|
+
Apartment.configure do |config|
|
90
|
+
config.excluded_models = [Company]
|
91
|
+
end
|
92
|
+
|
93
|
+
Apartment::Database.switch database
|
94
|
+
Company.connection.schema_search_path.should == @schema_search_path
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should fail with invalid schema" do
|
98
|
+
expect {
|
99
|
+
Apartment::Database.switch('some_nonexistent_schema')
|
100
|
+
}.to raise_error Apartment::SchemaNotFound
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Apartment::Elevators::Subdomain do
|
4
|
+
|
5
|
+
let(:company){ mock_model(Company, :subdomain => 'foo').as_null_object }
|
6
|
+
let(:domain){ "http://#{company.subdomain}.domain.com" }
|
7
|
+
|
8
|
+
before do
|
9
|
+
Apartment.seed_after_create = false
|
10
|
+
Apartment.use_postgres_schemas = true
|
11
|
+
|
12
|
+
Apartment::Database.create(company.subdomain)
|
13
|
+
end
|
14
|
+
|
15
|
+
after do
|
16
|
+
Apartment::Test.drop_schema(company.subdomain)
|
17
|
+
end
|
18
|
+
|
19
|
+
context "single request" do
|
20
|
+
it "should switch the db" do
|
21
|
+
ActiveRecord::Base.connection.schema_search_path.should_not == 'foo'
|
22
|
+
|
23
|
+
visit(domain)
|
24
|
+
ActiveRecord::Base.connection.schema_search_path.should == company.subdomain
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "simultaneous requests" do
|
29
|
+
let(:company2){ mock_model(Company, :subdomain => 'bar').as_null_object }
|
30
|
+
let(:domain2){ "http://#{company2.subdomain}.domain.com" }
|
31
|
+
|
32
|
+
before do
|
33
|
+
Apartment::Database.create(company2.subdomain)
|
34
|
+
# Create some users for each db
|
35
|
+
Apartment::Test.in_database(company.subdomain) do
|
36
|
+
@c1_user_count = (2 + rand(2)).times{ User.create }
|
37
|
+
end
|
38
|
+
|
39
|
+
Apartment::Test.in_database(company2.subdomain) do
|
40
|
+
@c2_user_count = (@c1_user_count + 2).times{ User.create }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
after do
|
45
|
+
Apartment::Test.drop_schema(company2.subdomain)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should fetch the correct user count for each session based on subdomain" do
|
49
|
+
visit(domain)
|
50
|
+
|
51
|
+
in_new_session do |session|
|
52
|
+
session.visit(domain2)
|
53
|
+
User.count.should == @c2_user_count
|
54
|
+
end
|
55
|
+
|
56
|
+
visit(domain)
|
57
|
+
User.count.should == @c1_user_count
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
# Configure Rails Environment
|
4
|
+
ENV["RAILS_ENV"] = "test"
|
5
|
+
|
6
|
+
require File.expand_path("../dummy/config/environment.rb", __FILE__)
|
7
|
+
require "rspec/rails"
|
8
|
+
require 'capybara/rspec'
|
9
|
+
require 'capybara/rails'
|
10
|
+
|
11
|
+
|
12
|
+
ActionMailer::Base.delivery_method = :test
|
13
|
+
ActionMailer::Base.perform_deliveries = true
|
14
|
+
ActionMailer::Base.default_url_options[:host] = "test.com"
|
15
|
+
|
16
|
+
Rails.backtrace_cleaner.remove_silencers!
|
17
|
+
|
18
|
+
# Load support files
|
19
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
20
|
+
|
21
|
+
|
22
|
+
RSpec.configure do |config|
|
23
|
+
|
24
|
+
config.include RSpec::Integration::CapybaraSessions, :type => :request
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Apartment
|
2
|
+
module Test
|
3
|
+
def self.reset
|
4
|
+
Apartment::Database.instance_variable_set :@initialized, nil
|
5
|
+
Apartment.excluded_models = nil
|
6
|
+
Apartment.use_postgres_schemas = nil
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.drop_schema(schema)
|
10
|
+
ActiveRecord::Base.connection.execute("DROP SCHEMA IF EXISTS #{schema} CASCADE")
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.create_schema(schema)
|
14
|
+
ActiveRecord::Base.connection.execute("CREATE SCHEMA #{schema}")
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.in_database(db)
|
18
|
+
Apartment::Database.switch db
|
19
|
+
yield if block_given?
|
20
|
+
Apartment::Database.reset
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
describe "apartment rake tasks" do
|
5
|
+
|
6
|
+
before do
|
7
|
+
@rake = Rake::Application.new
|
8
|
+
Rake.application = @rake
|
9
|
+
load 'tasks/apartment.rake'
|
10
|
+
Rake::Task.define_task(:environment) # stub out loading rails environment
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
Rake.application = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:version){ '1234' }
|
18
|
+
|
19
|
+
context 'database migration' do
|
20
|
+
|
21
|
+
let(:database_names){ ['company1', 'company2', 'company3'] }
|
22
|
+
let(:db_count){ database_names.length }
|
23
|
+
|
24
|
+
before do
|
25
|
+
Apartment.stub(:database_names).and_return database_names
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "apartment:migrate" do
|
29
|
+
before do
|
30
|
+
ActiveRecord::Migrator.stub(:migrate) # don't care about this
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should migrate all multi-tenant dbs" do
|
34
|
+
Apartment::Migrator.should_receive(:migrate).exactly(db_count).times
|
35
|
+
@rake['apartment:migrate'].invoke
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "apartment:migrate:up" do
|
40
|
+
|
41
|
+
context "without a version" do
|
42
|
+
before do
|
43
|
+
ENV['VERSION'] = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
it "requires a version to migrate to" do
|
47
|
+
lambda{
|
48
|
+
@rake['apartment:migrate:up'].invoke
|
49
|
+
}.should raise_error("VERSION is required")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "with version" do
|
54
|
+
|
55
|
+
before do
|
56
|
+
ENV['VERSION'] = version
|
57
|
+
end
|
58
|
+
|
59
|
+
it "migrates up to a specific version" do
|
60
|
+
Apartment::Migrator.should_receive(:run).with(:up, anything, version.to_i).exactly(db_count).times
|
61
|
+
@rake['apartment:migrate:up'].invoke
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "apartment:migrate:down" do
|
67
|
+
|
68
|
+
context "without a version" do
|
69
|
+
before do
|
70
|
+
ENV['VERSION'] = nil
|
71
|
+
end
|
72
|
+
|
73
|
+
it "requires a version to migrate to" do
|
74
|
+
lambda{
|
75
|
+
@rake['apartment:migrate:down'].invoke
|
76
|
+
}.should raise_error("VERSION is required")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "with version" do
|
81
|
+
|
82
|
+
before do
|
83
|
+
ENV['VERSION'] = version
|
84
|
+
end
|
85
|
+
|
86
|
+
it "migrates up to a specific version" do
|
87
|
+
Apartment::Migrator.should_receive(:run).with(:down, anything, version.to_i).exactly(db_count).times
|
88
|
+
@rake['apartment:migrate:down'].invoke
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "apartment:rollback" do
|
94
|
+
|
95
|
+
let(:step){ '3' }
|
96
|
+
|
97
|
+
it "should rollback dbs" do
|
98
|
+
Apartment::Migrator.should_receive(:rollback).exactly(db_count).times
|
99
|
+
@rake['apartment:rollback'].invoke
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should rollback dbs STEP amt" do
|
103
|
+
Apartment::Migrator.should_receive(:rollback).with(anything, step.to_i).exactly(db_count).times
|
104
|
+
ENV['STEP'] = step
|
105
|
+
@rake['apartment:rollback'].invoke
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Apartment do
|
4
|
+
|
5
|
+
describe "#config" do
|
6
|
+
|
7
|
+
let(:excluded_models){ [Company] }
|
8
|
+
|
9
|
+
after do
|
10
|
+
Apartment::Test.reset
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should yield the Apartment object" do
|
14
|
+
Apartment.configure do |config|
|
15
|
+
config.excluded_models = []
|
16
|
+
config.should == Apartment
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should initialize Database" do
|
21
|
+
Apartment::Database.should_receive(:init).once
|
22
|
+
Apartment.configure
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should set excluded models" do
|
26
|
+
Apartment.configure do |config|
|
27
|
+
config.excluded_models = excluded_models
|
28
|
+
end
|
29
|
+
Apartment.excluded_models.should == excluded_models
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should set postgres_schemas" do
|
33
|
+
Apartment.configure do |config|
|
34
|
+
config.excluded_models = []
|
35
|
+
config.use_postgres_schemas = false
|
36
|
+
end
|
37
|
+
Apartment.use_postgres_schemas.should be_false
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should set seed_after_create" do
|
41
|
+
Apartment.configure do |config|
|
42
|
+
config.excluded_models = []
|
43
|
+
config.seed_after_create = true
|
44
|
+
end
|
45
|
+
Apartment.seed_after_create.should be_true
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
context "databases" do
|
50
|
+
it "should return object if it doesnt respond_to call" do
|
51
|
+
database_names = ['users', 'companies']
|
52
|
+
|
53
|
+
Apartment.configure do |config|
|
54
|
+
config.excluded_models = []
|
55
|
+
config.database_names = database_names
|
56
|
+
end
|
57
|
+
Apartment.database_names.should == database_names
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should invoke the proc if appropriate" do
|
61
|
+
database_names = lambda{ ['users', 'users'] }
|
62
|
+
database_names.should_receive(:call)
|
63
|
+
|
64
|
+
Apartment.configure do |config|
|
65
|
+
config.excluded_models = []
|
66
|
+
config.database_names = database_names
|
67
|
+
end
|
68
|
+
Apartment.database_names
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should return the invoked proc if appropriate" do
|
72
|
+
dbs = lambda{ Company.scoped }
|
73
|
+
|
74
|
+
Apartment.configure do |config|
|
75
|
+
config.excluded_models = []
|
76
|
+
config.database_names = dbs
|
77
|
+
end
|
78
|
+
|
79
|
+
Apartment.database_names.should == Company.scoped
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|