How to check if object can be destroyed if it has dependent: restrict associations
Rails provides several handy options for specifying how to deal with associated models upon deletion, for example:
class Blog has_many :posts, :dependent => :destroy end
The destroy value for the dependent option will call the destroy method for every post in the blog when the blog itself is destroyed. Other options are delete or nullify, but the one we are interested in is restrict.
By specifying a relation as dependent restrict, Rails will prevent us from destroying a particular object if it has any associated objects. In the example, we would not be able to destroy a Blog if it has any Posts. It is implemented simply, by raising an ActiveRecord::DeleteRestrictionError if there is any associated object.
Now, this works perfectly for preventing us from accidentally destroying an object, but we will usually want to check if we can destroy it beforehand, this is, when rendering a page to the client with a big bad delete button. Showing a delete button only to show an alert box with a "Could not delete" annoying message is certainly not a good practice, we should simply not draw the delete button in the first place.
How do we check this? We could manually check if each and every one of the associations we have marked with dependent restrict in the object are empty, but taking Rails DRY principle into account, we would like to automatically get that information from the object.
Luckily, ActiveRecord provides reflection methods for obtaining info on the associations. Therefore, given an object, we can iterate its associations, and check if the restricted ones are empty or not.
This all boils down to this small method, which can be placed as an initializer in the Rails app:
class ActiveRecord::Base
def can_destroy?
self.class.reflect_on_all_associations.all? do |assoc|
assoc.options[:dependent] != :restrict ||
(assoc.macro == :has_one && self.send(assoc.name).nil?) ||
(assoc.macro == :has_many && self.send(assoc.name).empty?)
end
end
end
That's it! Now you can simply make a small helper method that renders a destroy link if can_destroy?, or a plain span notifying the user why she cannot destroy the object.
Default request parameters in Rails functional tests
I was looking for an easy way to force every request in a functional test in Rails to use a set of parameters by default, regardless of being specified explicitly. This is, every time I write:
it "should get index" do get :index end
Rails should actually do:
it "should get index" do get :index, :foo => 'value' end
Lacking any option in the testing framework, I opted for simply monkeypatching the process method in ActionController::TestCase::Behaviour. This method is invoked whenever methods get, post, put, delete or head are called, so it is the easiest single point to modify.
This gist has the necessary code to perform the patch. If you are using rspec, simply copy the file in the spec/support folder.
Deleting children with accepts_nested_attributes_for in Rails
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
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!