Skip to content

Commit

Permalink
Add transient variables
Browse files Browse the repository at this point in the history
Closes #142
Closes #103
  • Loading branch information
joshuaclayton committed Aug 20, 2011
1 parent 0ddc78b commit c87429b
Show file tree
Hide file tree
Showing 15 changed files with 161 additions and 20 deletions.
24 changes: 24 additions & 0 deletions GETTING_STARTED.md
Expand Up @@ -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
------------

Expand Down
7 changes: 6 additions & 1 deletion lib/factory_girl/attribute.rb
Expand Up @@ -10,13 +10,18 @@ class AttributeDefinitionError < RuntimeError
class Attribute #:nodoc:
include Comparable

attr_reader :name
attr_reader :name, :ignored

This comment has been minimized.

Copy link
@hoverlover

hoverlover Aug 21, 2011

Sweet! Thanks guys.


def initialize(name)
@name = name.to_sym
@ignored = false
ensure_non_attribute_writer!
end

def ignore
@ignored = true
end

def add_to(proxy)
end

Expand Down
2 changes: 1 addition & 1 deletion lib/factory_girl/attribute/dynamic.rb
Expand Up @@ -11,7 +11,7 @@ def add_to(proxy)
if FactoryGirl::Sequence === value
raise SequenceAbuseError
end
proxy.set(name, value)
proxy.set(name, value, @ignored)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/factory_girl/attribute/static.rb
Expand Up @@ -9,7 +9,7 @@ def initialize(name, value)
end

def add_to(proxy)
proxy.set(name, @value)
proxy.set(name, @value, @ignored)
end

def priority
Expand Down
2 changes: 1 addition & 1 deletion lib/factory_girl/factory.rb
Expand Up @@ -86,7 +86,7 @@ def run(proxy_class, overrides) #:nodoc:
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) }
Expand Down
8 changes: 6 additions & 2 deletions lib/factory_girl/proxy.rb
Expand Up @@ -9,7 +9,7 @@ def initialize(klass)
def get(attribute)
end

def set(attribute, value)
def set(attribute, value, ignored = false)
end

def associate(name, factory, attributes)
Expand All @@ -24,7 +24,11 @@ def add_callback(name, block)
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
Expand Down
11 changes: 8 additions & 3 deletions lib/factory_girl/proxy/attributes_for.rb
Expand Up @@ -3,14 +3,19 @@ class Proxy #:nodoc:
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)
Expand Down
15 changes: 12 additions & 3 deletions lib/factory_girl/proxy/build.rb
Expand Up @@ -3,14 +3,23 @@ class Proxy #:nodoc:
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)
Expand Down
15 changes: 12 additions & 3 deletions lib/factory_girl/proxy/stub.rb
Expand Up @@ -5,6 +5,7 @@ class Stub < Proxy #:nodoc:

def initialize(klass)
@instance = klass.new
@ignored_attributes = {}
@instance.id = next_id
@instance.instance_eval do
def persisted?
Expand Down Expand Up @@ -42,11 +43,19 @@ def next_id
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)
Expand Down
68 changes: 68 additions & 0 deletions spec/acceptance/transient_attributes_spec.rb
@@ -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
6 changes: 3 additions & 3 deletions spec/factory_girl/attribute/dynamic_spec.rb
Expand Up @@ -14,7 +14,7 @@

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

Expand All @@ -23,7 +23,7 @@

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

Expand All @@ -37,7 +37,7 @@

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

Expand Down
2 changes: 1 addition & 1 deletion spec/factory_girl/attribute/static_spec.rb
Expand Up @@ -12,7 +12,7 @@
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

Expand Down
2 changes: 1 addition & 1 deletion spec/factory_girl/factory_spec.rb
Expand Up @@ -309,7 +309,7 @@

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
Expand Down
9 changes: 9 additions & 0 deletions spec/factory_girl/proxy_spec.rb
Expand Up @@ -71,5 +71,14 @@
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
8 changes: 8 additions & 0 deletions spec/support/shared_examples/proxy.rb
Expand Up @@ -63,6 +63,14 @@
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 }

Expand Down

0 comments on commit c87429b

Please sign in to comment.