Add transient variables

Closes #142
Closes #103
This commit is contained in:
Joshua Clayton 2011-08-20 18:05:41 -04:00
parent 0ddc78bd4e
commit c87429b829
15 changed files with 161 additions and 20 deletions

View File

@ -149,6 +149,30 @@ Attributes can be based on the values of other attributes using the proxy that i
FactoryGirl.create(:user, :last_name => 'Doe').email
# => "joe.doe@example.com"
Transient Attributes
--------------------
There may be times where your code can be DRYed up by passing in transient attributes to factories.
factory :user do
rockstar(true).ignore
upcased { false }.ignore
name { "John Doe#{" - Rockstar" if rockstar}" }
email { "#{name.downcase}@example.com" }
after_create do |user, proxy|
user.name.upcase! if proxy.upcased
end
end
FactoryGirl.create(:user, :upcased => true).name
# => "JOHN DOE - ROCKSTAR"
Static and dynamic attributes can be ignored. Ignored attributes will be ignored within attributes_for and won't be set on the model, even if the attribute exists or you attempt to override it.
Within Factory Girl's dynamic attributes, you can access ignored attributes as you would expect. If you need to access the proxy in a Factory Girl callback, you'll need to declare a second block argument (for the proxy) and access ignored attributes from there.
Associations
------------

View File

@ -10,13 +10,18 @@ module FactoryGirl
class Attribute #:nodoc:
include Comparable
attr_reader :name
attr_reader :name, :ignored
def initialize(name)
@name = name.to_sym
@ignored = false
ensure_non_attribute_writer!
end
def ignore
@ignored = true
end
def add_to(proxy)
end

View File

@ -11,7 +11,7 @@ module FactoryGirl
if FactoryGirl::Sequence === value
raise SequenceAbuseError
end
proxy.set(name, value)
proxy.set(name, value, @ignored)
end
end
end

View File

@ -9,7 +9,7 @@ module FactoryGirl
end
def add_to(proxy)
proxy.set(name, @value)
proxy.set(name, @value, @ignored)
end
def priority

View File

@ -86,7 +86,7 @@ module FactoryGirl
if factory_overrides.empty?
attribute.add_to(proxy)
else
factory_overrides.each { |attr, val| proxy.set(attr, val); overrides.delete(attr) }
factory_overrides.each { |attr, val| proxy.set(attr, val, attribute.ignored); overrides.delete(attr) }
end
end
overrides.each { |attr, val| proxy.set(attr, val) }

View File

@ -9,7 +9,7 @@ module FactoryGirl
def get(attribute)
end
def set(attribute, value)
def set(attribute, value, ignored = false)
end
def associate(name, factory, attributes)
@ -24,7 +24,11 @@ module FactoryGirl
def run_callbacks(name)
if @callbacks && @callbacks[name]
@callbacks[name].each do |block|
block.arity.zero? ? block.call : block.call(@instance)
case block.arity
when 0 then block.call
when 2 then block.call(@instance, self)
else block.call(@instance)
end
end
end
end

View File

@ -3,14 +3,19 @@ module FactoryGirl
class AttributesFor < Proxy #:nodoc:
def initialize(klass)
@hash = {}
@ignored_attributes = {}
end
def get(attribute)
@hash[attribute]
@ignored_attributes[attribute] || @hash[attribute]
end
def set(attribute, value)
@hash[attribute] = value
def set(attribute, value, ignored = false)
if ignored
@ignored_attributes[attribute] = value
else
@hash[attribute] = value
end
end
def result(to_create)

View File

@ -3,14 +3,23 @@ module FactoryGirl
class Build < Proxy #:nodoc:
def initialize(klass)
@instance = klass.new
@ignored_attributes = {}
end
def get(attribute)
@instance.send(attribute)
if @ignored_attributes.has_key?(attribute)
@ignored_attributes[attribute]
else
@instance.send(attribute)
end
end
def set(attribute, value)
@instance.send(:"#{attribute}=", value)
def set(attribute, value, ignored = false)
if ignored
@ignored_attributes[attribute] = value
else
@instance.send(:"#{attribute}=", value)
end
end
def associate(name, factory_name, overrides)

View File

@ -5,6 +5,7 @@ module FactoryGirl
def initialize(klass)
@instance = klass.new
@ignored_attributes = {}
@instance.id = next_id
@instance.instance_eval do
def persisted?
@ -42,11 +43,19 @@ module FactoryGirl
end
def get(attribute)
@instance.send(attribute)
if @ignored_attributes.has_key?(attribute)
@ignored_attributes[attribute]
else
@instance.send(attribute)
end
end
def set(attribute, value)
@instance.send(:"#{attribute}=", value)
def set(attribute, value, ignored = false)
if ignored
@ignored_attributes[attribute] = value
else
@instance.send(:"#{attribute}=", value)
end
end
def associate(name, factory_name, overrides)

View File

@ -0,0 +1,68 @@
require "spec_helper"
describe "transient attributes" do
before do
define_model("User", :name => :string, :email => :string)
FactoryGirl.define do
sequence(:name) {|n| "John #{n}" }
factory :user do
four { 2 + 2 }.ignore
rockstar(true).ignore
upcased(false).ignore
name { "#{FactoryGirl.generate(:name)}#{" - Rockstar" if rockstar}" }
email { "#{name.downcase}#{four}@example.com" }
after_create do |user, proxy|
user.name.upcase! if proxy.upcased
end
end
end
end
context "returning attributes for a factory" do
subject { FactoryGirl.attributes_for(:user, :rockstar => true) }
it { should_not have_key(:four) }
it { should_not have_key(:rockstar) }
it { should_not have_key(:upcased) }
it { should have_key(:name) }
it { should have_key(:email) }
end
context "with a transient variable assigned" do
let(:rockstar) { FactoryGirl.create(:user, :rockstar => true, :four => "1234") }
let(:rockstar_with_name) { FactoryGirl.create(:user, :name => "Jane Doe", :rockstar => true) }
let(:upcased_rockstar) { FactoryGirl.create(:user, :rockstar => true, :upcased => true) }
let(:groupie) { FactoryGirl.create(:user, :rockstar => false) }
it "generates the correct attributes on a rockstar" do
rockstar.name.should == "John 1 - Rockstar"
rockstar.email.should == "john 1 - rockstar1234@example.com"
end
it "generates the correct attributes on an upcased rockstar" do
upcased_rockstar.name.should == "JOHN 1 - ROCKSTAR"
upcased_rockstar.email.should == "john 1 - rockstar4@example.com"
end
it "generates the correct attributes on a groupie" do
groupie.name.should == "John 1"
groupie.email.should == "john 14@example.com"
end
it "generates the correct attributes on a rockstar with a name" do
rockstar_with_name.name.should == "Jane Doe"
rockstar_with_name.email.should == "jane doe4@example.com"
end
end
context "without transient variables assigned" do
let(:rockstar) { FactoryGirl.create(:user) }
it "uses the default value of the attribute" do
rockstar.name.should == "John 1 - Rockstar"
end
end
end

View File

@ -14,7 +14,7 @@ describe FactoryGirl::Attribute::Dynamic do
it "calls the block to set a value" do
subject.add_to(proxy)
proxy.should have_received(:set).with(name, "value")
proxy.should have_received(:set).with(name, "value", false)
end
end
@ -23,7 +23,7 @@ describe FactoryGirl::Attribute::Dynamic do
it "yields the proxy to the block" do
subject.add_to(proxy)
proxy.should have_received(:set).with(name, proxy)
proxy.should have_received(:set).with(name, proxy, false)
end
end
@ -37,7 +37,7 @@ describe FactoryGirl::Attribute::Dynamic do
it "evaluates the attribute from the proxy" do
subject.add_to(proxy)
proxy.should have_received(:set).with(name, result)
proxy.should have_received(:set).with(name, result, false)
end
end

View File

@ -12,7 +12,7 @@ describe FactoryGirl::Attribute::Static do
it "sets its static value on a proxy" do
proxy.stubs(:set)
subject.add_to(proxy)
proxy.should have_received(:set).with(name, value)
proxy.should have_received(:set).with(name, value, false)
end
end

View File

@ -309,7 +309,7 @@ end
describe FactoryGirl::Factory, "running a factory" do
subject { FactoryGirl::Factory.new(:user) }
let(:attribute) { stub("attribute", :name => :name, :add_to => nil, :aliases_for? => true) }
let(:attribute) { stub("attribute", :name => :name, :ignored => false, :add_to => nil, :aliases_for? => true) }
let(:proxy) { stub("proxy", :result => "result", :set => nil) }
before do

View File

@ -71,5 +71,14 @@ describe FactoryGirl::Proxy do
subject.run_callbacks(:after_create)
object_1_within_callback.should have_received(:foo).once
end
it "passes in the instance and the proxy if the block takes two arguments" do
subject.instance_variable_set("@instance", object_1_within_callback)
proxy_instance = nil
subject.add_callback(:after_create, proc {|spy, proxy| spy.foo; proxy_instance = proxy })
subject.run_callbacks(:after_create)
object_1_within_callback.should have_received(:foo).once
proxy_instance.should == subject
end
end
end

View File

@ -63,6 +63,14 @@ shared_examples_for "proxy with standard getters and setters" do |attribute, val
it { instance.should have_received(:"#{attribute}=").with(value) }
end
describe "when setting an ignored attribute" do
before do
subject.set(attribute, value, true)
end
it { instance.should have_received(:"#{attribute}=").with(value).never }
end
describe "when getting an attribute" do
it { subject.get(attribute).should == value }