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 }
|
u.activation_code { User.generate_activation_code }
|
||||||
end
|
end
|
||||||
|
|
||||||
== Using factories
|
== Dependent Attributes
|
||||||
|
|
||||||
# Build and save a User instance
|
Some attributes may need to be generated based on the values of other
|
||||||
Factory(:user)
|
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.define :user do |u|
|
||||||
Factory.build(:user, :first_name => 'Joe')
|
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
|
Factory(:user, :last_name => 'Doe').email
|
||||||
attrs = Factory.attributes_for(:user)
|
# => "joe.doe@example.com"
|
||||||
|
|
||||||
== Associations
|
== Associations
|
||||||
|
|
||||||
|
@ -66,3 +70,15 @@ When using the association method, the same build strategy (build, create, or at
|
||||||
Factory.build(:post)
|
Factory.build(:post)
|
||||||
post.new_record? # => true
|
post.new_record? # => true
|
||||||
post.author.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
|
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
|
@factory = factory
|
||||||
@attribute_name = attr
|
@attribute_name = attr
|
||||||
@strategy = strategy
|
@strategy = strategy
|
||||||
|
@current_values = values
|
||||||
end
|
end
|
||||||
|
|
||||||
# Generates an association using the current build strategy.
|
# Generates an association using the current build strategy.
|
||||||
|
@ -34,6 +35,42 @@ class Factory
|
||||||
Factory.send(strategy, name, attributes)
|
Factory.send(strategy, name, attributes)
|
||||||
end
|
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
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -34,7 +34,8 @@ class Factory
|
||||||
@options = options
|
@options = options
|
||||||
|
|
||||||
@static_attributes = {}
|
@static_attributes = {}
|
||||||
@lazy_attributes = {}
|
@lazy_attribute_blocks = {}
|
||||||
|
@lazy_attribute_names = []
|
||||||
end
|
end
|
||||||
|
|
||||||
# Adds an attribute that should be assigned on generated instances for this
|
# Adds an attribute that should be assigned on generated instances for this
|
||||||
|
@ -60,7 +61,8 @@ class Factory
|
||||||
unless value.nil?
|
unless value.nil?
|
||||||
raise ArgumentError, "Both value and block given"
|
raise ArgumentError, "Both value and block given"
|
||||||
end
|
end
|
||||||
@lazy_attributes[name] = block
|
@lazy_attribute_blocks[name] = block
|
||||||
|
@lazy_attribute_names << name
|
||||||
else
|
else
|
||||||
@static_attributes[name] = value
|
@static_attributes[name] = value
|
||||||
end
|
end
|
||||||
|
@ -158,13 +160,12 @@ class Factory
|
||||||
private
|
private
|
||||||
|
|
||||||
def build_attributes_hash (override, strategy)
|
def build_attributes_hash (override, strategy)
|
||||||
result = {}
|
result = @static_attributes.merge(override)
|
||||||
@lazy_attributes.each do |name, block|
|
@lazy_attribute_names.each do |name|
|
||||||
proxy = AttributeProxy.new(self, name, strategy)
|
proxy = AttributeProxy.new(self, name, strategy, result)
|
||||||
result[name] = block.call(proxy) unless override.key?(name)
|
result[name] = @lazy_attribute_blocks[name].call(proxy) unless override.key?(name)
|
||||||
end
|
end
|
||||||
result.update(@static_attributes)
|
result
|
||||||
result.update(override)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_instance (override, strategy)
|
def build_instance (override, strategy)
|
||||||
|
|
|
@ -7,8 +7,9 @@ class AttributeProxyTest < Test::Unit::TestCase
|
||||||
setup do
|
setup do
|
||||||
@factory = mock('factory')
|
@factory = mock('factory')
|
||||||
@attr = :user
|
@attr = :user
|
||||||
|
@attrs = { :first_name => 'John' }
|
||||||
@strategy = :create
|
@strategy = :create
|
||||||
@proxy = Factory::AttributeProxy.new(@factory, @attr, @strategy)
|
@proxy = Factory::AttributeProxy.new(@factory, @attr, @strategy, @attrs)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "have a factory" do
|
should "have a factory" do
|
||||||
|
@ -23,6 +24,10 @@ class AttributeProxyTest < Test::Unit::TestCase
|
||||||
assert_equal @strategy, @proxy.strategy
|
assert_equal @strategy, @proxy.strategy
|
||||||
end
|
end
|
||||||
|
|
||||||
|
should "have attributes" do
|
||||||
|
assert_equal @attrs, @proxy.current_values
|
||||||
|
end
|
||||||
|
|
||||||
context "building an association" do
|
context "building an association" do
|
||||||
|
|
||||||
setup do
|
setup do
|
||||||
|
@ -44,6 +49,34 @@ class AttributeProxyTest < Test::Unit::TestCase
|
||||||
|
|
||||||
end
|
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
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -86,6 +86,7 @@ class FactoryTest < Test::Unit::TestCase
|
||||||
|
|
||||||
setup do
|
setup do
|
||||||
@attr = :name
|
@attr = :name
|
||||||
|
@attrs = {}
|
||||||
@proxy = mock('attr-proxy')
|
@proxy = mock('attr-proxy')
|
||||||
Factory::AttributeProxy.stubs(:new).returns(@proxy)
|
Factory::AttributeProxy.stubs(:new).returns(@proxy)
|
||||||
end
|
end
|
||||||
|
@ -110,7 +111,7 @@ class FactoryTest < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
should "build an attribute proxy" do
|
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.add_attribute(@attr) {}
|
||||||
@factory.attributes_for
|
@factory.attributes_for
|
||||||
end
|
end
|
||||||
|
@ -122,6 +123,26 @@ class FactoryTest < Test::Unit::TestCase
|
||||||
assert_equal @proxy, yielded
|
assert_equal @proxy, yielded
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
should "add an attribute using the method name when passed an undefined method" do
|
should "add an attribute using the method name when passed an undefined method" do
|
||||||
|
|
Loading…
Reference in a new issue