diff --git a/README b/README index 215097a..45e5b44 100644 --- a/README +++ b/README @@ -34,16 +34,20 @@ a block instead of a parameter: u.activation_code { User.generate_activation_code } end -== Using factories +== Dependent Attributes - # Build and save a User instance - Factory(:user) +Some attributes may need to be generated based on the values of other +attributes. This can be done by calling the attribute name on +Factory::AttributeProxy, which is yielded to lazy attribute blocks: - # Build a User instance and override the first_name property - Factory.build(:user, :first_name => 'Joe') + Factory.define :user do |u| + u.first_name 'Joe' + u.last_name 'Blow' + u.email {|a| "#{a.first_name}.#{a.last_name}@example.com".downcase } + end - # Return an attributes Hash that can be used to build a User instance - attrs = Factory.attributes_for(:user) + Factory(:user, :last_name => 'Doe').email + # => "joe.doe@example.com" == Associations @@ -66,3 +70,15 @@ When using the association method, the same build strategy (build, create, or at Factory.build(:post) post.new_record? # => true post.author.new_record # => true + +== Using factories + + # Build and save a User instance + Factory(:user) + + # Build a User instance and override the first_name property + Factory.build(:user, :first_name => 'Joe') + + # Return an attributes Hash that can be used to build a User instance + attrs = Factory.attributes_for(:user) + diff --git a/lib/factory_girl/attribute_proxy.rb b/lib/factory_girl/attribute_proxy.rb index 58fe23a..d19a286 100644 --- a/lib/factory_girl/attribute_proxy.rb +++ b/lib/factory_girl/attribute_proxy.rb @@ -2,12 +2,13 @@ class Factory class AttributeProxy - attr_accessor :factory, :attribute_name, :strategy #:nodoc: + attr_accessor :factory, :attribute_name, :strategy, :current_values #:nodoc: - def initialize (factory, attr, strategy) #:nodoc: + def initialize (factory, attr, strategy, values) #:nodoc: @factory = factory @attribute_name = attr @strategy = strategy + @current_values = values end # Generates an association using the current build strategy. @@ -34,6 +35,42 @@ class Factory Factory.send(strategy, name, attributes) end + # Returns the value for specified attribute. A value will only be available + # if it was overridden when calling the factory, or if a value is added + # earlier in the definition of the factory. + # + # Arguments: + # attribute: (Symbol) + # The attribute whose value should be returned. + # + # Returns: + # The value of the requested attribute. (Object) + def value_for (attribute) + unless current_values.key?(attribute) + raise ArgumentError, "No such attribute: #{attribute.inspect}" + end + current_values[attribute] + end + + # Undefined methods are delegated to value_for, which means that: + # + # Factory.define :user do |f| + # f.first_name 'Ben' + # f.last_name {|a| a.value_for(:first_name) } + # end + # + # and: + # + # Factory.define :user do |f| + # f.first_name 'Ben' + # f.last_name {|a| a.first_name } + # end + # + # are equivilent. + def method_missing (name, *args, &block) + current_values[name] + end + end end diff --git a/lib/factory_girl/factory.rb b/lib/factory_girl/factory.rb index 9f37b7d..0ec799f 100644 --- a/lib/factory_girl/factory.rb +++ b/lib/factory_girl/factory.rb @@ -33,8 +33,9 @@ class Factory @name = name @options = options - @static_attributes = {} - @lazy_attributes = {} + @static_attributes = {} + @lazy_attribute_blocks = {} + @lazy_attribute_names = [] end # Adds an attribute that should be assigned on generated instances for this @@ -60,7 +61,8 @@ class Factory unless value.nil? raise ArgumentError, "Both value and block given" end - @lazy_attributes[name] = block + @lazy_attribute_blocks[name] = block + @lazy_attribute_names << name else @static_attributes[name] = value end @@ -158,13 +160,12 @@ class Factory 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) + result = @static_attributes.merge(override) + @lazy_attribute_names.each do |name| + proxy = AttributeProxy.new(self, name, strategy, result) + result[name] = @lazy_attribute_blocks[name].call(proxy) unless override.key?(name) end - result.update(@static_attributes) - result.update(override) + result end def build_instance (override, strategy) diff --git a/test/attribute_proxy_test.rb b/test/attribute_proxy_test.rb index 0298500..fabfb73 100644 --- a/test/attribute_proxy_test.rb +++ b/test/attribute_proxy_test.rb @@ -7,8 +7,9 @@ class AttributeProxyTest < Test::Unit::TestCase setup do @factory = mock('factory') @attr = :user + @attrs = { :first_name => 'John' } @strategy = :create - @proxy = Factory::AttributeProxy.new(@factory, @attr, @strategy) + @proxy = Factory::AttributeProxy.new(@factory, @attr, @strategy, @attrs) end should "have a factory" do @@ -23,6 +24,10 @@ class AttributeProxyTest < Test::Unit::TestCase assert_equal @strategy, @proxy.strategy end + should "have attributes" do + assert_equal @attrs, @proxy.current_values + end + context "building an association" do setup do @@ -44,6 +49,34 @@ class AttributeProxyTest < Test::Unit::TestCase end + context "fetching the value of an attribute" do + + setup do + @attr = :first_name + end + + should "return the correct value" do + assert_equal @attrs[@attr], @proxy.value_for(@attr) + end + + should "call value_for for undefined methods" do + assert_equal @attrs[@attr], @proxy.send(@attr) + end + + end + + context "fetching the value of an undefined attribute" do + + setup do + @attr = :beachball + end + + should "raise an ArgumentError" do + assert_raise(ArgumentError) { @proxy.value_for(@attr) } + end + + end + end end diff --git a/test/factory_test.rb b/test/factory_test.rb index be5018a..943abaa 100644 --- a/test/factory_test.rb +++ b/test/factory_test.rb @@ -86,6 +86,7 @@ class FactoryTest < Test::Unit::TestCase setup do @attr = :name + @attrs = {} @proxy = mock('attr-proxy') Factory::AttributeProxy.stubs(:new).returns(@proxy) end @@ -110,7 +111,7 @@ class FactoryTest < Test::Unit::TestCase end should "build an attribute proxy" do - Factory::AttributeProxy.expects(:new).with(@factory, @attr, :attributes_for) + Factory::AttributeProxy.expects(:new).with(@factory, @attr, :attributes_for, @attrs) @factory.add_attribute(@attr) {} @factory.attributes_for end @@ -122,6 +123,26 @@ class FactoryTest < Test::Unit::TestCase assert_equal @proxy, yielded end + context "when other attributes have previously been defined" do + + setup do + @attr = :unimportant + @attrs = { + :one => 'whatever', + :another => 'soup' + } + @factory.add_attribute(:one, 'whatever') + @factory.add_attribute(:another) { 'soup' } + @factory.add_attribute(@attr) {} + end + + should "provide previously set attributes" do + Factory::AttributeProxy.expects(:new).with(@factory, @attr, :attributes_for, @attrs) + @factory.attributes_for + end + + end + end should "add an attribute using the method name when passed an undefined method" do