thoughtbot--shoulda-matchers/spec/support/unit/helpers/model_builder.rb

127 lines
3.1 KiB
Ruby
Raw Normal View History

require_relative 'class_builder'
module UnitTests
module ModelBuilder
include ClassBuilder
def self.configure_example_group(example_group)
example_group.include(self)
example_group.after do
clear_column_caches
drop_created_tables
end
end
def create_table(table_name, options = {}, &block)
connection = ActiveRecord::Base.connection
begin
connection.execute("DROP TABLE IF EXISTS #{table_name}")
connection.create_table(table_name, options, &block)
created_tables << table_name
connection
rescue Exception => e
connection.execute("DROP TABLE IF EXISTS #{table_name}")
raise e
end
end
def define_model_class(class_name, &block)
define_class(class_name, ActiveRecord::Base, &block)
end
def define_active_model_class(class_name, options = {}, &block)
define_class(class_name) do
include ActiveModel::Validations
options[:accessors].each do |column|
attr_accessor column.to_sym
end
if block_given?
class_eval(&block)
end
end
end
def define_model(name, columns = {}, &block)
class_name = name.to_s.pluralize.classify
table_name = class_name.tableize.gsub('/', '_')
table_block = lambda do |table|
2014-10-22 04:38:56 +00:00
columns.each do |column_name, specification|
if specification.is_a?(Hash)
column_type = specification[:type]
column_options = specification.fetch(:options, {})
else
column_type = specification
column_options = {}
end
begin
table.__send__(column_type, column_name, column_options)
rescue NoMethodError
raise NoMethodError, "#{Tests::Database.instance.adapter_class} does not support :#{column_type} columns"
end
end
2012-03-09 16:53:24 +00:00
end
if columns.key?(:id) && columns[:id] == false
columns.delete(:id)
create_table(table_name, id: false, &table_block)
else
create_table(table_name, &table_block)
end
model = define_model_class(class_name).tap do |m|
if block
Rewrite tests for validate_uniqueness_of * The main problem I had with the old tests is that information that the reader didn't need to care about was not properly abstracted away. For instance, a helper method used by almost all tests will always create a model called Example, and will always use an attribute called "attr" (on which the validation is present). However, in some tests the class or attribute is referred to directly. The reader shouldn't have to care about either of these things, since they are constant -- the tests should be readable enough so that this information is not necessary to understand the case being tested against. * Speaking of this helper method, some of the tests used it and some didn't. Some defined their own helper methods to represent a particular case (`case_sensitive: true`, `allow_nil`, etc.). This is now fixed so that all but two tests use the same helper method to define a model. This model is completely customizable -- one can specify the type of the attribute being validated, the names and types of scoped attributes, etc. * The tests around scoped attributes and different types are all basically the same, so they are now compressed into a shared context. * Related to this, we no longer have to worry about setting a proper value for a scope attribute. One had to know which type that attribute had and come up with a reasonable default for that type. Now there is a helper method that worries about this automatically. * Finally, we remove tests around case_insensitive against an integer attribute (these don't make any sense, and don't work).
2015-02-08 19:10:23 +00:00
if block.arity == 0
m.class_eval(&block)
Rewrite tests for validate_uniqueness_of * The main problem I had with the old tests is that information that the reader didn't need to care about was not properly abstracted away. For instance, a helper method used by almost all tests will always create a model called Example, and will always use an attribute called "attr" (on which the validation is present). However, in some tests the class or attribute is referred to directly. The reader shouldn't have to care about either of these things, since they are constant -- the tests should be readable enough so that this information is not necessary to understand the case being tested against. * Speaking of this helper method, some of the tests used it and some didn't. Some defined their own helper methods to represent a particular case (`case_sensitive: true`, `allow_nil`, etc.). This is now fixed so that all but two tests use the same helper method to define a model. This model is completely customizable -- one can specify the type of the attribute being validated, the names and types of scoped attributes, etc. * The tests around scoped attributes and different types are all basically the same, so they are now compressed into a shared context. * Related to this, we no longer have to worry about setting a proper value for a scope attribute. One had to know which type that attribute had and come up with a reasonable default for that type. Now there is a helper method that worries about this automatically. * Finally, we remove tests around case_insensitive against an integer attribute (these don't make any sense, and don't work).
2015-02-08 19:10:23 +00:00
else
block.call(m)
Rewrite tests for validate_uniqueness_of * The main problem I had with the old tests is that information that the reader didn't need to care about was not properly abstracted away. For instance, a helper method used by almost all tests will always create a model called Example, and will always use an attribute called "attr" (on which the validation is present). However, in some tests the class or attribute is referred to directly. The reader shouldn't have to care about either of these things, since they are constant -- the tests should be readable enough so that this information is not necessary to understand the case being tested against. * Speaking of this helper method, some of the tests used it and some didn't. Some defined their own helper methods to represent a particular case (`case_sensitive: true`, `allow_nil`, etc.). This is now fixed so that all but two tests use the same helper method to define a model. This model is completely customizable -- one can specify the type of the attribute being validated, the names and types of scoped attributes, etc. * The tests around scoped attributes and different types are all basically the same, so they are now compressed into a shared context. * Related to this, we no longer have to worry about setting a proper value for a scope attribute. One had to know which type that attribute had and come up with a reasonable default for that type. Now there is a helper method that worries about this automatically. * Finally, we remove tests around case_insensitive against an integer attribute (these don't make any sense, and don't work).
2015-02-08 19:10:23 +00:00
end
end
m.table_name = table_name
end
defined_models << model
model
end
private
2012-12-20 05:04:27 +00:00
def clear_column_caches
# Rails 4.x
if ActiveRecord::Base.connection.respond_to?(:schema_cache)
ActiveRecord::Base.connection.schema_cache.clear!
2014-12-24 21:46:49 +00:00
# Rails 3.1 - 4.0
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
connection = ActiveRecord::Base.connection
created_tables.each do |table_name|
connection.execute("DROP TABLE IF EXISTS #{table_name}")
end
end
def created_tables
@_created_tables ||= []
end
def defined_models
@_defined_models ||= []
end
end
end