mirror of
https://github.com/thoughtbot/factory_bot.git
synced 2022-11-09 11:43:51 -05:00
Added the AttributeProxy class and an association method, and added functionality for using AttributeProxy from Factory. This allows lazy generation of associations using the appropriate method.
This commit is contained in:
parent
0a6a54c6b9
commit
7b3936b611
6 changed files with 172 additions and 16 deletions
35
README
35
README
|
@ -21,6 +21,19 @@ factories there. This file can be included from test_helper or directly from
|
|||
your test files. Don't forget:
|
||||
require 'factory_girl'
|
||||
|
||||
== Lazy Attributes
|
||||
|
||||
Most attributes can be added using static values that are evaluated when the
|
||||
factory is defined, but some attributes (such as associations and other
|
||||
attributes that must be dynamically generated) will need values assigned each
|
||||
time an instance is generated. These "lazy" attributes can be added by passing
|
||||
a block instead of a parameter:
|
||||
|
||||
Factory.define :user do |u|
|
||||
# ...
|
||||
u.activation_code { User.generate_activation_code }
|
||||
end
|
||||
|
||||
== Using factories
|
||||
|
||||
# Build and save a User instance
|
||||
|
@ -31,3 +44,25 @@ your test files. Don't forget:
|
|||
|
||||
# Return an attributes Hash that can be used to build a User instance
|
||||
attrs = Factory.attributes_for(:user)
|
||||
|
||||
== Associations
|
||||
|
||||
Associated instances can be generated by using the association method when
|
||||
defining a lazy attribute:
|
||||
|
||||
Factory.define :post do |p|
|
||||
# ...
|
||||
p.author {|author| author.association(:user, :last_name => 'Writely') }
|
||||
end
|
||||
|
||||
When using the association method, the same build strategy (build, create, or attributes_for) will be used for all generated instances:
|
||||
|
||||
# Builds and saves a User and a Post
|
||||
post = Factory(:post)
|
||||
post.new_record? # => false
|
||||
post.author.new_record # => false
|
||||
|
||||
# Builds but does not save a User and a Post
|
||||
Factory.build(:post)
|
||||
post.new_record? # => true
|
||||
post.author.new_record # => true
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
require 'activesupport'
|
||||
require 'factory_girl/factory'
|
||||
require 'factory_girl/attribute_proxy'
|
||||
|
||||
# Shortcut for Factory.create.
|
||||
#
|
||||
|
|
39
lib/factory_girl/attribute_proxy.rb
Normal file
39
lib/factory_girl/attribute_proxy.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
class Factory
|
||||
|
||||
class AttributeProxy
|
||||
|
||||
attr_accessor :factory, :attribute_name, :strategy #:nodoc:
|
||||
|
||||
def initialize (factory, attr, strategy) #:nodoc:
|
||||
@factory = factory
|
||||
@attribute_name = attr
|
||||
@strategy = strategy
|
||||
end
|
||||
|
||||
# Generates an association using the current build strategy.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# Factory.define :user do |f|
|
||||
# # ...
|
||||
# end
|
||||
#
|
||||
# Factory.define :post do |f|
|
||||
# # ...
|
||||
# f.author {|a| a.association :user, :name => 'Joe' }
|
||||
# end
|
||||
#
|
||||
# # Builds (but doesn't save) a Post and a User
|
||||
# Factory.build(:post)
|
||||
#
|
||||
# # Builds and saves a User, builds a Post, assigns the User to the
|
||||
# # author association, and saves the User.
|
||||
# Factory.create(:post)
|
||||
#
|
||||
def association (name, attributes = {})
|
||||
Factory.send(strategy, name, attributes)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -5,8 +5,8 @@ class Factory
|
|||
|
||||
attr_reader :name
|
||||
|
||||
# Defines a new factory that can be used by the generation methods (create
|
||||
# and build) to build new objects.
|
||||
# Defines a new factory that can be used by the build strategies (create and
|
||||
# build) to build new objects.
|
||||
#
|
||||
# Arguments:
|
||||
# name: (Symbol)
|
||||
|
@ -37,13 +37,18 @@ class Factory
|
|||
@lazy_attributes = {}
|
||||
end
|
||||
|
||||
# Adds an attribute that should be assigned on generated instances for this factory.
|
||||
# Adds an attribute that should be assigned on generated instances for this
|
||||
# factory.
|
||||
#
|
||||
# This method should be called with either a value or block, but not both. If
|
||||
# called with a block, the attribute will be generated "lazily," whenever an
|
||||
# instance is generated. Lazy attribute blocks will not be called if that
|
||||
# attribute is overriden for a specific instance.
|
||||
#
|
||||
# When defining lazy attributes, an instance of Factory::AttributeProxy will
|
||||
# be yielded, allowing associations to be built using the correct build
|
||||
# strategy.
|
||||
#
|
||||
# Arguments:
|
||||
# name: (Symbol)
|
||||
# The name of this attribute. This will be assigned using :"#{name}=" for
|
||||
|
@ -80,24 +85,15 @@ class Factory
|
|||
end
|
||||
|
||||
def attributes_for (attrs = {}) #:nodoc:
|
||||
result = {}
|
||||
@lazy_attributes.each do |name, block|
|
||||
result[name] = block.call unless attrs.key?(name)
|
||||
end
|
||||
result.update(@static_attributes)
|
||||
result.update(attrs)
|
||||
build_attributes_hash(attrs, :attributes_for)
|
||||
end
|
||||
|
||||
def build (attrs = {}) #:nodoc:
|
||||
instance = build_class.new
|
||||
attributes_for(attrs).each do |attr, value|
|
||||
instance.send(:"#{attr}=", value)
|
||||
end
|
||||
instance
|
||||
build_instance(attrs, :build)
|
||||
end
|
||||
|
||||
def create (attrs = {}) #:nodoc:
|
||||
instance = build(attrs)
|
||||
instance = build_instance(attrs, :create)
|
||||
instance.save!
|
||||
instance
|
||||
end
|
||||
|
@ -159,4 +155,25 @@ class Factory
|
|||
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
result.update(@static_attributes)
|
||||
result.update(override)
|
||||
end
|
||||
|
||||
def build_instance (override, strategy)
|
||||
instance = build_class.new
|
||||
attrs = build_attributes_hash(override, strategy)
|
||||
attrs.each do |attr, value|
|
||||
instance.send(:"#{attr}=", value)
|
||||
end
|
||||
instance
|
||||
end
|
||||
|
||||
end
|
||||
|
|
49
test/attribute_proxy_test.rb
Normal file
49
test/attribute_proxy_test.rb
Normal file
|
@ -0,0 +1,49 @@
|
|||
require(File.join(File.dirname(__FILE__), 'test_helper'))
|
||||
|
||||
class AttributeProxyTest < Test::Unit::TestCase
|
||||
|
||||
context "an association proxy" do
|
||||
|
||||
setup do
|
||||
@factory = mock('factory')
|
||||
@attr = :user
|
||||
@strategy = :create
|
||||
@proxy = Factory::AttributeProxy.new(@factory, @attr, @strategy)
|
||||
end
|
||||
|
||||
should "have a factory" do
|
||||
assert_equal @factory, @proxy.factory
|
||||
end
|
||||
|
||||
should "have an attribute name" do
|
||||
assert_equal @attr, @proxy.attribute_name
|
||||
end
|
||||
|
||||
should "have a build strategy" do
|
||||
assert_equal @strategy, @proxy.strategy
|
||||
end
|
||||
|
||||
context "building an association" do
|
||||
|
||||
setup do
|
||||
@association = mock('built-user')
|
||||
@name = :user
|
||||
@attribs = { :first_name => 'Billy' }
|
||||
|
||||
Factory.stubs(@strategy).returns(@association)
|
||||
end
|
||||
|
||||
should "delegate to the appropriate method on Factory" do
|
||||
Factory.expects(@strategy).with(@name, @attribs).returns(@association)
|
||||
@proxy.association(@name, @attribs)
|
||||
end
|
||||
|
||||
should "return the built association" do
|
||||
assert_equal @association, @proxy.association(@name)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -85,7 +85,9 @@ class FactoryTest < Test::Unit::TestCase
|
|||
context "when adding an attribute with a block" do
|
||||
|
||||
setup do
|
||||
@attr = :name
|
||||
@attr = :name
|
||||
@proxy = mock('attr-proxy')
|
||||
Factory::AttributeProxy.stubs(:new).returns(@proxy)
|
||||
end
|
||||
|
||||
should "not evaluate the block when the attribute is added" do
|
||||
|
@ -107,6 +109,19 @@ class FactoryTest < Test::Unit::TestCase
|
|||
assert_equal value, @factory.attributes_for[@attr]
|
||||
end
|
||||
|
||||
should "build an attribute proxy" do
|
||||
Factory::AttributeProxy.expects(:new).with(@factory, @attr, :attributes_for)
|
||||
@factory.add_attribute(@attr) {}
|
||||
@factory.attributes_for
|
||||
end
|
||||
|
||||
should "yield an attribute proxy to the block" do
|
||||
yielded = nil
|
||||
@factory.add_attribute(@attr) {|y| yielded = y }
|
||||
@factory.attributes_for
|
||||
assert_equal @proxy, yielded
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
should "add an attribute using the method name when passed an undefined method" do
|
||||
|
|
Loading…
Add table
Reference in a new issue