Question about accepts_nested_attributes_for and reject_if

Hi,

I am developing a rails 3.0.3 application and
accepts_nested_attributes_for method is giving me pains.

To simplify the issue, I created a new app and generated 2 models.

users

name: string

cars

user_id: integer
name: string

class User < ActiveRecord::Base
has_many :cars

accepts_nested_attributes_for :cars, :allow_destroy => true,
:reject_if => proc { |attrs| attrs[‘name’].blank? }
end

class Car < ActiveRecord::Base
belongs_to :user
end

Very simple, huh?

In console,

I created a user and create 2 cars for the user.

u = User.first
u.cars_attributes={“0”=>{“id”=>2, “_destroy”=>“1”}}
u.save

This should destroy the car but didn’t.
If I modify the User model like

accepts_nested_attributes_for :cars, :allow_destroy => true

Then, it works as I expect meaning it destroy the car with the same code
in the console.

If I modify the line like the following, it works also.

accepts_nested_attributes_for :cars, :allow_destroy => true, :reject_if
=> proc { |attrs| attrs[‘id’].blank? and attrs[‘name’].blank? }

As I understand it, reject_if option is only for new instance not for
destroyed instance.
Am I wrong?

Sam

On Jan 24, 2011, at 9:44 PM, Sam K. wrote:

end
I created a user and create 2 cars for the user.
Then, it works as I expect meaning it destroy the car with the same code
in the console.

If I modify the line like the following, it works also.

accepts_nested_attributes_for :cars, :allow_destroy => true, :reject_if
=> proc { |attrs| attrs[‘id’].blank? and attrs[‘name’].blank? }

As I understand it, reject_if option is only for new instance not for
destroyed instance.
Am I wrong?

The docs seem to contradict each other. First…

# You may also set a :reject_if proc to silently ignore any new 

record
# hashes if they fail to pass your criteria. For example, the
previous
# example could be rewritten as:

But then…

  #   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.

The code says the second is true… from
active_record/nested_attributes.rb around line 376…

It loops through all the nested attributes…

  • if the ‘id’ is blank and we don’t reject the record, then build it.
  • else we have an ‘id’ so find the record and if we don’t reject it,
    add it to the target and then mark it for destruction.

Unless I’m reading it wrong :slight_smile:

  attributes_collection.each do |attributes|
    attributes = attributes.with_indifferent_access

    if attributes['id'].blank?
      unless reject_new_record?(association_name, attributes)
        association.build(attributes.except(*UNASSIGNABLE_KEYS))
      end

    elsif existing_record = existing_records.detect { |record| 

record.id.to_s == attributes[‘id’].to_s }
association.send(:add_record_to_target_with_callbacks,
existing_record) if !association.loaded? &&
!call_reject_if(association_name, attributes)
assign_to_or_mark_for_destruction(existing_record, attributes,
options[:allow_destroy])

    else
      raise_nested_attributes_record_not_found(association_name, 

attributes[‘id’])
end
end