Forms With Nested Resources In Rails

This post is more of an informational post for myself in case I forget this later on. I have been working on a client project thats using Ruby on Rails and I was having a little trouble getting my head around nested resources and the routing that goes along with that. Once I had the routing figured out, the next thing to address was handling my forms. Now, as with all things in programming, there is more than one way to “skin a cat” when it comes to this in Rails but for the most part, this is from what I can tell, the preferred method and I like it better than the alternative.
To start off with, nested resources is simply a way to have much cleaner urls without having to append a query string to the url. So you can have something like:

mysite.com/category/1/products/3

instead of:


mysite.com/category/1?product=3

Of course the nice URL’s are not the only reason for doing this. When it comes to handling forms, Rails can handle all of the associations for you reducing the amount of code you are writing.
So, borrowing form the earlier example of products and categories, we can easily setup our form to handle the relationship by modifying our form_for arguments in the form like this:
form_for [@category, @product] do |f|
Now we no longer have to manually add a hidden field containing the category id.
In our controller, several methods are affected by this change, but lucky for us the changes are simple.
First, lets look at the new method which sets up the blank form:


def new
@category = Category.find_by_id(params[:user_id])
@product = @category.build_product
respond_to do |format|
format.html
end
end

Since we are referencing the product through the category, its best to use the category itself to build the new product object. This allows the previous change that we made to the form to work. Now lets have a look at the create method that will actually save our form:


def create
@category = Category.find_by_id(params[:user_id])
@product = @category.build_product(params[:product])
respond_to do |format|
if @product.save
flash[:notice] = 'Product was successfully created.'
format.html { redirect_to edit_category_product_path(@category,@product) }
else
format.html { render :action => "new" }
end
end
end

Notice again that we are building the product object through the category association and passing the product data to it from the form to populate it. Thats pretty much all of the magic sauce. There is one other thing to notice in that method that warrants attention. Notice the redirect_to method? Normally that would look something like this:


redirect_to edit_product_path(@product)

But since we are accessing products as a nested resource of categories, we simply add in the category part to the method and supply the category instance variable to the argument list. This will ensure we are redirected to the proper url.
Now I know this is a dumbed down version of this, but hey, like I said earlier, its more for my reference later on. Hopefully someone can get something out of it as well.
 
 

3 thoughts on “Forms With Nested Resources In Rails”

  1. Hi Russ,

    Thanks for such a nice post. My situation is a bit different.

    I built an application that used nested attributes which took in Sample model details within a Batch model. I was extending my project and required Experiment model to be a child nested to the Sample model, so that when ever a user fills/updates some sample details, an experiment attribute would be created pertaining that sample.

    The over all senario would be:

    Batches has_many samples,
    Samples has many experiments.

    But when I had created samples in the first place within the Batch model I didnt realize how I would go about with the Experiment model. So when I am adding details to the ‘existing’ sample attributes i would like to create new experiment attributes for the existing samples.

    Please could you suggest me on to how to update an existing model and simultaneously create a new model attribute using nested resources from the same form which could be stored in the database.

    Cheers

    A1aks

  2. Thanks so much for this Russ, just spent the last hour trying to find a solution to why my form_for wasn’t saving the ID of my parent resource. Here’s my solution (Rails 4.1) for adding a task to a goal:

    Routes.rb:

    resources :goals do
    resources :tasks
    end

    The view for creating a new task (new.html.erb):

    “form-control”, autofocus: true) %>

    “button”) %>

    Tasks controller:

    def new
    @goal = Goal.find(params[:goal_id])
    @task = @goal.tasks.build
    end

    def create
    # Get the ID of the goal that the task belongs to
    @goal = Goal.find(params[:goal_id])

    @task = @goal.tasks.build(task_params)
    # Save the object
    if @task.save
    # If save succeeds, redirect to the index action
    flash[:notice] = “Task created successfully.”
    redirect_to(goals_path)
    else
    # If save fails, redisplay the form so user can fix problems
    render(‘new’)
    end
    end

    The trick was to explicitly build your tasks with your goal in the controller. Hope this helps someone!

  3. My pasted view code got cut off there. Hopefully this will fix it.

    “form-control”, autofocus: true) %>

    “button”) %>

Leave a Reply

Your email address will not be published. Required fields are marked *


*