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.
$ 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"
# 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"
$ 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
# 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"
$ 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
$ 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
# 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
- <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
# 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.
Rails noob and had been trying to figure this out for hours. Your tutorial helped me tremendously. Thanks.