mirror of
https://github.com/thoughtbot/factory_bot.git
synced 2022-11-09 11:43:51 -05:00
Added support for dependent attributes
This commit is contained in:
parent
7b3936b611
commit
05766cc8e9
5 changed files with 128 additions and 20 deletions
30
README
30
README
|
@ -34,16 +34,20 @@ a block instead of a parameter:
|
|||
u.activation_code { User.generate_activation_code }
|
||||
end
|
||||
|
||||
== Using factories
|
||||
== Dependent Attributes
|
||||
|
||||
# Build and save a User instance
|
||||
Factory(:user)
|
||||
Some attributes may need to be generated based on the values of other
|
||||
attributes. This can be done by calling the attribute name on
|
||||
Factory::AttributeProxy, which is yielded to lazy attribute blocks:
|
||||
|
||||
# Build a User instance and override the first_name property
|
||||
Factory.build(:user, :first_name => 'Joe')
|
||||
Factory.define :user do |u|
|
||||
u.first_name 'Joe'
|
||||
u.last_name 'Blow'
|
||||
u.email {|a| "#{a.first_name}.#{a.last_name}@example.com".downcase }
|
||||
end
|
||||
|
||||
# Return an attributes Hash that can be used to build a User instance
|
||||
attrs = Factory.attributes_for(:user)
|
||||
Factory(:user, :last_name => 'Doe').email
|
||||
# => "joe.doe@example.com"
|
||||
|
||||
== Associations
|
||||
|
||||
|
@ -66,3 +70,15 @@ When using the association method, the same build strategy (build, create, or at
|
|||
Factory.build(:post)
|
||||
post.new_record? # => true
|
||||
post.author.new_record # => true
|
||||
|
||||
== Using factories
|
||||
|
||||
# Build and save a User instance
|
||||
Factory(:user)
|
||||
|
||||
# Build a User instance and override the first_name property
|
||||
Factory.build(:user, :first_name => 'Joe')
|
||||
|
||||
# Return an attributes Hash that can be used to build a User instance
|
||||
attrs = Factory.attributes_for(:user)
|
||||
|
||||
|
|
|
@ -2,12 +2,13 @@ class Factory
|
|||
|
||||
class AttributeProxy
|
||||
|
||||
attr_accessor :factory, :attribute_name, :strategy #:nodoc:
|
||||
attr_accessor :factory, :attribute_name, :strategy, :current_values #:nodoc:
|
||||
|
||||
def initialize (factory, attr, strategy) #:nodoc:
|
||||
def initialize (factory, attr, strategy, values) #:nodoc:
|
||||
@factory = factory
|
||||
@attribute_name = attr
|
||||
@strategy = strategy
|
||||
@current_values = values
|
||||
end
|
||||
|
||||
# Generates an association using the current build strategy.
|
||||
|
@ -34,6 +35,42 @@ class Factory
|
|||
Factory.send(strategy, name, attributes)
|
||||
end
|
||||
|
||||
# Returns the value for specified attribute. A value will only be available
|
||||
# if it was overridden when calling the factory, or if a value is added
|
||||
# earlier in the definition of the factory.
|
||||
#
|
||||
# Arguments:
|
||||
# attribute: (Symbol)
|
||||
# The attribute whose value should be returned.
|
||||
#
|
||||
# Returns:
|
||||
# The value of the requested attribute. (Object)
|
||||
def value_for (attribute)
|
||||
unless current_values.key?(attribute)
|
||||
raise ArgumentError, "No such attribute: #{attribute.inspect}"
|
||||
end
|
||||
current_values[attribute]
|
||||
end
|
||||
|
||||
# Undefined methods are delegated to value_for, which means that:
|
||||
#
|
||||
# Factory.define :user do |f|
|
||||
# f.first_name 'Ben'
|
||||
# f.last_name {|a| a.value_for(:first_name) }
|
||||
# end
|
||||
#
|
||||
# and:
|
||||
#
|
||||
# Factory.define :user do |f|
|
||||
# f.first_name 'Ben'
|
||||
# f.last_name {|a| a.first_name }
|
||||
# end
|
||||
#
|
||||
# are equivilent.
|
||||
def method_missing (name, *args, &block)
|
||||
current_values[name]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -34,7 +34,8 @@ class Factory
|
|||
@options = options
|
||||
|
||||
@static_attributes = {}
|
||||
@lazy_attributes = {}
|
||||
@lazy_attribute_blocks = {}
|
||||
@lazy_attribute_names = []
|
||||
end
|
||||
|
||||
# Adds an attribute that should be assigned on generated instances for this
|
||||
|
@ -60,7 +61,8 @@ class Factory
|
|||
unless value.nil?
|
||||
raise ArgumentError, "Both value and block given"
|
||||
end
|
||||
@lazy_attributes[name] = block
|
||||
@lazy_attribute_blocks[name] = block
|
||||
@lazy_attribute_names << name
|
||||
else
|
||||
@static_attributes[name] = value
|
||||
end
|
||||
|
@ -158,13 +160,12 @@ class Factory
|
|||
private
|
||||
|
||||
def build_attributes_hash (override, strategy)
|
||||
result = {}
|
||||
@lazy_attributes.each do |name, block|
|
||||
proxy = AttributeProxy.new(self, name, strategy)
|
||||
result[name] = block.call(proxy) unless override.key?(name)
|
||||
result = @static_attributes.merge(override)
|
||||
@lazy_attribute_names.each do |name|
|
||||
proxy = AttributeProxy.new(self, name, strategy, result)
|
||||
result[name] = @lazy_attribute_blocks[name].call(proxy) unless override.key?(name)
|
||||
end
|
||||
result.update(@static_attributes)
|
||||
result.update(override)
|
||||
result
|
||||
end
|
||||
|
||||
def build_instance (override, strategy)
|
||||
|
|
|
@ -7,8 +7,9 @@ class AttributeProxyTest < Test::Unit::TestCase
|
|||
setup do
|
||||
@factory = mock('factory')
|
||||
@attr = :user
|
||||
@attrs = { :first_name => 'John' }
|
||||
@strategy = :create
|
||||
@proxy = Factory::AttributeProxy.new(@factory, @attr, @strategy)
|
||||
@proxy = Factory::AttributeProxy.new(@factory, @attr, @strategy, @attrs)
|
||||
end
|
||||
|
||||
should "have a factory" do
|
||||
|
@ -23,6 +24,10 @@ class AttributeProxyTest < Test::Unit::TestCase
|
|||
assert_equal @strategy, @proxy.strategy
|
||||
end
|
||||
|
||||
should "have attributes" do
|
||||
assert_equal @attrs, @proxy.current_values
|
||||
end
|
||||
|
||||
context "building an association" do
|
||||
|
||||
setup do
|
||||
|
@ -44,6 +49,34 @@ class AttributeProxyTest < Test::Unit::TestCase
|
|||
|
||||
end
|
||||
|
||||
context "fetching the value of an attribute" do
|
||||
|
||||
setup do
|
||||
@attr = :first_name
|
||||
end
|
||||
|
||||
should "return the correct value" do
|
||||
assert_equal @attrs[@attr], @proxy.value_for(@attr)
|
||||
end
|
||||
|
||||
should "call value_for for undefined methods" do
|
||||
assert_equal @attrs[@attr], @proxy.send(@attr)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "fetching the value of an undefined attribute" do
|
||||
|
||||
setup do
|
||||
@attr = :beachball
|
||||
end
|
||||
|
||||
should "raise an ArgumentError" do
|
||||
assert_raise(ArgumentError) { @proxy.value_for(@attr) }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -86,6 +86,7 @@ class FactoryTest < Test::Unit::TestCase
|
|||
|
||||
setup do
|
||||
@attr = :name
|
||||
@attrs = {}
|
||||
@proxy = mock('attr-proxy')
|
||||
Factory::AttributeProxy.stubs(:new).returns(@proxy)
|
||||
end
|
||||
|
@ -110,7 +111,7 @@ class FactoryTest < Test::Unit::TestCase
|
|||
end
|
||||
|
||||
should "build an attribute proxy" do
|
||||
Factory::AttributeProxy.expects(:new).with(@factory, @attr, :attributes_for)
|
||||
Factory::AttributeProxy.expects(:new).with(@factory, @attr, :attributes_for, @attrs)
|
||||
@factory.add_attribute(@attr) {}
|
||||
@factory.attributes_for
|
||||
end
|
||||
|
@ -122,6 +123,26 @@ class FactoryTest < Test::Unit::TestCase
|
|||
assert_equal @proxy, yielded
|
||||
end
|
||||
|
||||
context "when other attributes have previously been defined" do
|
||||
|
||||
setup do
|
||||
@attr = :unimportant
|
||||
@attrs = {
|
||||
:one => 'whatever',
|
||||
:another => 'soup'
|
||||
}
|
||||
@factory.add_attribute(:one, 'whatever')
|
||||
@factory.add_attribute(:another) { 'soup' }
|
||||
@factory.add_attribute(@attr) {}
|
||||
end
|
||||
|
||||
should "provide previously set attributes" do
|
||||
Factory::AttributeProxy.expects(:new).with(@factory, @attr, :attributes_for, @attrs)
|
||||
@factory.attributes_for
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
should "add an attribute using the method name when passed an undefined method" do
|
||||
|
|
Loading…
Reference in a new issue