Add support for Postgres

When running tests, you can now switch between running them against a
SQLite or PostgreSQL database. This is accomplished by modifying the
unit and acceptance tests so that when they generate and load the test
Rails application, database.yml is replaced with content that will
configure the database appropriately.
This commit is contained in:
Elliot Winkler 2015-02-08 14:38:55 -07:00
parent 942a600e07
commit 72f60fae94
30 changed files with 288 additions and 51 deletions

View File

@ -6,6 +6,10 @@ branches:
only:
- master
env:
- DATABASE_ADAPTER=sqlite3
- DATABASE_ADAPTER=postgresql
rvm:
- 2.0.0
- 2.1.4

View File

@ -4,6 +4,7 @@ shared_dependencies = proc do
gem 'rspec-rails', '>= 3.2.0', '< 4'
gem 'shoulda-context', '~> 1.2.0'
gem 'sqlite3', platform: :ruby
gem 'pg', platform: :ruby
gem 'activerecord-jdbc-adapter', platform: :jruby
gem 'activerecord-jdbcsqlite3-adapter', platform: :jruby
gem 'jdbc-sqlite3', platform: :jruby

View File

@ -3,6 +3,7 @@ require 'bundler/gem_tasks'
require 'rspec/core/rake_task'
require 'appraisal'
require_relative 'tasks/documentation'
require_relative 'spec/support/tests/database'
RSpec::Core::RakeTask.new('spec:unit') do |t|
t.ruby_opts = '-w -r ./spec/report_warnings'

View File

@ -15,6 +15,7 @@ gem "watchr"
gem "rspec-rails", ">= 3.2.0", "< 4"
gem "shoulda-context", "~> 1.2.0"
gem "sqlite3", :platform => :ruby
gem "pg", :platform => :ruby
gem "activerecord-jdbc-adapter", :platform => :jruby
gem "activerecord-jdbcsqlite3-adapter", :platform => :jruby
gem "jdbc-sqlite3", :platform => :jruby

View File

@ -70,6 +70,7 @@ GEM
minitest (>= 2.12, < 5.0)
powerbar
multi_json (1.10.1)
pg (0.18.1)
polyglot (0.3.5)
posix-spawn (0.3.9)
powerbar (1.0.11)
@ -181,6 +182,7 @@ DEPENDENCIES
jquery-rails
jruby-openssl
minitest-reporters
pg
protected_attributes
pry-nav
pygments.rb

View File

@ -15,6 +15,7 @@ gem "watchr"
gem "rspec-rails", ">= 3.2.0", "< 4"
gem "shoulda-context", "~> 1.2.0"
gem "sqlite3", :platform => :ruby
gem "pg", :platform => :ruby
gem "activerecord-jdbc-adapter", :platform => :jruby
gem "activerecord-jdbcsqlite3-adapter", :platform => :jruby
gem "jdbc-sqlite3", :platform => :jruby

View File

@ -70,6 +70,7 @@ GEM
minitest (>= 2.12, < 5.0)
powerbar
multi_json (1.10.1)
pg (0.18.1)
polyglot (0.3.5)
posix-spawn (0.3.9)
powerbar (1.0.11)
@ -181,6 +182,7 @@ DEPENDENCIES
jquery-rails
jruby-openssl
minitest-reporters
pg
protected_attributes
pry-nav
pygments.rb

View File

@ -15,6 +15,7 @@ gem "watchr"
gem "rspec-rails", ">= 3.2.0", "< 4"
gem "shoulda-context", "~> 1.2.0"
gem "sqlite3", :platform => :ruby
gem "pg", :platform => :ruby
gem "activerecord-jdbc-adapter", :platform => :jruby
gem "activerecord-jdbcsqlite3-adapter", :platform => :jruby
gem "jdbc-sqlite3", :platform => :jruby

View File

@ -70,6 +70,7 @@ GEM
minitest (>= 5.0)
ruby-progressbar
multi_json (1.10.1)
pg (0.18.1)
posix-spawn (0.3.9)
protected_attributes (1.0.8)
activemodel (>= 4.0.1, < 5.0)
@ -178,6 +179,7 @@ DEPENDENCIES
jquery-rails
jruby-openssl
minitest-reporters
pg
protected_attributes (~> 1.0.6)
pry-nav
pygments.rb

View File

@ -15,6 +15,7 @@ gem "watchr"
gem "rspec-rails", ">= 3.2.0", "< 4"
gem "shoulda-context", "~> 1.2.0"
gem "sqlite3", :platform => :ruby
gem "pg", :platform => :ruby
gem "activerecord-jdbc-adapter", :platform => :jruby
gem "activerecord-jdbcsqlite3-adapter", :platform => :jruby
gem "jdbc-sqlite3", :platform => :jruby

View File

@ -87,6 +87,7 @@ GEM
multi_json (1.10.1)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
pg (0.18.1)
posix-spawn (0.3.9)
protected_attributes (1.0.8)
activemodel (>= 4.0.1, < 5.0)
@ -205,6 +206,7 @@ DEPENDENCIES
jquery-rails
jruby-openssl
minitest-reporters
pg
protected_attributes (~> 1.0.6)
pry-nav
pygments.rb

View File

@ -14,7 +14,7 @@ describe 'shoulda-matchers integrates with Rails' do
end
FILE
run_rake_tasks!('db:migrate', 'db:test:prepare')
run_rake_tasks! *%w(db:drop db:create db:migrate)
write_file 'app/models/user.rb', <<-FILE
class User < ActiveRecord::Base

View File

@ -5,6 +5,8 @@ Tests::CurrentBundle.instance.assert_appraisal!
#---
require 'rspec/core'
require 'pry'
require 'pry-nav'
Dir[ File.join(File.expand_path('../support/acceptance/**/*.rb', __FILE__)) ].sort.each do |file|
require file

View File

@ -1,5 +1,6 @@
require_relative '../../tests/filesystem'
require_relative '../../tests/bundle'
require_relative '../../tests/database'
require_relative '../../tests/filesystem'
module AcceptanceTests
module BaseHelpers
@ -10,5 +11,9 @@ module AcceptanceTests
def bundle
@_bundle ||= Tests::Bundle.new
end
def database
@_database ||= Tests::Database.instance
end
end
end

View File

@ -41,11 +41,15 @@ module AcceptanceTests
end
def run_rake_tasks(*tasks)
run_command_within_bundle('rake', *tasks)
options = tasks.last.is_a?(Hash) ? tasks.pop : {}
args = ['rake', *tasks, '--trace'] + [options]
run_command_within_bundle(*args)
end
def run_rake_tasks!(*tasks)
run_command_within_bundle!('rake', *tasks)
options = tasks.last.is_a?(Hash) ? tasks.pop : {}
args = ['rake', *tasks, '--trace'] + [options]
run_command_within_bundle!(*args)
end
end
end

View File

@ -2,6 +2,8 @@ require_relative 'file_helpers'
require_relative 'gem_helpers'
require_relative 'minitest_helpers'
require 'yaml'
module AcceptanceTests
module StepHelpers
include FileHelpers
@ -72,6 +74,11 @@ module AcceptanceTests
bundle.remove_gem 'debugger'
bundle.remove_gem 'byebug'
bundle.remove_gem 'web-console'
bundle.add_gem 'pg'
end
fs.open('config/database.yml', 'w') do |file|
YAML.dump(database.config.to_hash, file)
end
end

View File

@ -134,7 +134,7 @@ Output:
end
def command
([command_prefix] + args).flat_map do |word|
([command_prefix] + args).flatten.flat_map do |word|
Shellwords.split(word)
end
end

View File

@ -0,0 +1,28 @@
require_relative 'database_configuration'
module Tests
class Database
NAME = 'shoulda-matchers-test'
ADAPTER_NAME = ENV.fetch('DATABASE_ADAPTER', 'sqlite3').to_sym
include Singleton
attr_reader :config
def initialize
@config = Tests::DatabaseConfiguration.for(NAME, ADAPTER_NAME)
end
def name
config.database
end
def adapter_name
config.adapter
end
def adapter_class
config.adapter_class
end
end
end

View File

@ -0,0 +1,25 @@
module Tests
module DatabaseAdapters
class PostgreSQL
def self.name
:postgresql
end
attr_reader :database
def initialize(database)
@database = database
end
def adapter
self.class.name
end
def require_dependencies
require 'pg'
end
end
DatabaseConfigurationRegistry.instance.register(PostgreSQL)
end
end

View File

@ -0,0 +1,26 @@
module Tests
module DatabaseAdapters
class SQLite3
def self.name
:sqlite3
end
def initialize(_database)
end
def adapter
self.class.name
end
def database
'db/db.sqlite3'
end
def require_dependencies
require 'sqlite3'
end
end
DatabaseConfigurationRegistry.instance.register(SQLite3)
end
end

View File

@ -0,0 +1,26 @@
require_relative 'database_configuration_registry'
require 'delegate'
module Tests
class DatabaseConfiguration < SimpleDelegator
ENVIRONMENTS = %w(development test production)
def self.for(database_name, adapter_name)
config_class = DatabaseConfigurationRegistry.instance.get(adapter_name)
config = config_class.new(database_name)
new(config)
end
def to_hash
ENVIRONMENTS.each_with_object({}) do |env, config_as_hash|
config_as_hash[env] = inner_config_as_hash
end
end
private
def inner_config_as_hash
{ 'adapter' => adapter.to_s, 'database' => database.to_s }
end
end
end

View File

@ -0,0 +1,28 @@
require 'singleton'
module Tests
class DatabaseConfigurationRegistry
include Singleton
def initialize
@registry = {}
end
def register(config_class)
registry[config_class.name] = config_class
end
def get(name)
registry.fetch(name) do
raise KeyError, "No such adapter registered: #{name}"
end
end
protected
attr_reader :registry
end
end
require_relative 'database_adapters/postgresql'
require_relative 'database_adapters/sqlite3'

View File

@ -0,0 +1,26 @@
module UnitTests
module ColumnTypeHelpers
def self.configure_example_group(example_group)
example_group.include(self)
end
def column_type_class_namespace
if database_adapter == :postgresql
ActiveRecord::ConnectionAdapters::PostgreSQL
else
ActiveRecord::Type
end
end
def column_type_class_for(type)
namespace =
if type == :integer && database_adapter == :postgresql
column_type_class_namespace::OID
else
column_type_class_namespace
end
namespace.const_get(type.to_s.camelize)
end
end
end

View File

@ -0,0 +1,16 @@
module UnitTests
module DatabaseHelpers
def self.configure_example_group(example_group)
example_group.include(self)
example_group.extend(self)
end
def database_adapter
Tests::Database.instance.adapter_name
end
def database_supports_uuid_columns?
database_adapter == :postgresql
end
end
end

View File

@ -66,17 +66,21 @@ module UnitTests
create_table(table_name, &table_block)
end
define_model_class(class_name).tap do |model|
model = define_model_class(class_name).tap do |m|
if block
if block.arity == 0
model.class_eval(&block)
m.class_eval(&block)
else
block.call(model)
block.call(m)
end
end
model.table_name = table_name
m.table_name = table_name
end
defined_models << model
model
end
private
@ -89,6 +93,10 @@ module UnitTests
elsif ActiveRecord::Base.connection_pool.respond_to?(:clear_cache!)
ActiveRecord::Base.connection_pool.clear_cache!
end
defined_models.each do |model|
model.reset_column_information
end
end
def drop_created_tables
@ -102,5 +110,9 @@ module UnitTests
def created_tables
@_created_tables ||= []
end
def defined_models
@_defined_models ||= []
end
end
end

View File

@ -1,17 +1,22 @@
require_relative '../tests/command_runner'
require_relative '../tests/filesystem'
require_relative '../tests/bundle'
require_relative '../tests/command_runner'
require_relative '../tests/database'
require_relative '../tests/filesystem'
require 'yaml'
module UnitTests
class RailsApplication
def initialize
@fs = Tests::Filesystem.new
@bundle = Tests::Bundle.new
@database = Tests::Database.instance
end
def create
fs.clean
generate
fs.within_project do
install_gems
remove_unwanted_gems
@ -54,7 +59,7 @@ module UnitTests
protected
attr_reader :fs, :shell, :bundle
attr_reader :fs, :shell, :bundle, :database
private
@ -69,6 +74,7 @@ module UnitTests
def generate
rails_new
fix_available_locales_warning
write_database_configuration
end
def rails_new
@ -87,13 +93,18 @@ end
end
end
def write_database_configuration
YAML.dump(database.config.to_hash, fs.open('config/database.yml', 'w'))
end
def load_environment
require environment_file_path
end
def run_migrations
ActiveRecord::Migration.verbose = false
ActiveRecord::Migrator.migrate(migrations_directory)
fs.within_project do
run_command! 'bundle exec rake db:drop db:create db:migrate'
end
end
def install_gems

View File

@ -237,19 +237,22 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
context 'when the value is outside of the range of the column' do
context 'not qualified with strict' do
it 'rejects, failing with the correct message' do
attribute_options = { type: :integer, options: { limit: 2 } }
type = :integer
attribute_options = { type: type, options: { limit: 2 } }
record = define_model(:example, attr: attribute_options).new
assertion = -> { expect(record).to allow_value(100000).for(:attr) }
column_type_class = column_type_class_for(type)
message = <<-MESSAGE.strip_heredoc.strip
Did not expect errors when attr is set to 100000,
got RangeError: "100000 is out of range for ActiveRecord::Type::Integer with limit 2"
got RangeError: "100000 is out of range for #{column_type_class} with limit 2"
MESSAGE
expect(&assertion).to fail_with_message(message)
end
context 'qualified with a message' do
it 'ignores any specified message, failing with the correct message' do
attribute_options = { type: :integer, options: { limit: 2 } }
type = :integer
attribute_options = { type: type, options: { limit: 2 } }
record = define_model(:example, attr: attribute_options).new
assertion = -> do
expect(record).
@ -257,9 +260,10 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
for(:attr).
with_message('some message')
end
column_type_class = column_type_class_for(type)
message = <<-MESSAGE.strip_heredoc.strip
Did not expect errors to include "some message" when attr is set to 100000,
got RangeError: "100000 is out of range for ActiveRecord::Type::Integer with limit 2"
got RangeError: "100000 is out of range for #{column_type_class} with limit 2"
MESSAGE
expect(&assertion).to fail_with_message(message)
end
@ -269,7 +273,8 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
if active_model_supports_strict?
context 'qualified with strict' do
it 'rejects, failing with the correct message' do
attribute_options = { type: :integer, options: { limit: 2 } }
type = :integer
attribute_options = { type: type, options: { limit: 2 } }
record = define_model(:example, attr: attribute_options).new
assertion = -> do
expect(record).
@ -277,16 +282,18 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
for(:attr).
strict
end
column_type_class = column_type_class_for(type)
message = <<-MESSAGE.strip_heredoc.strip
Did not expect an exception to have been raised when attr is set to 100000,
got RangeError: "100000 is out of range for ActiveRecord::Type::Integer with limit 2"
got RangeError: "100000 is out of range for #{column_type_class} with limit 2"
MESSAGE
expect(&assertion).to fail_with_message(message)
end
context 'qualified with a message' do
it 'ignores any specified message' do
attribute_options = { type: :integer, options: { limit: 2 } }
type = :integer
attribute_options = { type: type, options: { limit: 2 } }
record = define_model(:example, attr: attribute_options).new
assertion = -> do
expect(record).
@ -295,9 +302,10 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
with_message('some message').
strict
end
column_type_class = column_type_class_for(type)
message = <<-MESSAGE.strip_heredoc.strip
Did not expect exception to include "some message" when attr is set to 100000,
got RangeError: "100000 is out of range for ActiveRecord::Type::Integer with limit 2"
got RangeError: "100000 is out of range for #{column_type_class} with limit 2"
MESSAGE
expect(&assertion).to fail_with_message(message)
end
@ -306,4 +314,5 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
end
end
end
end

View File

@ -83,19 +83,22 @@ describe Shoulda::Matchers::ActiveModel::DisallowValueMatcher, type: :model do
context 'when the value is outside of the range of the column' do
context 'not qualified with strict' do
it 'accepts, failing with the correct message' do
attribute_options = { type: :integer, options: { limit: 2 } }
type = :integer
attribute_options = { type: type, options: { limit: 2 } }
record = define_model(:example, attr: attribute_options).new
assertion = -> { expect(record).not_to disallow_value(100000).for(:attr) }
column_type_class = column_type_class_for(type)
message = <<-MESSAGE.strip_heredoc.strip
Did not expect errors when attr is set to 100000,
got RangeError: "100000 is out of range for ActiveRecord::Type::Integer with limit 2"
got RangeError: "100000 is out of range for #{column_type_class} with limit 2"
MESSAGE
expect(&assertion).to fail_with_message(message)
end
context 'qualified with a message' do
it 'ignores any specified message, failing with the correct message' do
attribute_options = { type: :integer, options: { limit: 2 } }
type = :integer
attribute_options = { type: type, options: { limit: 2 } }
record = define_model(:example, attr: attribute_options).new
assertion = -> do
expect(record).
@ -103,9 +106,10 @@ describe Shoulda::Matchers::ActiveModel::DisallowValueMatcher, type: :model do
for(:attr).
with_message('some message')
end
column_type_class = column_type_class_for(type)
message = <<-MESSAGE.strip_heredoc.strip
Did not expect errors to include "some message" when attr is set to 100000,
got RangeError: "100000 is out of range for ActiveRecord::Type::Integer with limit 2"
got RangeError: "100000 is out of range for #{column_type_class} with limit 2"
MESSAGE
expect(&assertion).to fail_with_message(message)
end
@ -115,7 +119,8 @@ describe Shoulda::Matchers::ActiveModel::DisallowValueMatcher, type: :model do
if active_model_supports_strict?
context 'qualified with strict' do
it 'accepts, failing with the correct message' do
attribute_options = { type: :integer, options: { limit: 2 } }
type = :integer
attribute_options = { type: type, options: { limit: 2 } }
record = define_model(:example, attr: attribute_options).new
assertion = -> do
expect(record).
@ -123,16 +128,18 @@ describe Shoulda::Matchers::ActiveModel::DisallowValueMatcher, type: :model do
for(:attr).
strict
end
column_type_class = column_type_class_for(type)
message = <<-MESSAGE.strip_heredoc.strip
Did not expect an exception to have been raised when attr is set to 100000,
got RangeError: "100000 is out of range for ActiveRecord::Type::Integer with limit 2"
got RangeError: "100000 is out of range for #{column_type_class} with limit 2"
MESSAGE
expect(&assertion).to fail_with_message(message)
end
context 'qualified with a message' do
it 'ignores any specified message' do
attribute_options = { type: :integer, options: { limit: 2 } }
type = :integer
attribute_options = { type: type, options: { limit: 2 } }
record = define_model(:example, attr: attribute_options).new
assertion = -> do
expect(record).
@ -141,9 +148,10 @@ describe Shoulda::Matchers::ActiveModel::DisallowValueMatcher, type: :model do
with_message('some message').
strict
end
column_type_class = column_type_class_for(type)
message = <<-MESSAGE.strip_heredoc.strip
Did not expect exception to include "some message" when attr is set to 100000,
got RangeError: "100000 is out of range for ActiveRecord::Type::Integer with limit 2"
got RangeError: "100000 is out of range for #{column_type_class} with limit 2"
MESSAGE
expect(&assertion).to fail_with_message(message)
end

View File

@ -16,15 +16,6 @@ describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :mo
)
expect(record).to validate_uniqueness.scoped_to(:scope1, :scope2)
end
it 'still accepts if the value of the scope is nil' do
record = build_record_validating_uniqueness(
scopes: [
build_attribute(name: :scope, value: nil)
]
)
expect(record).to validate_uniqueness.scoped_to(:scope)
end
end
context 'when the subject is an existing record' do
@ -38,15 +29,6 @@ describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :mo
expect(record).to validate_uniqueness.scoped_to(:scope1, :scope2)
end
it 'still accepts if the value of the scope is nil' do
record = create_record_validating_uniqueness(
scopes: [
build_attribute(name: :scope, value: nil)
]
)
expect(record).to validate_uniqueness.scoped_to(:scope)
end
end
end
@ -330,9 +312,11 @@ describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :mo
value_type: :time
end
context 'when one of the scoped attributes is a UUID column' do
include_context 'it supports scoped attributes of a certain type',
column_type: :uuid
if database_supports_uuid_columns?
context 'when one of the scoped attributes is a UUID column' do
include_context 'it supports scoped attributes of a certain type',
column_type: :uuid
end
end
end

View File

@ -51,6 +51,8 @@ RSpec.configure do |config|
UnitTests::RailsVersions.configure_example_group(config)
UnitTests::ActiveRecordVersions.configure_example_group(config)
UnitTests::ActiveModelVersions.configure_example_group(config)
UnitTests::DatabaseHelpers.configure_example_group(config)
UnitTests::ColumnTypeHelpers.configure_example_group(config)
config.include UnitTests::Matchers
end