diff --git a/lib/factory_girl.rb b/lib/factory_girl.rb index e1f999f06..f026f17a2 100644 --- a/lib/factory_girl.rb +++ b/lib/factory_girl.rb @@ -13,6 +13,7 @@ require 'factory_girl/attribute/sequence' require 'factory_girl/attribute/implicit' require 'factory_girl/sequence' +require 'factory_girl/attribute_group' require 'factory_girl/aliases' require 'factory_girl/definition_proxy' require 'factory_girl/syntax/methods' diff --git a/lib/factory_girl/attribute_group.rb b/lib/factory_girl/attribute_group.rb new file mode 100644 index 000000000..f6e6e5784 --- /dev/null +++ b/lib/factory_girl/attribute_group.rb @@ -0,0 +1,29 @@ +module FactoryGirl + + class AttributeGroup + attr_reader :name + attr_reader :attributes + + def initialize(name, &block) #:nodoc: + @name = name + @attributes = [] + proxy = FactoryGirl::DefinitionProxy.new(self) + proxy.instance_eval(&block) if block_given? + end + + def define_attribute(attribute) + name = attribute.name + if attribute_defined?(name) + raise AttributeDefinitionError, "Attribute already defined: #{name}" + end + @attributes << attribute + end + + private + + def attribute_defined? (name) + !@attributes.detect {|attr| attr.name == name && !attr.is_a?(Attribute::Callback) }.nil? + end + + end +end diff --git a/lib/factory_girl/definition_proxy.rb b/lib/factory_girl/definition_proxy.rb index b9be6bcbb..bd81108be 100644 --- a/lib/factory_girl/definition_proxy.rb +++ b/lib/factory_girl/definition_proxy.rb @@ -7,7 +7,6 @@ class DefinitionProxy end attr_reader :child_factories - def initialize(factory) @factory = factory @child_factories = [] @@ -154,5 +153,9 @@ def to_create(&block) def factory(name, options = {}, &block) @child_factories << [name, options, block] end + + def attr_group(name, &block) + @factory.define_attribute_group(AttributeGroup.new(name, &block)) + end end end diff --git a/lib/factory_girl/factory.rb b/lib/factory_girl/factory.rb index fb047b51f..34a871e50 100644 --- a/lib/factory_girl/factory.rb +++ b/lib/factory_girl/factory.rb @@ -14,7 +14,8 @@ class DuplicateDefinitionError < RuntimeError class Factory attr_reader :name #:nodoc: attr_reader :attributes #:nodoc: - + attr_reader :attribute_groups #:nodoc: + def factory_name puts "WARNING: factory.factory_name is deprecated. Use factory.name instead." name @@ -37,6 +38,7 @@ def initialize(name, options = {}) #:nodoc: @name = factory_name_for(name) @options = options @attributes = [] + @attribute_groups = {} end def inherit_from(parent) #:nodoc: @@ -55,7 +57,24 @@ def inherit_from(parent) #:nodoc: end @attributes.unshift *new_attributes - @attributes = @attributes.partition {|attr| attr.priority.zero? }.flatten + @attributes = @attributes.partition{|attr| attr.priority.zero? }.flatten + end + + def apply_attribute_groups(groups) + groups.reverse.map{ |name| attribute_group_by_name(name) }.each do |group| + new_attributes=[] + group.attributes.each do |attribute| + if attribute_defined?(attribute.name) + @attributes.delete_if do |attrib| + new_attributes << attrib.clone if attrib.name == attribute.name + end + else + new_attributes << attribute.clone + end + end + @attributes.unshift *new_attributes + @attributes = @attributes.partition{|attr| attr.priority.zero?}.flatten + end end def define_attribute(attribute) @@ -69,6 +88,10 @@ def define_attribute(attribute) end @attributes << attribute end + + def define_attribute_group(group) + @attribute_groups[group.name.to_sym] = group + end def add_callback(name, &block) unless [:after_build, :after_create, :after_stub].include?(name.to_sym) @@ -100,6 +123,14 @@ def associations attributes.select {|attribute| attribute.association? } end + def attribute_group_by_name(name) + name=name.to_sym + group = @attribute_groups[name] + unless @options[:parent].nil? + group ||= FactoryGirl.factory_by_name(@options[:parent]).attribute_group_by_name(name) + end + group + end # Names for this factory, including aliases. # # Example: @@ -159,7 +190,7 @@ def attribute_defined? (name) end def assert_valid_options(options) - invalid_keys = options.keys - [:class, :parent, :default_strategy, :aliases] + invalid_keys = options.keys - [:class, :parent, :default_strategy, :aliases, :attr_groups] unless invalid_keys == [] raise ArgumentError, "Unknown arguments: #{invalid_keys.inspect}" end diff --git a/lib/factory_girl/syntax/default.rb b/lib/factory_girl/syntax/default.rb index 4165a616c..03d20eb2e 100644 --- a/lib/factory_girl/syntax/default.rb +++ b/lib/factory_girl/syntax/default.rb @@ -16,6 +16,11 @@ def factory(name, options = {}, &block) factory = Factory.new(name, options) proxy = FactoryGirl::DefinitionProxy.new(factory) proxy.instance_eval(&block) if block_given? + + if groups = options.delete(:attr_groups) + factory.apply_attribute_groups(groups) + end + if parent = options.delete(:parent) factory.inherit_from(FactoryGirl.factory_by_name(parent)) end @@ -25,7 +30,7 @@ def factory(name, options = {}, &block) factory(child_name, child_options.merge(:parent => name), &child_block) end end - + def sequence(name, start_value = 1, &block) FactoryGirl.register_sequence(Sequence.new(name, start_value, &block)) end diff --git a/spec/acceptance/attribute_groups_spec.rb b/spec/acceptance/attribute_groups_spec.rb index 07b866870..2e4917727 100644 --- a/spec/acceptance/attribute_groups_spec.rb +++ b/spec/acceptance/attribute_groups_spec.rb @@ -14,6 +14,7 @@ end attr_group :male do + name "Joe" gender "Male" end @@ -38,47 +39,47 @@ its(:gender) { should be_nil } it { should_not be_admin } end - + context "the child class with one attribute group" do subject { FactoryGirl.create(:admin) } its(:name) { should == "John" } its(:gender) { should be_nil } it { should be_admin } end - + context "the other child class with one attribute group" do subject { FactoryGirl.create(:female) } its(:name) { should == "Jane" } its(:gender) { should == "Female" } it { should_not be_admin } end - + context "the child with multiple attribute groups" do subject { FactoryGirl.create(:female_admin) } its(:name) { should == "Jane" } its(:gender) { should == "Female" } it { should be_admin } end - + context "the child with multiple attribute groups and overridden attributes" do subject { FactoryGirl.create(:female_admin, :name => "Jill", :gender => nil) } its(:name) { should == "Jill" } its(:gender) { should be_nil } it { should be_admin } end - + context "the child with multiple attribute groups who override the same attribute" do context "when the male assigns name after female" do subject { FactoryGirl.create(:male_after_female_admin) } - - its(:name) { should == "John" } + + its(:name) { should == "Joe" } its(:gender) { should == "Male" } it { should be_admin } end - + context "when the female assigns name after male" do subject { FactoryGirl.create(:female_after_male_admin) } - + its(:name) { should == "Jane" } its(:gender) { should == "Female" } it { should be_admin }