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 --- .../lib/active_record/nested_attributes.rb | 15 ++++++++++- activerecord/test/cases/nested_attributes_test.rb | 30 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) (limited to 'activerecord') 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 diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 2a2a19fd4c..53fd168e1b 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -603,3 +603,33 @@ class TestNestedAttributesOnAHasAndBelongsToManyAssociation < ActiveRecord::Test include NestedAttributesOnACollectionAssociationTests end + +class TestNestedAttributesLimit < ActiveRecord::TestCase + def setup + Pirate.accepts_nested_attributes_for :parrots, :limit => 2 + + @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") + end + + def teardown + Pirate.accepts_nested_attributes_for :parrots, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } + end + + def test_limit_with_less_records + @pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Big Big Love' } } } + assert_difference('Parrot.count') { @pirate.save! } + end + + def test_limit_with_number_exact_records + @pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Lovely Day' }, 'bar' => { :name => 'Blown Away' } } } + assert_difference('Parrot.count', 2) { @pirate.save! } + end + + def test_limit_with_exceeding_records + assert_raises(ActiveRecord::NestedAttributes::TooManyRecords) do + @pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Lovely Day' }, + 'bar' => { :name => 'Blown Away' }, + 'car' => { :name => 'The Happening' }} } + end + end +end -- cgit v1.2.3