From c87429b8298ff98e0fe3437de2951a7f3aa6330c Mon Sep 17 00:00:00 2001 From: Joshua Clayton Date: Sat, 20 Aug 2011 18:05:41 -0400 Subject: [PATCH] Add transient variables Closes #142 Closes #103 --- GETTING_STARTED.md | 24 +++++++ lib/factory_girl/attribute.rb | 7 +- lib/factory_girl/attribute/dynamic.rb | 2 +- lib/factory_girl/attribute/static.rb | 2 +- lib/factory_girl/factory.rb | 2 +- lib/factory_girl/proxy.rb | 8 ++- lib/factory_girl/proxy/attributes_for.rb | 11 +++- lib/factory_girl/proxy/build.rb | 15 ++++- lib/factory_girl/proxy/stub.rb | 15 ++++- spec/acceptance/transient_attributes_spec.rb | 68 ++++++++++++++++++++ spec/factory_girl/attribute/dynamic_spec.rb | 6 +- spec/factory_girl/attribute/static_spec.rb | 2 +- spec/factory_girl/factory_spec.rb | 2 +- spec/factory_girl/proxy_spec.rb | 9 +++ spec/support/shared_examples/proxy.rb | 8 +++ 15 files changed, 161 insertions(+), 20 deletions(-) create mode 100644 spec/acceptance/transient_attributes_spec.rb diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index d24af10..8e92b2b 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -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 ------------ diff --git a/lib/factory_girl/attribute.rb b/lib/factory_girl/attribute.rb index b217489..8d14256 100644 --- a/lib/factory_girl/attribute.rb +++ b/lib/factory_girl/attribute.rb @@ -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 diff --git a/lib/factory_girl/attribute/dynamic.rb b/lib/factory_girl/attribute/dynamic.rb index 9dc28ff..d421a63 100644 --- a/lib/factory_girl/attribute/dynamic.rb +++ b/lib/factory_girl/attribute/dynamic.rb @@ -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 diff --git a/lib/factory_girl/attribute/static.rb b/lib/factory_girl/attribute/static.rb index 85e4df3..becc315 100644 --- a/lib/factory_girl/attribute/static.rb +++ b/lib/factory_girl/attribute/static.rb @@ -9,7 +9,7 @@ module FactoryGirl end def add_to(proxy) - proxy.set(name, @value) + proxy.set(name, @value, @ignored) end def priority diff --git a/lib/factory_girl/factory.rb b/lib/factory_girl/factory.rb index 2c8e286..905fd50 100644 --- a/lib/factory_girl/factory.rb +++ b/lib/factory_girl/factory.rb @@ -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) } diff --git a/lib/factory_girl/proxy.rb b/lib/factory_girl/proxy.rb index a9acecd..cb23f28 100644 --- a/lib/factory_girl/proxy.rb +++ b/lib/factory_girl/proxy.rb @@ -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 diff --git a/lib/factory_girl/proxy/attributes_for.rb b/lib/factory_girl/proxy/attributes_for.rb index cdac21c..a658f2d 100644 --- a/lib/factory_girl/proxy/attributes_for.rb +++ b/lib/factory_girl/proxy/attributes_for.rb @@ -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) diff --git a/lib/factory_girl/proxy/build.rb b/lib/factory_girl/proxy/build.rb index 28cdb09..25aea58 100644 --- a/lib/factory_girl/proxy/build.rb +++ b/lib/factory_girl/proxy/build.rb @@ -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) diff --git a/lib/factory_girl/proxy/stub.rb b/lib/factory_girl/proxy/stub.rb index 3560fcd..73563c9 100644 --- a/lib/factory_girl/proxy/stub.rb +++ b/lib/factory_girl/proxy/stub.rb @@ -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) diff --git a/spec/acceptance/transient_attributes_spec.rb b/spec/acceptance/transient_attributes_spec.rb new file mode 100644 index 0000000..67ac9f6 --- /dev/null +++ b/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 diff --git a/spec/factory_girl/attribute/dynamic_spec.rb b/spec/factory_girl/attribute/dynamic_spec.rb index a209197..2ba78b8 100644 --- a/spec/factory_girl/attribute/dynamic_spec.rb +++ b/spec/factory_girl/attribute/dynamic_spec.rb @@ -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 diff --git a/spec/factory_girl/attribute/static_spec.rb b/spec/factory_girl/attribute/static_spec.rb index ab5300e..cb8f6a1 100644 --- a/spec/factory_girl/attribute/static_spec.rb +++ b/spec/factory_girl/attribute/static_spec.rb @@ -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 diff --git a/spec/factory_girl/factory_spec.rb b/spec/factory_girl/factory_spec.rb index 83eaf7f..01c4711 100644 --- a/spec/factory_girl/factory_spec.rb +++ b/spec/factory_girl/factory_spec.rb @@ -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 diff --git a/spec/factory_girl/proxy_spec.rb b/spec/factory_girl/proxy_spec.rb index 98fd243..6188780 100644 --- a/spec/factory_girl/proxy_spec.rb +++ b/spec/factory_girl/proxy_spec.rb @@ -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 diff --git a/spec/support/shared_examples/proxy.rb b/spec/support/shared_examples/proxy.rb index cb8a2bc..73325e3 100644 --- a/spec/support/shared_examples/proxy.rb +++ b/spec/support/shared_examples/proxy.rb @@ -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 }