2014-10-14 05:16:12 +00:00
|
|
|
require_relative 'class_builder'
|
|
|
|
|
2014-10-27 00:51:42 +00:00
|
|
|
module UnitTests
|
|
|
|
module ModelBuilder
|
|
|
|
include ClassBuilder
|
2010-12-13 22:28:59 +00:00
|
|
|
|
2014-10-27 00:51:42 +00:00
|
|
|
def self.configure_example_group(example_group)
|
|
|
|
example_group.include(self)
|
2014-10-14 05:16:12 +00:00
|
|
|
|
2014-10-27 00:51:42 +00:00
|
|
|
example_group.after do
|
2014-11-06 17:28:13 +00:00
|
|
|
clear_column_caches
|
2014-10-27 00:51:42 +00:00
|
|
|
drop_created_tables
|
|
|
|
end
|
2010-12-13 22:28:59 +00:00
|
|
|
end
|
|
|
|
|
2014-10-27 00:51:42 +00:00
|
|
|
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
|
2014-10-14 05:16:12 +00:00
|
|
|
|
2014-10-27 00:51:42 +00:00
|
|
|
def define_model_class(class_name, &block)
|
|
|
|
define_class(class_name, ActiveRecord::Base, &block)
|
2008-12-15 18:13:56 +00:00
|
|
|
end
|
|
|
|
|
2014-10-27 00:51:42 +00:00
|
|
|
def define_active_model_class(class_name, options = {}, &block)
|
allow_value: Raise error if attr sets value differently
`allow_value` will now raise a CouldNotSetAttribute error if the
attribute in question cannot be changed from a non-nil value to a nil
value, or vice versa. In other words, these are the exact cases in which
the error will occur:
* If you're testing whether the attribute allows `nil`, but the
attribute detects and ignores nil. (For instance, you have a model
that `has_secure_password`. This will add a #password= method to your
model that is defined in a such a way that you cannot clear the
password by setting it to nil -- nothing happens.)
* If you're testing whether the attribute allows a non-nil value, but
the attribute fails to set that value. (For instance, you have an
ActiveRecord model. If ActiveRecord cannot typecast the value in the
context of the column, then it will do nothing, and the attribute will be
effectively set to nil.)
What's the reasoning behind this change? Simply put, if you are assuming
that the attribute is changing but in fact it is not, then the test
you're writing isn't the test that actually gets run. We feel that this
is dishonest and produces an invalid test.
2013-11-22 20:46:59 +00:00
|
|
|
accessors = options.fetch(:accessors, [])
|
|
|
|
|
2015-12-22 07:01:21 +00:00
|
|
|
attributes_module = Module.new do
|
|
|
|
accessors.each do |column|
|
|
|
|
attr_accessor column.to_sym
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-10-27 00:51:42 +00:00
|
|
|
define_class(class_name) do
|
|
|
|
include ActiveModel::Validations
|
2015-12-22 07:01:21 +00:00
|
|
|
include attributes_module
|
2009-01-31 23:42:48 +00:00
|
|
|
|
allow_value: Raise error if attr sets value differently
`allow_value` will now raise a CouldNotSetAttribute error if the
attribute in question cannot be changed from a non-nil value to a nil
value, or vice versa. In other words, these are the exact cases in which
the error will occur:
* If you're testing whether the attribute allows `nil`, but the
attribute detects and ignores nil. (For instance, you have a model
that `has_secure_password`. This will add a #password= method to your
model that is defined in a such a way that you cannot clear the
password by setting it to nil -- nothing happens.)
* If you're testing whether the attribute allows a non-nil value, but
the attribute fails to set that value. (For instance, you have an
ActiveRecord model. If ActiveRecord cannot typecast the value in the
context of the column, then it will do nothing, and the attribute will be
effectively set to nil.)
What's the reasoning behind this change? Simply put, if you are assuming
that the attribute is changing but in fact it is not, then the test
you're writing isn't the test that actually gets run. We feel that this
is dishonest and produces an invalid test.
2013-11-22 20:46:59 +00:00
|
|
|
def initialize(attributes = {})
|
|
|
|
attributes.each do |name, value|
|
|
|
|
__send__("#{name}=", value)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-10-27 00:51:42 +00:00
|
|
|
if block_given?
|
|
|
|
class_eval(&block)
|
|
|
|
end
|
2011-01-24 16:19:34 +00:00
|
|
|
end
|
2014-10-27 00:51:42 +00:00
|
|
|
end
|
2011-01-24 16:19:34 +00:00
|
|
|
|
2014-10-27 00:51:42 +00:00
|
|
|
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|
|
2014-10-27 00:51:42 +00:00
|
|
|
if specification.is_a?(Hash)
|
2015-02-08 22:23:10 +00:00
|
|
|
column_type = specification[:type]
|
|
|
|
column_options = specification.fetch(:options, {})
|
2014-10-27 00:51:42 +00:00
|
|
|
else
|
2015-02-08 22:23:10 +00:00
|
|
|
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"
|
2014-10-27 00:51:42 +00:00
|
|
|
end
|
|
|
|
end
|
2012-03-09 16:53:24 +00:00
|
|
|
end
|
2011-01-24 16:19:34 +00:00
|
|
|
|
2014-10-27 00:51:42 +00:00
|
|
|
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
|
2014-10-08 05:19:07 +00:00
|
|
|
|
2015-02-08 21:38:55 +00:00
|
|
|
model = define_model_class(class_name).tap do |m|
|
2014-10-27 00:51:42 +00:00
|
|
|
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
|
2015-02-08 21:38:55 +00:00
|
|
|
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
|
2015-02-08 21:38:55 +00:00
|
|
|
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
|
2013-11-11 23:38:07 +00:00
|
|
|
end
|
2014-10-27 00:51:42 +00:00
|
|
|
|
2015-02-08 21:38:55 +00:00
|
|
|
m.table_name = table_name
|
2009-01-24 21:23:11 +00:00
|
|
|
end
|
2015-02-08 21:38:55 +00:00
|
|
|
|
|
|
|
defined_models << model
|
|
|
|
|
|
|
|
model
|
2009-01-24 21:23:11 +00:00
|
|
|
end
|
|
|
|
|
2014-10-27 00:51:42 +00:00
|
|
|
private
|
2012-12-20 05:04:27 +00:00
|
|
|
|
2014-11-06 17:28:13 +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!
|
2014-11-06 17:28:13 +00:00
|
|
|
end
|
2015-02-08 21:38:55 +00:00
|
|
|
|
|
|
|
defined_models.each do |model|
|
|
|
|
model.reset_column_information
|
|
|
|
end
|
2014-11-06 17:28:13 +00:00
|
|
|
end
|
|
|
|
|
2014-10-27 00:51:42 +00:00
|
|
|
def drop_created_tables
|
|
|
|
connection = ActiveRecord::Base.connection
|
2014-10-08 05:19:07 +00:00
|
|
|
|
2014-10-27 00:51:42 +00:00
|
|
|
created_tables.each do |table_name|
|
|
|
|
connection.execute("DROP TABLE IF EXISTS #{table_name}")
|
|
|
|
end
|
2014-10-08 05:19:07 +00:00
|
|
|
end
|
2014-10-14 05:16:12 +00:00
|
|
|
|
2014-10-27 00:51:42 +00:00
|
|
|
def created_tables
|
|
|
|
@_created_tables ||= []
|
|
|
|
end
|
2015-02-08 21:38:55 +00:00
|
|
|
|
|
|
|
def defined_models
|
|
|
|
@_defined_models ||= []
|
|
|
|
end
|
2014-10-14 05:16:12 +00:00
|
|
|
end
|
2010-12-13 22:28:59 +00:00
|
|
|
end
|