Deleting children with accepts_nested_attributes_for in Rails

Posted in Rails,Ruby by spalladino on the March 15th, 2010

On the previous post I wrote a few lines on the basic usage of the accepts_nested_attributes_for method in rails models. I strongly recommend reading that post before this one if you haven’t.

Although there is a standard way for deleting items, there is few information on how to deal with them on the model or the controllers without messing with the unprocessed parameters. I’ll try to go over some of the different ways for deleting (or rejecting) children.

Do not create at all

Before actually deleting, it is important to consider the case in which you do not want to create the child in the first place. Doing this is easy and handled completely on the model: there is a very useful reject_if option that allows you to specify conditions under which you do not want to process a specific child.

accepts_nested_attributes_for :children
  :reject_if => proc { |att| att['name'].blank? }

The procedure runs for every child, if it evaluates to true, that set of parameters is ignored. Note that this is not the same as a validation: the child will be ignored simply rejected, and will not raise a validation error (children validation and parental control will be left for another post).

Remember that here you must predicate on the raw attributes. The class is not constructed (unless you manually do it in the procedure) so you do not have access to any model fields or instance methods.

This method is useful when you display a text field for the user to complete, which represents the child. If the user does not want to fill that field (name in the example), the child will not be created.

Suppose you have 5 empty text fields for the user to fill his/her hobbies. Should a user have only 3 hobbies, then you want to create only 3 instances of the Hobby class that belong_to that user.

Check _delete

The recommended option for deleting an item is setting a _delete parameter to true in the attributes. This is, if you have a set of parameters like the following:

children_attributes =>
    1 => { id => 16, :name => Jack, :_delete => true }
    2 => { id => 18, :name => Mary }

Then Jack will be removed from the association to his parent. If the association has :dependent => :destroy set, Jack will be completely destroyed.

It is critical not to forget adding the allow_destroy option to the nested attributes method:

accepts_nested_attributes_for :children,
  :reject_if => proc { |att| att['name'].blank? },
  :allow_destroy => true

The rationale behind this feature is that if the user wants to delete an item, he/she must simply check a box named _delete and the controller will forward the parameter to the model, which will remove the child.

If you want to use a different method for deleting (in the hobbies example, delete when the user clears the textbox) you can use javascript to toggle a hidden checkbox whenever a textbox changes, for example. But this is clearly delegating model logic to the view, so a different approach is needed.

Let the controller do it

Since we are messing with the raw params representation of the object, the controller could simply iterate over each of the children_attributes and add a _delete parameter whenever needed, if a certain condition occurs.

However, if we had rejected the previous javascript-based solution for deleting a child since it implies keeping model logic in the view, why should we be happy by moving it to the controller? We have to move another step further.

Mark for destruction

No, we will not hack the attributes= method to manually walk the children_attributes params and add the deletion flag. Since we are in the model, we will use the objects themselves.

The AutosaveAssociation class has a handy method for marking objects for removal which allows you to flag certain objects that should be destroyed when the parent is saved.

Therefore, you can add a callback before the parent is saved that walks through the children and marks for removal those who match a certain condition. And this time, you have the actual children, not some raw-params representation:

before_save :mark_children_for_removal

def mark_children_for_removal
  children.each do |child|
    child.mark_for_destruction if child.name.blank?
  end
end

This allows you to keep all your model logic in your model, where it belongs, and avoid messing with nested parameters in the view or the controller.

Handling children with accepts_nested_attributes_for in Rails

Posted in Rails,Ruby by spalladino on the March 3rd, 2010

Rails makes it easy to build HTML forms associated to a certain model. Simply using the form_for instruction on the view, writing a simple update method in the controller and setting validation logic on the model, makes standard CRUD operations incredibly easy to code.

Since version 2.3, Rails also provides a convenient way of dealing with multi-model forms, specifically parent-children relations. Ryan has an excellent blog post on the subject, I strongly recommend taking a look at it before going on reading if you have never used accepts_nested_attributes_for or fields_for.

To make a long story short, the accepts_nested_attributes_for method applied to a model allows you to assign values directly to the children, using the same hash format as for standard attributes.

An example

For example, having a Parent class which has_many children of class Child, a typical POST with data for both classes would look like the following:

params =>
    action => update
    id => 1
    controller => parents
    parent =>
        first_name => John
        last_name => Doe
        age => 40
        children_attributes =>
            1 => { id => 16, :name => Jack }
            2 => { id => 18, :name => Mary }

(I removed several symbols from the hash to improve readability)

Note the children_attributes element among the parent’s attributes. The fields_for command produces the necessary syntax to produce POST data that will be converted to a hash in the convenient way for accepts_nested_attributes_for to interpret.

The children_attributes is not an array but a hash, and its keys are a simple indexer, not the IDs of the model, which is used to group attributes from a single child entity toghether.

The reason for not using IDs is simple once you think about it: the model being edited might not be saved by the time it is sent to the client (therefore its id is nil), so it is a good practice to keep a hidden field with the id for each of the children being displayed.

On the view

Taking a look at the page source generated, we can see the naming convention rails uses to generate a hash with the previous structure:

parent[children_attributes][0][name]

This naming is automatically generated by opening a new context using the fields_for instruction in the view.

On the model

The change in the model is really simple, as it involves simply telling Rails which classes might be updated directly from the parent:

accepts_nested_attributes_for :children

We will look into additional options (rejecting and deleting) later.

On the controller

Note that all these changes only involve the view (clearly) and a single line in the model to specify which classes may be modified through the attributes accessor. The controller remains as dumb as before:

def update
    if @parent.update_attributes params[:parent]
        redirect_to :action => 'success_page'
    else
        render :action => 'edit'
    end
end

Not even a single mention to the children, the method is untouched.

After that…

Once you start dealing with certain validations and callbacks, keeping in mind when are these children validated created, updated and deleted is extemely important. Deleting items is specially delicate here, and will make a good subject for the next post!