From 572323135f235cc43e8a5bbafa3a634b4ec71ba8 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 7 Oct 2009 23:50:30 +0100 Subject: Allow accepts_nested_attributes_for :reject_if option accept symbols for using a method Conflicts: activerecord/lib/active_record/nested_attributes.rb --- .../lib/active_record/nested_attributes.rb | 39 ++++++++++++++++++---- 1 file changed, 33 insertions(+), 6 deletions(-) (limited to 'activerecord/lib/active_record/nested_attributes.rb') diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 3c8140816c..654e91bece 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -127,6 +127,22 @@ module ActiveRecord # member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!' # member.posts.second.title # => 'The egalitarian assumption of the modern citizen' # + # Alternatively, :reject_if also accepts a symbol for using methods: + # + # class Member < ActiveRecord::Base + # has_many :posts + # accepts_nested_attributes_for :posts, :reject_if => :new_record? + # end + # + # class Member < ActiveRecord::Base + # has_many :posts + # accepts_nested_attributes_for :posts, :reject_if => :reject_posts + # + # def reject_posts(attributed) + # attributed['title].blank? + # end + # end + # # If the hash contains an id key that matches an already # associated record, the matching record will be modified: # @@ -179,10 +195,11 @@ module ActiveRecord # _destroy key and a value that evaluates to +true+ # (eg. 1, '1', true, or 'true'). This option is off by default. # [:reject_if] - # Allows you to specify a Proc that checks whether a record should be - # built for a certain attribute hash. The hash is passed to the Proc - # and the Proc should return either +true+ or +false+. When no Proc - # is specified a record will be built for all attribute hashes that + # Allows you to specify a Proc or a Symbol pointing to a method + # that checks whether a record should be built for a certain attribute + # hash. The hash is passed to the supplied Proc or the method + # and it should return either +true+ or +false+. When no :reject_if + # is specified, a record will be built for all attribute hashes that # do not have a _destroy value that evaluates to true. # Passing :all_blank instead of a Proc will create a proc # that will reject a record where all the attributes are blank. @@ -351,8 +368,18 @@ module ActiveRecord # has_destroy_flag? or if a :reject_if proc exists for this # association and evaluates to +true+. def reject_new_record?(association_name, attributes) - has_destroy_flag?(attributes) || - self.class.reject_new_nested_attributes_procs[association_name].try(:call, attributes) + has_destroy_flag?(attributes) || call_reject_if(association_name, attributes) + end + + def call_reject_if(association_name, attributes) + callback = self.class.reject_new_nested_attributes_procs[association_name] + + case callback + when Symbol + method(callback).arity == 0 ? send(callback) : send(callback, attributes) + when Proc + callback.try(:call, attributes) + end end end end -- cgit v1.2.3 From 0c27d0886e880f90240ad2004f1600c1b1c4bfed Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Thu, 8 Oct 2009 00:14:37 +0100 Subject: Use indifferent access attributes instead of stringifying them --- activerecord/lib/active_record/nested_attributes.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record/nested_attributes.rb') diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 654e91bece..ec6c02db38 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -283,7 +283,7 @@ module ActiveRecord # :_destroy key set to a truthy value, then the existing record # will be marked for destruction. def assign_nested_attributes_for_one_to_one_association(association_name, attributes, allow_destroy) - attributes = attributes.stringify_keys + attributes = attributes.with_indifferent_access if attributes['id'].blank? unless reject_new_record?(association_name, attributes) @@ -336,7 +336,7 @@ module ActiveRecord end attributes_collection.each do |attributes| - attributes = attributes.stringify_keys + attributes = attributes.with_indifferent_access if attributes['id'].blank? unless reject_new_record?(association_name, attributes) -- cgit v1.2.3 From e94caf0788df87b139e575f33cdeea12b06f2609 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 9 Oct 2009 15:37:10 +0100 Subject: Store entire options hash in the class var rather than just the reject_if proc for the nested attributes --- .../lib/active_record/nested_attributes.rb | 27 ++++++++++++---------- 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'activerecord/lib/active_record/nested_attributes.rb') diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index ec6c02db38..05c7e9713f 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -6,8 +6,8 @@ module ActiveRecord extend ActiveSupport::Concern included do - class_inheritable_accessor :reject_new_nested_attributes_procs, :instance_writer => false - self.reject_new_nested_attributes_procs = {} + class_inheritable_accessor :nested_attributes_options, :instance_writer => false + self.nested_attributes_options = {} end # == Nested Attributes @@ -227,10 +227,10 @@ module ActiveRecord reflection.options[:autosave] = true - self.reject_new_nested_attributes_procs[association_name.to_sym] = if options[:reject_if] == :all_blank - proc { |attributes| attributes.all? {|k,v| v.blank?} } - else - options[:reject_if] + self.nested_attributes_options[association_name.to_sym] = options + + if options[:reject_if] == :all_blank + self.nested_attributes_options[association_name.to_sym][:reject_if] = proc { |attributes| attributes.all? {|k,v| v.blank?} } end # def pirate_attributes=(attributes) @@ -238,7 +238,7 @@ module ActiveRecord # end class_eval %{ def #{association_name}_attributes=(attributes) - assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, #{options[:allow_destroy]}) + assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes) end }, __FILE__, __LINE__ else @@ -282,7 +282,8 @@ module ActiveRecord # If the given attributes include a matching :id attribute _and_ a # :_destroy key set to a truthy value, then the existing record # will be marked for destruction. - def assign_nested_attributes_for_one_to_one_association(association_name, attributes, allow_destroy) + def assign_nested_attributes_for_one_to_one_association(association_name, attributes) + options = self.nested_attributes_options[association_name] attributes = attributes.with_indifferent_access if attributes['id'].blank? @@ -295,7 +296,7 @@ module ActiveRecord end end elsif (existing_record = send(association_name)) && existing_record.id.to_s == attributes['id'].to_s - assign_to_or_mark_for_destruction(existing_record, attributes, allow_destroy) + assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) end end @@ -326,7 +327,9 @@ module ActiveRecord # { :name => 'John' }, # { :id => '2', :_destroy => true } # ]) - def assign_nested_attributes_for_collection_association(association_name, attributes_collection, allow_destroy) + def assign_nested_attributes_for_collection_association(association_name, attributes_collection) + options = self.nested_attributes_options[association_name] + unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array) raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})" end @@ -343,7 +346,7 @@ module ActiveRecord send(association_name).build(attributes.except(*UNASSIGNABLE_KEYS)) end elsif existing_record = send(association_name).detect { |record| record.id.to_s == attributes['id'].to_s } - assign_to_or_mark_for_destruction(existing_record, attributes, allow_destroy) + assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) end end end @@ -372,7 +375,7 @@ module ActiveRecord end def call_reject_if(association_name, attributes) - callback = self.class.reject_new_nested_attributes_procs[association_name] + callback = self.nested_attributes_options[association_name][:reject_if] case callback when Symbol -- cgit v1.2.3 From 68d416a58fb5a47df2365c4f3a6da9f8db5c7cb7 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 9 Oct 2009 16:08:11 +0100 Subject: Add a :limit option to specify the maximum number of records that can be processed by accepts_nested_attributes_for --- activerecord/lib/active_record/nested_attributes.rb | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/nested_attributes.rb') diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 05c7e9713f..edcf547e01 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -3,6 +3,9 @@ require 'active_support/core_ext/object/try' module ActiveRecord module NestedAttributes #:nodoc: + class TooManyRecords < ActiveRecordError + end + extend ActiveSupport::Concern included do @@ -203,6 +206,12 @@ module ActiveRecord # do not have a _destroy value that evaluates to true. # Passing :all_blank instead of a Proc will create a proc # that will reject a record where all the attributes are blank. + # [:limit] + # Allows you to specify the maximum number of the associated records that + # can be processes with the nested attributes. If the size of the + # nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords + # exception is raised. If omitted, any number associations can be processed. + # Note that the :limit option is only applicable to one-to-many associations. # # Examples: # # creates avatar_attributes= @@ -214,7 +223,7 @@ module ActiveRecord def accepts_nested_attributes_for(*attr_names) options = { :allow_destroy => false } options.update(attr_names.extract_options!) - options.assert_valid_keys(:allow_destroy, :reject_if) + options.assert_valid_keys(:allow_destroy, :reject_if, :limit) attr_names.each do |association_name| if reflection = reflect_on_association(association_name) @@ -334,6 +343,10 @@ module ActiveRecord raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})" end + if options[:limit] && attributes_collection.size > options[:limit] + raise TooManyRecords, "Maximum #{options[:limit]} records are allowed. Got #{attributes_collection.size} records instead." + end + if attributes_collection.is_a? Hash attributes_collection = attributes_collection.sort_by { |index, _| index.to_i }.map { |_, attributes| attributes } end -- cgit v1.2.3