devroom.io/content/posts/2007-01-23-rails-nested-resource-scaffold.md
2019-06-05 14:32:16 +02:00

101 lines
4.0 KiB
Markdown

+++
date = "2007-01-23"
title = "Rails: Nested resource scaffold"
tags = ["General", "Everything", "Features"]
slug = "rails-nested-resource-scaffold"
description = "Nested resources with Rails' scaffolds"
+++
In my <a href="http://ariejan.net/2007/01/23/new-in-rails-resource-scaffold-generator/">previous post</a> 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.
<!--more-->
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.
<pre lang="bash">$ mkdir cookbook
rails --database sqlite3 cookbook
cd cookbook</pre>
First I create resource scaffolds for both the Recipe and Ingredient models:
<pre lang="bash">$ ./script/generate scaffold_resource Recipe title:string instructions:text
./script/generate scaffold_resource Ingredient name:string quantity:string</pre>
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:
<pre lang="bash">$ rake db:migrate</pre>
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:
<pre lang="ruby">class Recipe < ActiveRecord::Base
has_many :ingredients
end</pre>
app/models/ingredient.rb
</pre><pre lang="ruby">class Ingredient < ActiveRecord::Base
belongs_to :recipe
end</pre>
So far, nothing new. Next we check out config/routes.rb:
<pre lang="ruby">map.resources :ingredients
map.resources :recipes</pre>
What we want is to map ingredients as a resource to recipes. Replace these two lines with:
<pre lang="ruby">map.resources :recipes do |recipes|
recipes.resources :ingredients
end</pre>
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:
<pre lang="ruby">before_filter(:get_recipe)
private
def get_recipe
@recipe = Recipe.find(params[:recipe_id])
end</pre>
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:
<pre lang="ruby">@ingredients = @recipe.ingredients.find(:all)</pre>
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
<pre lang="ruby">ingredient_url(@ingredient)</pre>
and
<pre lang="ruby">ingredient_path(@ingredient)</pre>
to
<pre lang="ruby">ingredient_url(@recipe, @ingredient)</pre>
and
<pre lang="ruby">ingredient_path(@recipe, @ingredient)</pre>
<em>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.
<pre lang="ruby">link_to 'Ingredients', ingredients_path(recipe)</pre>
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:
<pre lang="ruby">def create
@ingredient = @recipe.ingredients.new(params[:ingredient])</pre>
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.</em></pre>