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, edit file: config/routes.rb
- resources :children
- resources :parents
+ resources :parents do
+ resources :children
+ end
Version control
$ git commit -m config/routes.rb "nested resources in routes file"
Reviewing changes to 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' )
=> #<Parent id: nil, name: "Paul", created_at: nil, updated_at: nil>
> 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' )
=> #<Child id: nil, name: "Eric", parent_id: 1, created_at: nil, updated_at: nil>
> daughter = dad.children.new( :name => 'Mara' )
=> #<Child id: nil, name: "Mara", parent_id: 1, created_at: nil, updated_at: nil>
> 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
Update view. file: app/views/children/index.html.erb
- <td><%= link_to 'Show', child %></td>
- <td><%= link_to 'Edit', edit_child_path(child) %></td>
- <td><%= link_to 'Destroy', child, confirm: 'Are you sure?', method: :delete %></td>
+ <td><%= link_to 'Show', parent_child_path(@parent, child) %></td>
+ <td><%= link_to 'Edit', edit_parent_child_path(@parent, child) %></td>
+ <td><%= link_to 'Destroy', [@parent, child], confirm: 'Are you sure?', method: :delete %></td>
-<%= 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
Update view: file: app/views/children/_form.html.erb
-<%= form_for(@child) do |f| %>
+<%= form_for([@parent, @child]) do |f| %>
Update view: 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])
Update view: 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])
Update view: 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.