Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

added test suite

added rake tasks for migrating up/down and rolling back

added tests for db switching, creation etc
  • Loading branch information...
commit 301ea0994f779c8c5f1f4667b842d807cfa98a43 1 parent 7a01ee3
@bradrobertson authored
View
2  .rspec
@@ -0,0 +1,2 @@
+--colour
+--format documentation
View
18 Rakefile
@@ -1,2 +1,20 @@
require 'bundler'
+Bundler.setup
Bundler::GemHelper.install_tasks
+
+require "rspec"
+require "rspec/core/rake_task"
+
+RSpec::Core::RakeTask.new(:spec) do |spec|
+ spec.pattern = "spec/**/*_spec.rb"
+end
+
+RSpec::Core::RakeTask.new('spec:tasks') do |spec|
+ spec.pattern = "spec/tasks/**/*_spec.rb"
+end
+
+RSpec::Core::RakeTask.new('spec:unit') do |spec|
+ spec.pattern = "spec/unit/**/*_spec.rb"
+end
+
+task :default => :spec
View
67 lib/apartment/database.rb
@@ -5,56 +5,65 @@ module Apartment
module Database
extend self
- def switch(database)
+ def switch(database = nil)
# Just connect to default db and return
return ActiveRecord::Base.establish_connection(config) if database.nil?
connect_to_new(database)
- puts "Apartment::Config.excluded_models: #{Apartment::Config.excluded_models}"
-
Apartment::Config.excluded_models.each do |excluded_model|
- klass = excluded_model.constantize
-
- raise "Excluded class #{klass} could not be found." if klass.nil?
-
- puts "Excluding class #{excluded_model}"
-
+ klass = excluded_model.constantize
klass.establish_connection(config)
end
end
+ def reset
+ switch nil
+ end
+
def create(database)
-
# Postgres will (optionally) use 'schemas' instead of actual dbs, create a new schema while connected to main (global) db
ActiveRecord::Base.connection.execute("create schema #{database}") if use_schemas?
-
- connect_to_new(database)
-
- import_database_schema
- # Manually init schema migrations table (apparently there were issues with Postgres when this isn't done)
- ActiveRecord::ConnectionAdapters::SchemaStatements.initialize_schema_migrations_table
+ connect_and_reset(database) do
+ import_database_schema
- reset_connection
+ # Manually init schema migrations table (apparently there were issues with Postgres when this isn't done)
+ ActiveRecord::Base.connection.initialize_schema_migrations_table
+ end
end
+ # Migrate to latest
def migrate(database)
-
- connect_to_new(database)
-
- ActiveRecord::Migrator.migrate(File.join(Rails.root, ActiveRecord::Migrator.migrations_path))
-
- reset_connection
+ connect_and_reset(database){ ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_path) }
end
- def reset
- switch(nil)
+ # Migrate up to version
+ def migrate_up(database, version)
+ connect_and_reset(database){ ActiveRecord::Migrator.run(:up, ActiveRecord::Migrator.migrations_path, version) }
+ end
+
+ # Migrate down to version
+ def migrate_down(database, version)
+ connect_and_reset(database){ ActiveRecord::Migrator.run(:down, ActiveRecord::Migrator.migrations_path, version) }
+ end
+
+ def rollback(database, step = 1)
+ connect_and_reset(database){ ActiveRecord::Migrator.rollback(ActiveRecord::Migrator.migrations_path, step) }
end
+
+ def run(direction, path, version)
+ end
protected
+ def connect_and_reset(database)
+ connect_to_new(database)
+ yield if block_given?
+ reset
+ end
+
def import_database_schema
file = "#{Rails.root}/db/schema.rb"
if File.exists?(file)
@@ -73,8 +82,6 @@ def use_schemas?(conf = nil)
def connect_to_new(database)
switched_config = multi_tenantify(database)
- puts "connecting to db with config: #{switched_config.to_yaml}"
-
ActiveRecord::Base.establish_connection(switched_config)
end
@@ -94,12 +101,6 @@ def default_database_config
Rails.configuration.database_configuration[Rails.env]
end
- def reset_connection
- ActiveRecord::Base.establish_connection(config)
- end
-
- private
-
def config
@config ||= default_database_config
end
View
54 lib/tasks/apartment.rake
@@ -1,14 +1,54 @@
namespace :apartment do
- desc "Apply database changes to all multi-tenant databases"
- task :migrate => :environment do |t, args|
+
+ desc "Migrate all multi-tenant databases"
+ task :migrate => :environment do
puts "Apartment :: [Migrating to default environment]"
- ActiveRecord::Migrator.migrate(File.join(Rails.root, ActiveRecord::Migrator.migrations_path))
-
- Admin::Company.where("database is not null").select("distinct database").each do |company|
- puts "Apartment :: [Migrating to #{company.database}]"
-
+ ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_path)
+
+ companies do |company|
+ puts "Apartment :: [Migrating to #{company.database}]"
Apartment::Database.migrate company.database
end
+
end
+
+ desc "Rolls the schema back to the previous version (specify steps w/ STEP=n) across all multi-tenant dbs."
+ task :rollback => :environment do
+ step = ENV['STEP'] ? ENV['STEP'].to_i : 1
+ companies do |company|
+ Apartment::Database.rollback company.database, step
+ end
+ end
+
+ namespace :migrate do
+
+ desc 'Runs the "up" for a given migration VERSION across all multi-tenant dbs.'
+ task :up => :environment do
+ version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
+ raise 'VERSION is required' unless version
+
+ companies do |company|
+ Apartment::Database.migrate_up company.database, version
+ end
+ end
+
+ desc 'Runs the "down" for a given migration VERSION across all multi-tenant dbs.'
+ task :down => :environment do
+ version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
+ raise 'VERSION is required' unless version
+
+ companies do |company|
+ Apartment::Database.migrate_down company.database, version
+ end
+ end
+
+ end
+
+ # Migrate public database as well as all multi-tenanted dbs
+ def companies
+ Admin::Company.where("database is not null").select("distinct database").each do |company|
+ yield company if block_given?
+ end
+ end
end
View
16 spec/spec_helper.rb
@@ -0,0 +1,16 @@
+$LOAD_PATH.unshift(File.dirname(__FILE__))
+$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
+
+require 'apartment'
+require 'rspec'
+
+# stub out rails models
+require File.join(File.dirname(__FILE__), 'support', 'models')
+
+RSpec.configure do |config|
+
+ config.before do
+ Rails.stub(:root).and_return '.'
+ end
+
+end
View
13 spec/support/models.rb
@@ -0,0 +1,13 @@
+# Used to stub out Rails models from our app
+# Note that we shouldn't have to do this... A better setup would be to give a configuration
+# object that can take an array of db names to iterate over
+
+require 'active_record'
+
+module Admin
+ class Company < ActiveRecord::Base
+ end
+end
+
+class User < ActiveRecord::Base
+end
View
114 spec/tasks/apartment_rake_spec.rb
@@ -0,0 +1,114 @@
+require 'spec_helper'
+require 'rake'
+
+describe "apartment rake tasks" do
+
+ before do
+ @rake = Rake::Application.new
+ Rake.application = @rake
+ load 'tasks/apartment.rake'
+ Rake::Task.define_task(:environment) # stub out loading rails environment
+ end
+
+ after do
+ Rake.application = nil
+ end
+
+ let(:version){ '1234' }
+
+ context 'database migration' do
+
+ def company(db)
+ stub('Company', :database => db, :name => db)
+ end
+
+ let(:companies){ [company('company1'), company('company2'), company('company3')] }
+ let(:company_count){ companies.length }
+
+ before do
+ Admin::Company.stub(:where).and_return stub('Relation', :select => companies)
+ end
+
+ describe "apartment:migrate" do
+ before do
+ ActiveRecord::Migrator.stub(:migrate) # don't care about this
+ end
+
+ it "should migrate all multi-tenant dbs" do
+ Apartment::Database.should_receive(:migrate).exactly(company_count).times
+ @rake['apartment:migrate'].invoke
+ end
+ end
+
+ describe "apartment:migrate:up" do
+
+ context "without a version" do
+ before do
+ ENV['VERSION'] = nil
+ end
+
+ it "requires a version to migrate to" do
+ lambda{
+ @rake['apartment:migrate:up'].invoke
+ }.should raise_error("VERSION is required")
+ end
+ end
+
+ context "with version" do
+
+ before do
+ ENV['VERSION'] = version
+ end
+
+ it "migrates up to a specific version" do
+ Apartment::Database.should_receive(:migrate_up).with(anything, version.to_i).exactly(company_count).times
+ @rake['apartment:migrate:up'].invoke
+ end
+ end
+ end
+
+ describe "apartment:migrate:down" do
+
+ context "without a version" do
+ before do
+ ENV['VERSION'] = nil
+ end
+
+ it "requires a version to migrate to" do
+ lambda{
+ @rake['apartment:migrate:down'].invoke
+ }.should raise_error("VERSION is required")
+ end
+ end
+
+ context "with version" do
+
+ before do
+ ENV['VERSION'] = version
+ end
+
+ it "migrates up to a specific version" do
+ Apartment::Database.should_receive(:migrate_down).with(anything, version.to_i).exactly(company_count).times
+ @rake['apartment:migrate:down'].invoke
+ end
+ end
+ end
+
+ describe "apartment:rollback" do
+
+ let(:step){ '3' }
+
+ it "should rollback dbs" do
+ Apartment::Database.should_receive(:rollback).exactly(company_count).times
+ @rake['apartment:rollback'].invoke
+ end
+
+ it "should rollback dbs STEP amt" do
+ Apartment::Database.should_receive(:rollback).with(anything, step.to_i).exactly(company_count).times
+ ENV['STEP'] = step
+ @rake['apartment:rollback'].invoke
+ end
+ end
+ end
+
+end
View
162 spec/unit/database_spec.rb
@@ -0,0 +1,162 @@
+require 'spec_helper'
+
+describe Apartment::Database do
+
+ let(:config){ {'adapter' => 'postgresql', 'database' => 'some_database'} }
+ let(:schema_name){ 'some_database_schema' }
+ let(:multi_tenant_config){ config.merge 'schema_search_path' => schema_name }
+ let(:version){ 1234 }
+
+ before do
+ Apartment::Database.stub(:config).and_return config
+ Apartment::Database.stub(:import_database_schema)
+ ActiveRecord::Base.stub(:connection).and_return stub('Connection')
+ end
+
+ describe "#switch" do
+
+ it "should establish original connection with no database" do
+ ActiveRecord::Base.should_receive(:establish_connection).with config
+ Apartment::Database.switch
+ end
+
+ context "using postgres schemas" do
+
+ before do
+ Apartment::Database.stub(:use_schemas?).and_return true
+ end
+
+ context "with no model exclusions" do
+
+ before do
+ Apartment::Config.stub(:excluded_models).and_return []
+ end
+
+ it "should establish new connection to passed in database" do
+ ActiveRecord::Base.should_receive(:establish_connection).with multi_tenant_config
+ Apartment::Database.switch(schema_name)
+ end
+ end
+
+ context "with exclusions" do
+ before do
+ Apartment::Config.stub(:excluded_models).and_return ['Admin::Company', 'User']
+ end
+
+ it "should connect excluded model with original config" do
+ Admin::Company.should_receive(:establish_connection).with config
+ User.should_receive(:establish_connection).with config
+ Apartment::Database.switch(schema_name)
+ end
+
+ it "should raise an error for unkown class names" do
+ Apartment::Config.stub(:excluded_models).and_return ['Admin::Company', 'User', "Unknown::Class"]
+ lambda{
+ Apartment::Database.switch(schema_name)
+ }.should raise_error
+ end
+
+ end
+
+ end
+
+ end
+
+ describe "#create" do
+
+ before do
+ ActiveRecord::Base.connection.stub :initialize_schema_migrations_table # stub out migration table init
+ end
+
+ context "with postgres schemas" do
+
+ before do
+ Apartment::Database.stub(:use_schemas?).and_return true
+ end
+
+ it "should create the new schema" do
+ ActiveRecord::Base.connection.should_receive(:execute).with("create schema #{schema_name}")
+ Apartment::Database.create(schema_name)
+ end
+
+ # need more tests
+
+ end
+
+ context "without postgres schemas" do
+
+ before do
+ Apartment::Database.stub(:use_schemas?).and_return false
+ end
+
+ it "should not create schema" do
+ ActiveRecord::Base.connection.should_not_receive(:execute)
+ Apartment::Database.create(schema_name)
+ end
+ end
+ end
+
+ context "migrations" do
+ before do
+ ActiveRecord::Base.stub(:establish_connection)
+ end
+
+ describe "#migrate" do
+ it "should connect to new db, then reset when done" do
+ ActiveRecord::Migrator.stub(:migrate)
+ ActiveRecord::Base.should_receive(:establish_connection).with(multi_tenant_config).once
+ ActiveRecord::Base.should_receive(:establish_connection).with(config).once
+ Apartment::Database.migrate(schema_name)
+ end
+
+ it "should migrate db" do
+ ActiveRecord::Migrator.should_receive(:migrate)
+ Apartment::Database.migrate(schema_name)
+ end
+ end
+
+ describe "#migrate_up" do
+
+ it "should connect to new db, then reset when done" do
+ ActiveRecord::Migrator.stub(:run)
+ ActiveRecord::Base.should_receive(:establish_connection).with(multi_tenant_config).once
+ ActiveRecord::Base.should_receive(:establish_connection).with(config).once
+ Apartment::Database.migrate_up(schema_name, version)
+ end
+
+ it "should migrate to a version" do
+ ActiveRecord::Migrator.should_receive(:run).with(:up, anything, version)
+ Apartment::Database.migrate_up(schema_name, version)
+ end
+ end
+
+ describe "#migrate_down" do
+
+ it "should connect to new db, then reset when done" do
+ ActiveRecord::Migrator.stub(:run)
+ ActiveRecord::Base.should_receive(:establish_connection).with(multi_tenant_config).once
+ ActiveRecord::Base.should_receive(:establish_connection).with(config).once
+ Apartment::Database.migrate_down(schema_name, version)
+ end
+
+ it "should migrate to a version" do
+ ActiveRecord::Migrator.should_receive(:run).with(:down, anything, version)
+ Apartment::Database.migrate_down(schema_name, version)
+ end
+ end
+ end
+
+ describe "#rollback" do
+ before do
+ ActiveRecord::Base.stub :establish_connection
+ end
+
+ let(:steps){ 3 }
+
+ it "should rollback the db" do
+ ActiveRecord::Migrator.should_receive(:rollback).with(anything, steps)
+ Apartment::Database.rollback(schema_name, steps)
+ end
+ end
+
+end
Please sign in to comment.
Something went wrong with that request. Please try again.