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