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') 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') 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 From 21e7b8462113fc7c0fd1882dcc2bf7225de67b1a Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Mon, 12 Oct 2009 22:15:43 -0500 Subject: Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks --- .../active_record/associations/association_collection.rb | 2 +- activerecord/lib/active_record/callbacks.rb | 4 ++-- .../active_record/connection_adapters/abstract_adapter.rb | 4 ++-- activerecord/lib/active_record/validations.rb | 14 +++++++------- 4 files changed, 12 insertions(+), 12 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 1b7bf42b91..25e329c0c1 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -476,7 +476,7 @@ module ActiveRecord def callback(method, record) callbacks_for(method).each do |callback| - ActiveSupport::Callbacks::Callback.new(method, callback, record).call(@owner, record) + ActiveSupport::DeprecatedCallbacks::Callback.new(method, callback, record).call(@owner, record) end end diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 40a25811c4..b25893a1c3 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -20,7 +20,7 @@ module ActiveRecord # * (6) after_save # # That's a total of eight callbacks, which gives you immense power to react and prepare for each state in the - # Active Record lifecycle. The sequence for calling Base#save for an existing record is similar, except that each + # Active Record lifecycle. The sequence for calling Base#save for an existing record is similar, except that each # _on_create callback is replaced by the corresponding _on_update callback. # # Examples: @@ -210,7 +210,7 @@ module ActiveRecord # instead of quietly returning +false+. module Callbacks extend ActiveSupport::Concern - include ActiveSupport::NewCallbacks + include ActiveSupport::Callbacks CALLBACKS = [ :after_initialize, :after_find, :before_validation, :after_validation, diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 78c7a4b697..cdf0aebfee 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -32,7 +32,7 @@ module ActiveRecord class AbstractAdapter include Quoting, DatabaseStatements, SchemaStatements include QueryCache - include ActiveSupport::Callbacks + include ActiveSupport::DeprecatedCallbacks define_callbacks :checkout, :checkin @@row_even = true @@ -75,7 +75,7 @@ module ActiveRecord def supports_ddl_transactions? false end - + # Does this adapter support savepoints? PostgreSQL and MySQL do, SQLite # does not. def supports_savepoints? diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index ab79b520a2..e61b253192 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -59,15 +59,15 @@ module ActiveRecord end # Translates an error message in it's default scope (activerecord.errrors.messages). - # Error messages are first looked up in models.MODEL.attributes.ATTRIBUTE.MESSAGE, if it's not there, - # it's looked up in models.MODEL.MESSAGE and if that is not there it returns the translation of the - # default message (e.g. activerecord.errors.messages.MESSAGE). The translated model name, + # Error messages are first looked up in models.MODEL.attributes.ATTRIBUTE.MESSAGE, if it's not there, + # it's looked up in models.MODEL.MESSAGE and if that is not there it returns the translation of the + # default message (e.g. activerecord.errors.messages.MESSAGE). The translated model name, # translated attribute name and the value are available for interpolation. # # When using inheritance in your models, it will check all the inherited models too, but only if the model itself # hasn't been found. Say you have class Admin < User; end and you wanted the translation for the :blank # error +message+ for the title +attribute+, it looks for these translations: - # + # #
    #
  1. activerecord.errors.models.admin.attributes.title.blank
  2. #
  3. activerecord.errors.models.admin.blank
  4. @@ -80,10 +80,10 @@ module ActiveRecord message, options[:default] = options[:default], message if options[:default].is_a?(Symbol) defaults = @base.class.self_and_descendants_from_active_record.map do |klass| - [ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}", + [ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}", :"models.#{klass.name.underscore}.#{message}" ] end - + defaults << options.delete(:default) defaults = defaults.compact.flatten << :"messages.#{message}" @@ -104,7 +104,7 @@ module ActiveRecord module Validations extend ActiveSupport::Concern - include ActiveSupport::Callbacks + include ActiveSupport::DeprecatedCallbacks include ActiveModel::Validations included do -- cgit v1.2.3