1
0
Fork 0
mirror of https://github.com/thoughtbot/factory_bot.git synced 2022-11-09 11:43:51 -05:00

Added a new definition syntax using instance_eval

This commit is contained in:
Joe Ferris 2010-07-06 20:54:55 -04:00
parent 7c649b2c85
commit bf0b6a6c16
6 changed files with 254 additions and 242 deletions

View file

@ -13,6 +13,7 @@ require 'factory_girl/sequence'
require 'factory_girl/aliases'
require 'factory_girl/definition_proxy'
require 'factory_girl/syntax/default'
require 'factory_girl/syntax/vintage'
require 'factory_girl/find_definitions'
require 'factory_girl/deprecated'

View file

@ -9,7 +9,7 @@ module FactoryGirl
def initialize(value = 1, &proc) #:nodoc:
@proc = proc
@value = value || 1
@value = value || 1
end
# Returns the next value for this sequence

View file

@ -1,201 +1,31 @@
module FactoryGirl
module Syntax
module Default
module Factory
# Defines a new factory that can be used by the build strategies (create and
# build) to build new objects.
#
# Arguments:
# * name: +Symbol+ or +String+
# A unique name used to identify this factory.
# * options: +Hash+
#
# Options:
# * class: +Symbol+, +Class+, or +String+
# The class that will be used when generating instances for this factory. If not specified, the class will be guessed from the factory name.
# * parent: +Symbol+
# The parent factory. If specified, the attributes from the parent
# factory will be copied to the current one with an ability to override
# them.
# * default_strategy: +Symbol+
# The strategy that will be used by the Factory shortcut method.
# Defaults to :create.
#
# Yields: +Factory+
# The newly created factory.
def self.define(name, options = {})
factory = FactoryGirl::Factory.new(name, options)
def define(&block)
DSL.run(block)
end
class DSL
def self.run(block)
new.instance_eval(&block)
end
def factory(name, options = {}, &block)
factory = Factory.new(name, options)
proxy = FactoryGirl::DefinitionProxy.new(factory)
yield(proxy)
proxy.instance_eval(&block)
if parent = options.delete(:parent)
factory.inherit_from(FactoryGirl.factory_by_name(parent))
end
FactoryGirl.register_factory(factory)
end
# Generates and returns a Hash of attributes from this factory. Attributes
# can be individually overridden by passing in a Hash of attribute => value
# pairs.
#
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this set.
#
# Returns: +Hash+
# A set of attributes that can be used to build an instance of the class
# this factory generates.
def self.attributes_for(name, overrides = {})
FactoryGirl.factory_by_name(name).run(Proxy::AttributesFor, overrides)
end
# Generates and returns an instance from this factory. Attributes can be
# individually overridden by passing in a Hash of attribute => value pairs.
#
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this instance.
#
# Returns: +Object+
# An instance of the class this factory generates, with generated attributes
# assigned.
def self.build(name, overrides = {})
FactoryGirl.factory_by_name(name).run(Proxy::Build, overrides)
end
# Generates, saves, and returns an instance from this factory. Attributes can
# be individually overridden by passing in a Hash of attribute => value
# pairs.
#
# Instances are saved using the +save!+ method, so ActiveRecord models will
# raise ActiveRecord::RecordInvalid exceptions for invalid attribute sets.
#
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this instance.
#
# Returns: +Object+
# A saved instance of the class this factory generates, with generated
# attributes assigned.
def self.create(name, overrides = {})
FactoryGirl.factory_by_name(name).run(Proxy::Create, overrides)
end
# Generates and returns an object with all attributes from this factory
# stubbed out. Attributes can be individually overridden by passing in a Hash
# of attribute => value pairs.
#
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this instance.
#
# Returns: +Object+
# An object with generated attributes stubbed out.
def self.stub(name, overrides = {})
FactoryGirl.factory_by_name(name).run(Proxy::Stub, overrides)
end
# Executes the default strategy for the given factory. This is usually create,
# but it can be overridden for each factory.
#
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this instance.
#
# Returns: +Object+
# The result of the default strategy.
def self.default_strategy(name, overrides = {})
self.send(FactoryGirl.factory_by_name(name).default_strategy, name, overrides)
end
# Defines a new sequence that can be used to generate unique values in a specific format.
#
# Arguments:
# name: (Symbol)
# A unique name for this sequence. This name will be referenced when
# calling next to generate new values from this sequence.
# start_value: +Integer+ or +String+
# The starting value for this sequence. Any object that responds to
# +next+ will work.
# Defaults to 1.
# block: (Proc)
# The code to generate each value in the sequence. This block will be
# called with a unique value each time an item in the sequence is to be
# generated. The block should return the generated value for the
# sequence.
#
# Example:
#
# Factory.sequence(:email) {|n| "somebody_#{n}@example.com" }
# Factory.sequence(:product_code, "AAAA") {|s| "PC-#{s}" }
def self.sequence(name, start_value = 1, &block)
def sequence(name, start_value = 1, &block)
FactoryGirl.sequences[name] = Sequence.new(start_value, &block)
end
# Generates and returns the next value in a sequence.
#
# Arguments:
# name: (Symbol)
# The name of the sequence that a value should be generated for.
#
# Returns:
# The next value in the sequence. (Object)
def self.next(sequence)
unless FactoryGirl.sequences.key?(sequence)
raise "No such sequence: #{sequence}"
end
FactoryGirl.sequences[sequence].next
end
# Defines a new alias for attributes.
#
# Arguments:
# * pattern: +Regexp+
# A pattern that will be matched against attributes when looking for
# aliases. Contents captured in the pattern can be used in the alias.
# * replace: +String+
# The alias that results from the matched pattern. Captured strings can
# be substituted like with +String#sub+.
#
# Example:
#
# Factory.alias /(.*)_confirmation/, '\1'
#
# factory_girl starts with aliases for foreign keys, so that a :user
# association can be overridden by a :user_id parameter:
#
# Factory.define :post do |p|
# p.association :user
# end
#
# # The user association will not be built in this example. The user_id
# # will be used instead.
# Factory(:post, :user_id => 1)
def self.alias(pattern, replace)
FactoryGirl.aliases << [pattern, replace]
end
end
# Shortcut for Factory.default_strategy.
#
# Example:
# Factory(:user, :name => 'Joe')
def Factory(name, attrs = {})
Factory.default_strategy(name, attrs)
end
end
end
end
include FactoryGirl::Syntax::Default
extend Syntax::Default
end

View file

@ -0,0 +1,196 @@
module FactoryGirl
module Syntax
module Vintage
module Factory
# Defines a new factory that can be used by the build strategies (create and
# build) to build new objects.
#
# Arguments:
# * name: +Symbol+ or +String+
# A unique name used to identify this factory.
# * options: +Hash+
#
# Options:
# * class: +Symbol+, +Class+, or +String+
# The class that will be used when generating instances for this factory. If not specified, the class will be guessed from the factory name.
# * parent: +Symbol+
# The parent factory. If specified, the attributes from the parent
# factory will be copied to the current one with an ability to override
# them.
# * default_strategy: +Symbol+
# The strategy that will be used by the Factory shortcut method.
# Defaults to :create.
#
# Yields: +Factory+
# The newly created factory.
def self.define(name, options = {})
factory = FactoryGirl::Factory.new(name, options)
proxy = FactoryGirl::DefinitionProxy.new(factory)
yield(proxy)
if parent = options.delete(:parent)
factory.inherit_from(FactoryGirl.factory_by_name(parent))
end
FactoryGirl.register_factory(factory)
end
# Generates and returns a Hash of attributes from this factory. Attributes
# can be individually overridden by passing in a Hash of attribute => value
# pairs.
#
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this set.
#
# Returns: +Hash+
# A set of attributes that can be used to build an instance of the class
# this factory generates.
def self.attributes_for(name, overrides = {})
FactoryGirl.factory_by_name(name).run(Proxy::AttributesFor, overrides)
end
# Generates and returns an instance from this factory. Attributes can be
# individually overridden by passing in a Hash of attribute => value pairs.
#
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this instance.
#
# Returns: +Object+
# An instance of the class this factory generates, with generated attributes
# assigned.
def self.build(name, overrides = {})
FactoryGirl.factory_by_name(name).run(Proxy::Build, overrides)
end
# Generates, saves, and returns an instance from this factory. Attributes can
# be individually overridden by passing in a Hash of attribute => value
# pairs.
#
# Instances are saved using the +save!+ method, so ActiveRecord models will
# raise ActiveRecord::RecordInvalid exceptions for invalid attribute sets.
#
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this instance.
#
# Returns: +Object+
# A saved instance of the class this factory generates, with generated
# attributes assigned.
def self.create(name, overrides = {})
FactoryGirl.factory_by_name(name).run(Proxy::Create, overrides)
end
# Generates and returns an object with all attributes from this factory
# stubbed out. Attributes can be individually overridden by passing in a Hash
# of attribute => value pairs.
#
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this instance.
#
# Returns: +Object+
# An object with generated attributes stubbed out.
def self.stub(name, overrides = {})
FactoryGirl.factory_by_name(name).run(Proxy::Stub, overrides)
end
# Executes the default strategy for the given factory. This is usually create,
# but it can be overridden for each factory.
#
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this instance.
#
# Returns: +Object+
# The result of the default strategy.
def self.default_strategy(name, overrides = {})
self.send(FactoryGirl.factory_by_name(name).default_strategy, name, overrides)
end
# Defines a new sequence that can be used to generate unique values in a specific format.
#
# Arguments:
# name: (Symbol)
# A unique name for this sequence. This name will be referenced when
# calling next to generate new values from this sequence.
# block: (Proc)
# The code to generate each value in the sequence. This block will be
# called with a unique number each time a value in the sequence is to be
# generated. The block should return the generated value for the
# sequence.
#
# Example:
#
# Factory.sequence(:email) {|n| "somebody_#{n}@example.com" }
def self.sequence(name, start_value = 1, &block)
FactoryGirl.sequences[name] = Sequence.new(start_value, &block)
end
# Generates and returns the next value in a sequence.
#
# Arguments:
# name: (Symbol)
# The name of the sequence that a value should be generated for.
#
# Returns:
# The next value in the sequence. (Object)
def self.next(sequence)
unless FactoryGirl.sequences.key?(sequence)
raise "No such sequence: #{sequence}"
end
FactoryGirl.sequences[sequence].next
end
# Defines a new alias for attributes.
#
# Arguments:
# * pattern: +Regexp+
# A pattern that will be matched against attributes when looking for
# aliases. Contents captured in the pattern can be used in the alias.
# * replace: +String+
# The alias that results from the matched pattern. Captured strings can
# be substituted like with +String#sub+.
#
# Example:
#
# Factory.alias /(.*)_confirmation/, '\1'
#
# factory_girl starts with aliases for foreign keys, so that a :user
# association can be overridden by a :user_id parameter:
#
# Factory.define :post do |p|
# p.association :user
# end
#
# # The user association will not be built in this example. The user_id
# # will be used instead.
# Factory(:post, :user_id => 1)
def self.alias(pattern, replace)
FactoryGirl.aliases << [pattern, replace]
end
end
# Shortcut for Factory.default_strategy.
#
# Example:
# Factory(:user, :name => 'Joe')
def Factory(name, attrs = {})
Factory.default_strategy(name, attrs)
end
end
end
end
include FactoryGirl::Syntax::Vintage

View file

@ -3,57 +3,58 @@ require 'acceptance/acceptance_helper'
describe "integration" do
before do
Factory.define :user, :class => 'user' do |f|
f.first_name 'Jimi'
f.last_name 'Hendrix'
f.admin false
f.email {|a| "#{a.first_name}.#{a.last_name}@example.com".downcase }
end
FactoryGirl.define do
factory :user, :class => 'user' do
first_name 'Jimi'
last_name 'Hendrix'
admin false
email {|a| "#{a.first_name}.#{a.last_name}@example.com".downcase }
end
Factory.define Post, :default_strategy => :attributes_for do |f|
f.name 'Test Post'
f.association :author, :factory => :user
end
factory Post, :default_strategy => :attributes_for do
name 'Test Post'
association :author, :factory => :user
end
Factory.define :admin, :class => User do |f|
f.first_name 'Ben'
f.last_name 'Stein'
f.admin true
f.sequence(:username) { |n| "username#{n}" }
f.email { Factory.next(:email) }
end
factory :admin, :class => User do
first_name 'Ben'
last_name 'Stein'
admin true
sequence(:username) { |n| "username#{n}" }
email { Factory.next(:email) }
end
Factory.define :sequence_abuser, :class => User do |f|
f.first_name { Factory.sequence(:email) }
end
factory :sequence_abuser, :class => User do
first_name { Factory.sequence(:email) }
end
Factory.define :guest, :parent => :user do |f|
f.last_name 'Anonymous'
f.username 'GuestUser'
end
factory :guest, :parent => :user do
last_name 'Anonymous'
username 'GuestUser'
end
Factory.define :user_with_callbacks, :parent => :user do |f|
f.after_stub {|u| u.first_name = 'Stubby' }
f.after_build {|u| u.first_name = 'Buildy' }
f.after_create {|u| u.last_name = 'Createy' }
end
factory :user_with_callbacks, :parent => :user do
after_stub {|u| u.first_name = 'Stubby' }
after_build {|u| u.first_name = 'Buildy' }
after_create {|u| u.last_name = 'Createy' }
end
Factory.define :user_with_inherited_callbacks, :parent => :user_with_callbacks do |f|
f.after_stub {|u| u.last_name = 'Double-Stubby' }
end
factory :user_with_inherited_callbacks, :parent => :user_with_callbacks do
after_stub {|u| u.last_name = 'Double-Stubby' }
end
Factory.define :business do |f|
f.name 'Supplier of Awesome'
f.association :owner, :factory => :user
end
factory :business do
name 'Supplier of Awesome'
association :owner, :factory => :user
end
Factory.sequence :email do |n|
"somebody#{n}@example.com"
sequence :email do |n|
"somebody#{n}@example.com"
end
end
end
describe "a generated attributes hash" do
before do
@attrs = Factory.attributes_for(:user, :first_name => 'Bill')
end
@ -75,11 +76,9 @@ describe "integration" do
it "should not assign associations" do
Factory.attributes_for(:post)[:author].should be_nil
end
end
describe "a built instance" do
before do
@instance = Factory.build(:post)
end
@ -99,11 +98,9 @@ describe "integration" do
it "should not assign both an association and its foreign key" do
Factory.build(:post, :author_id => 1).author_id.should == 1
end
end
describe "a created instance" do
before do
@instance = Factory.create('post')
end
@ -119,11 +116,9 @@ describe "integration" do
it "should save associations" do
@instance.author.should_not be_new_record
end
end
describe "a generated stub instance" do
before do
@stub = Factory.stub(:user, :first_name => 'Bill')
end
@ -170,7 +165,6 @@ describe "integration" do
end
describe "an instance generated by a factory with a custom class name" do
before do
@instance = Factory.create(:admin)
end
@ -182,7 +176,6 @@ describe "integration" do
it "should use the correct factory definition" do
@instance.should be_admin
end
end
describe "an instance generated by a factory that inherits from another factory" do
@ -208,7 +201,6 @@ describe "integration" do
end
describe "an attribute generated by a sequence" do
before do
@email = Factory.attributes_for(:admin)[:email]
end
@ -218,7 +210,6 @@ describe "integration" do
end
describe "after the attribute has already been generated once" do
before do
@another_email = Factory.attributes_for(:admin)[:email]
end
@ -230,13 +221,10 @@ describe "integration" do
it "should not be the same as the first generated value" do
@another_email.should_not == @email
end
end
end
describe "an attribute generated by an in-line sequence" do
before do
@username = Factory.attributes_for(:admin)[:username]
end
@ -246,7 +234,6 @@ describe "integration" do
end
describe "after the attribute has already been generated once" do
before do
@another_username = Factory.attributes_for(:admin)[:username]
end
@ -258,9 +245,7 @@ describe "integration" do
it "should not be the same as the first generated value" do
@another_username.should_not == @username
end
end
end
describe "a factory with a default strategy specified" do

View file

@ -153,7 +153,7 @@ describe "defining a sequence" do
Factory.sequence(@name) {|n| yielded = true }
(yielded).should be
end
it "should use the supplied start_value as the sequence start_value" do
mock(FactoryGirl::Sequence).new("A") { @sequence }
Factory.sequence(@name, "A")