Creating nested resources in ruby on rails 3 and updating scaffolding links and redirection

In this article, I’ll walk through a basic Rails (3.2.x) setup for creating a nested resource for two models. Nested resources work well when you want to build out URL structure between two related models, and still maintain a RESTful convention. This code assumes you are running RVM to manage Ruby/Gem versions, and Git for version control.

Creating a new Rails project
$ mkdir family # create rvm gemset
$ echo “rvm use –create ruby-1.9.2@family” > family/.rvmrc
$ cd family # install rails
$ gem install rails # create new rails project
$ rails new . # version control
$ git init
$ git add .
$ git commit -am “new rails project”
Create two models (Parent & Child)
# Parent model
$ rails generate scaffold Parent name:string
$ git add .
$ git commit -am “rails generate scaffold Parent name:string”
# Child model
$ rails generate scaffold Child name:string parent_id:integer
$ git add .
$ git commit -am “rails generate scaffold Child name:string parent_id:integer”
# Create db (defaults to SQLite3)
$ rake db:migrate
# version control
$ git add db/schema.rb
$ git commit db/schema.rb -m “created database schema”
Review un-nested routes
$ rake routes
children GET /children(.:format) children#index
POST /children(.:format) children#create
new_child GET /children/new(.:format) children#new
edit_child GET /children/:id/edit(.:format) children#edit
child GET /children/:id(.:format) children#show
PUT /children/:id(.:format) children#update
DELETE /children/:id(.:format) children#destroy
parents GET /parents(.:format) parents#index
POST /parents(.:format) parents#create
new_parent GET /parents/new(.:format) parents#new
edit_parent GET /parents/:id/edit(.:format) parents#edit
parent GET /parents/:id(.:format) parents#show
PUT /parents/:id(.:format) parents#update
DELETE /parents/:id(.:format) parents#destroy
Adding model relationships
# file: app/models/parent.rb
class Parent < ActiveRecord::Base
attr_accessible :name
has_many :children
end

# file: app/models/child.rb
class Child < ActiveRecord::Base
attr_accessible :name, :parent_id
belongs_to :parent
end

# version control
$ git commit app/models -m "added relationships to models"
Nesting the routes
$ rake routes
parent_children GET /parents/:parent_id/children(.:format) children#index
POST /parents/:parent_id/children(.:format) children#create
new_parent_child GET /parents/:parent_id/children/new(.:format) children#new
edit_parent_child GET /parents/:parent_id/children/:id/edit(.:format) children#edit
parent_child GET /parents/:parent_id/children/:id(.:format) children#show
PUT /parents/:parent_id/children/:id(.:format) children#update
DELETE /parents/:parent_id/children/:id(.:format) children#destroy
parents GET /parents(.:format) parents#index
POST /parents(.:format) parents#create
new_parent GET /parents/new(.:format) parents#new
edit_parent GET /parents/:id/edit(.:format) parents#edit
parent GET /parents/:id(.:format) parents#show
PUT /parents/:id(.:format) parents#update
DELETE /parents/:id(.:format) parents#destroy
Adding test data via Rails console
$ rails c

> dad = Parent.new(:name => ‘Paul’)
=> #

> dad.save
(0.1ms) begin transaction
SQL (20.0ms) INSERT INTO “parents” (“created_at”, “name”, “updated_at”) VALUES (?, ?, ?) [["created_at", Fri, 06 Apr 2012 16:13:17 UTC +00:00], ["name", "Paul"], ["updated_at", Fri, 06 Apr 2012 16:13:17 UTC +00:00]]
(2.4ms) commit transaction
=> true

> son = dad.children.new(:name => ‘Eric’)
=> #

> daughter = dad.children.new(:name => ‘Mara’)
=> #

> exit
Adding a private controller method to load the Parent object for each method
# file: app/controllers/children_controller.rb
@@ -1,4 +1,7 @@
class ChildrenController < ApplicationController
+
+ before_filter :load_parent
+
# GET /children
# GET /children.json
def index
@@ -80,4 +83,11 @@ class ChildrenController < ApplicationController
format.json { head :no_content }
end
end
+
+ private
+
+ def load_parent
+ @parent = Parent.find(params[:parent_id])
+ end
+
end
At this point, each controller and view for the Child class model needs to be adjusted (links, redirection, form, etc)

Method: children#index

# file: app/controllers/children_controller.rb

def index
- @children = Child.all
+ @children = @parent.children.all
# file: app/views/children/index.html.erb

- <%= link_to 'Show', child %>

– <%= link_to 'Edit', edit_child_path(child) %>

– <%= link_to 'Destroy', child, confirm: 'Are you sure?', method: :delete %>

+ <%= link_to 'Show', parent_child_path(@parent, child) %>

+ <%= link_to 'Edit', edit_parent_child_path(@parent, child) %>

+ <%= link_to 'Destroy', [@parent, child], confirm: 'Are you sure?', method: :delete %>

-<%= link_to 'New Child', new_child_path %>
+<%= link_to 'New Child', new_parent_child_path(@parent) %>
Method: children#new

# file: app/controllers/children_controller.rb

def new
– @child = Child.new
+ @child = @parent.children.new
# file: app/views/children/_form.html.erb

-<%= form_for(@child) do |f| %>
+<%= form_for([@parent, @child]) do |f| %>
# file: app/views/children/new.html.erb

-<%= link_to 'Back', children_path %>
+<%= link_to 'Back', parent_children_path(@parent) %>
Method: children#create

# file: app/controllers/children_controller.rb

def create
– @child = Child.new(params[:child])
+ @child = @parent.children.new(params[:child])

respond_to do |format|
if @child.save
– format.html { redirect_to @child, notice: ‘Child was successfully created.’ }
+ format.html { redirect_to [@parent, @child], notice: ‘Child was successfully created.’ }
Method: children#show

# file: app/controllers/children_controller.rb

def show
– @child = Child.find(params[:id])
+ @child = @parent.children.find(params[:id])
# file: app/views/children/show.html.erb

-<%= link_to 'Edit', edit_child_path(@child) %> |
-<%= link_to 'Back', children_path %>
+<%= link_to 'Edit', edit_parent_child_path(@parent, @child) %> |
+<%= link_to 'Back', parent_children_path(@parent) %>
Method: children#edit

# file: app/controllers/children_controller.rb

def edit
– @child = Child.find(params[:id])
+ @child = @parent.children.find(params[:id])
# file: app/views/children/edit.html.erb

-<%= link_to 'Show', @child %> |
-<%= link_to 'Back', children_path %>
+<%= link_to 'Show', parent_child_path(@parent, @child) %> |
+<%= link_to 'Back', parent_children_path(@parent) %>
Method: children#update

# file: app/controllers/children_controller.rb

def update
– @child = Child.find(params[:id])
+ @child = @parent.children.find(params[:id])

respond_to do |format|
if @child.update_attributes(params[:child])
– format.html { redirect_to @child, notice: ‘Child was successfully updated.’ }
+ format.html { redirect_to [@parent, @child], notice: ‘Child was successfully updated.’ }
Method: children#destroy

# file: app/controllers/children_controller.rb

def destroy
– @child = Child.find(params[:id])
+ @child = @parent.children.find(params[:id])
@child.destroy

respond_to do |format|
– format.html { redirect_to children_url }
+ format.html { redirect_to parent_children_path(@parent) }
At this point, the default scaffolding’s links and redirection have been updated to work with the nested routes.