Last updated

Rails: Nested resource scaffold

In my previous post I told you about the resource scaffold. What you’ll be doing a lot is nesting these resources. Ingredients in recipes, comments on posts, options for products. You name it, you nest it!

Since Rails does not automatically nest resources for you, you should do this yourself. This is, with some minor tweaks, really easy to accomplish. In this example I’ll create recipes that have multiple ingredients.

I assume you have Rails 1.2.1 installed for this tutorial to work properly.

First, I create an new rails project named ‘cookbook’. I use an SQLite3 database because it’s easy to do so. You may use any Rails compatible database for this example.

1$ mkdir cookbook
2rails --database sqlite3 cookbook
3cd cookbook

First I create resource scaffolds for both the Recipe and Ingredient models:

1$ ./script/generate scaffold_resource Recipe title:string instructions:text
2./script/generate scaffold_resource Ingredient name:string quantity:string

As you can see I did not add a recipe_id to the ingredient model because of the has_many relationship. Add this column to the migration file. You should now be able to migrate your database:

1$ rake db:migrate

If you add the recipe_id to the generate script the view for your ingredients will include a field for the recipe_id and that’s not what you want.

Next, make the has_many relationship in your models.

app/models/recipe.rb:

1class Recipe < ActiveRecord::Base
2  has_many :ingredients
3end

app/models/ingredient.rb```ruby class Ingredient < ActiveRecord::Base belongs_to :recipe end

1
2So far, nothing new. Next we check out config/routes.rb:
3```ruby
4map.resources :ingredients
5map.resources :recipes

What we want is to map ingredients as a resource to recipes. Replace these two lines with:

1map.resources :recipes do |recipes|
2	recipes.resources :ingredients
3end

This will give you urls like /recipes/123/ingredients/321

Now we need to make some changes to the ingredients controller. Every ingredient belongs to a recipe. First add the filter:

1before_filter(:get_recipe)
2
3private
4def get_recipe
5	@recipe = Recipe.find(params[:recipe_id])
6end

This will make sure that every ingredient knows what recipe it belongs to.

In the index method of the ingredient controller, make sure you have this:

1@ingredients = @recipe.ingredients.find(:all)

This makes sure you only show ingredients for this recipe, and not all ingredients in the database.

Because we changed the route for ingredients, we need to update all ingredient_url() and ingredient_path() calls in our controller and views. Change all occurrences of

1ingredient_url(@ingredient)

and

1ingredient_path(@ingredient)

to

1ingredient_url(@recipe, @ingredient)

and

1ingredient_path(@recipe, @ingredient)

Note: Make sure that you don’t replace ‘ingredient’ with ‘@ingredient’ in your views!

Add a link to the ingredients to your recipe’s index.rhtml view.

1link_to 'Ingredients', ingredients_path(recipe)

And, at last, make sure the create method of your ingredients controller attaches the ingredient to the right recipe. Make sure the first two lines look like this:

1def create
2  @ingredient = @recipe.ingredients.new(params[:ingredient])

You may now start your webserver and check out http://localhost:8000/recipes . Create some recipes and click ‘ingredients’. You can now add ingredients for this recipe!

The next step will be customizing each method to suit your own needs.