apartment 0.1.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/.gitignore +6 -0
  2. data/.rspec +2 -0
  3. data/.rvmrc +1 -0
  4. data/Gemfile +5 -12
  5. data/HISTORY.md +31 -0
  6. data/{README.markdown → README.md} +39 -21
  7. data/Rakefile +44 -47
  8. data/apartment.gemspec +19 -58
  9. data/lib/apartment.rb +57 -6
  10. data/lib/apartment/adapters/abstract_adapter.rb +106 -0
  11. data/lib/apartment/adapters/mysql_adapter.rb +11 -0
  12. data/lib/apartment/adapters/postgresql_adapter.rb +55 -0
  13. data/lib/apartment/database.rb +58 -74
  14. data/lib/apartment/elevators/subdomain.rb +27 -0
  15. data/lib/apartment/migrator.rb +23 -0
  16. data/lib/apartment/railtie.rb +1 -2
  17. data/lib/apartment/version.rb +3 -0
  18. data/lib/tasks/apartment.rake +36 -0
  19. data/spec/apartment_spec.rb +7 -0
  20. data/spec/config/database.yml +10 -0
  21. data/spec/dummy/Rakefile +7 -0
  22. data/spec/dummy/app/controllers/application_controller.rb +6 -0
  23. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  24. data/spec/dummy/app/models/company.rb +3 -0
  25. data/spec/dummy/app/models/user.rb +3 -0
  26. data/spec/dummy/app/views/application/index.html.erb +1 -0
  27. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  28. data/spec/dummy/config.ru +4 -0
  29. data/spec/dummy/config/application.rb +47 -0
  30. data/spec/dummy/config/boot.rb +10 -0
  31. data/spec/dummy/config/database.yml +8 -0
  32. data/spec/dummy/config/environment.rb +5 -0
  33. data/spec/dummy/config/environments/development.rb +26 -0
  34. data/spec/dummy/config/environments/production.rb +49 -0
  35. data/spec/dummy/config/environments/test.rb +35 -0
  36. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  37. data/spec/dummy/config/initializers/inflections.rb +10 -0
  38. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  39. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  40. data/spec/dummy/config/initializers/session_store.rb +8 -0
  41. data/spec/dummy/config/locales/en.yml +5 -0
  42. data/spec/dummy/config/routes.rb +3 -0
  43. data/spec/dummy/db/migrate/20110613152810_create_dummy_models.rb +20 -0
  44. data/spec/dummy/db/schema.rb +26 -0
  45. data/spec/dummy/db/seeds.rb +8 -0
  46. data/spec/dummy/db/test.sqlite3 +0 -0
  47. data/spec/dummy/public/404.html +26 -0
  48. data/spec/dummy/public/422.html +26 -0
  49. data/spec/dummy/public/500.html +26 -0
  50. data/{lib/apartment/associations/multi_tenant_association.rb → spec/dummy/public/favicon.ico} +0 -0
  51. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  52. data/spec/dummy/script/rails +6 -0
  53. data/spec/integration/adapters/postgresql_integration_spec.rb +73 -0
  54. data/spec/integration/apartment_rake_integration_spec.rb +62 -0
  55. data/spec/integration/database_integration_spec.rb +107 -0
  56. data/spec/integration/middleware/subdomain_elevator_spec.rb +63 -0
  57. data/spec/integration/setup_spec.rb +8 -0
  58. data/spec/spec_helper.rb +26 -0
  59. data/spec/support/apartment_helpers.rb +24 -0
  60. data/spec/support/capybara_sessions.rb +15 -0
  61. data/spec/support/config.rb +11 -0
  62. data/spec/support/migrate.rb +9 -0
  63. data/spec/tasks/apartment_rake_spec.rb +110 -0
  64. data/spec/unit/config_spec.rb +84 -0
  65. data/spec/unit/middleware/subdomain_elevator_spec.rb +20 -0
  66. data/spec/unit/migrator_spec.rb +87 -0
  67. metadata +112 -62
  68. data/.bundle/config +0 -2
  69. data/.project +0 -11
  70. data/Gemfile.lock +0 -22
  71. data/VERSION +0 -1
  72. data/lib/apartment/config.rb +0 -10
  73. data/lib/apartment/config/default_config.yml +0 -17
  74. data/lib/tasks/multi_tenant_migrate.rake +0 -14
  75. 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
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Setup" do
4
+
5
+ it "should be a valid app" do
6
+ ::Rails.application.should be_a(Dummy::Application)
7
+ end
8
+ end
@@ -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,15 @@
1
+ module RSpec
2
+ module Integration
3
+ module CapybaraSessions
4
+
5
+ def in_new_session(&block)
6
+ yield new_session
7
+ end
8
+
9
+ def new_session
10
+ Capybara::Session.new(Capybara.current_driver, Capybara.app)
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module Apartment
2
+
3
+ module Test
4
+
5
+ def self.config
6
+ @config ||= YAML.load_file('spec/config/database.yml')
7
+ end
8
+
9
+ end
10
+
11
+ end
@@ -0,0 +1,9 @@
1
+ module Apartment
2
+
3
+ module Test
4
+
5
+ def self.migrate
6
+ ActiveRecord::Migrator.migrate Rails.root + ActiveRecord::Migrator.migrations_path
7
+ end
8
+ end
9
+ 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