Bestimmte Routen mit :only oder :except auswählen
Wenn Sie nur bestimmte Routen aus dem fertigen Satz von RESTful
Routen benutzen wollen, so können Sie diese mit :only
oder
:except
einschränken.
Folgende
conf/routes.rb
definiert nur die
Routen für
index
und
show
:
Blog::Application.routes.draw do
resources :posts, :only => [:index, :show]
end
Mit
rake routes können wir das Ergebnis
überprüfen:
MacBook:blog xyz$ rake routes
posts GET /posts(.:format) posts#index
post GET /posts/:id(.:format) posts#show
MacBook:blog xyz$
except
arbeitet genau anders herum:
Blog::Application.routes.draw do
resources :posts, :except => [:index, :show]
end
Jetzt sind alle Routen bis auf
index
und
show
möglich:
MacBook:blog xyz$ rake routes
posts POST /posts(.:format) posts#create
new_post GET /posts/new(.:format) posts#new
edit_post GET /posts/:id/edit(.:format) posts#edit
post PUT /posts/:id(.:format) posts#update
DELETE /posts/:id(.:format) posts#destroy
MacBook:blog xyz$
Warnung
Denken Sie bei der Verwendung von only und except bitte daran,
auch die vom Scaffold Generator generierten Views anzupassen. Dort
wird z. B. auf der index-Seite mit <%= link_to 'New Post',
new_post_path %>
auf den new View verlinkt, der im obigen
only-Beispiel dann aber gar nicht mehr existiert.
Verschachtelte Ressourcen (nested resources) beziehen sich auf
Routen von Ressourcen, die mit einer
has_many
-Verknüpfung (siehe
Abschnitt 4.8, „has_many –
1:n-Verknüpfung“) arbeiten. Diese lassen sich
eindeutig über Routen ansprechen. Legen wir eine zweite Ressource
comment
an:
MacBook:blog xyz$ rails generate scaffold comment post_id:integer content
[...]
MacBook:blog xyz$ rake db:migrate
[...]
MacBook:blog xyz$
Jetzt verküpfen wir beide Ressourcen miteinander. In der Datei
app/models/post.rb
fügen wir ein
has_many
hinzu:
class Post < ActiveRecord::Base
attr_accessible :content, :published_at, :subject
has_many :comments
end
Und in der Datei
app/models/comment.rb
das
Gegenstück
belongs_to
:
class Comment < ActiveRecord::Base
attr_accessible :content, :post_id
belongs_to :post
end
Die vom Scaffold Generator erstellten Routen sehen folgendermaßen
aus:
MacBook:blog xyz$ rake routes
comments GET /comments(.:format) comments#index
POST /comments(.:format) comments#create
new_comment GET /comments/new(.:format) comments#new
edit_comment GET /comments/:id/edit(.:format) comments#edit
comment GET /comments/:id(.:format) comments#show
PUT /comments/:id(.:format) comments#update
DELETE /comments/:id(.:format) comments#destroy
posts GET /posts(.:format) posts#index
POST /posts(.:format) posts#create
new_post GET /posts/new(.:format) posts#new
edit_post GET /posts/:id/edit(.:format) posts#edit
post GET /posts/:id(.:format) posts#show
PUT /posts/:id(.:format) posts#update
DELETE /posts/:id(.:format) posts#destroy
MacBook:blog xyz$
Wir können also mit
/posts/1
das erste Post und mit
/comments
alle Comments abfragen. Mithilfe von Nesting können
wir mit
/posts/1/
comments alle Comments des Posts mit der ID
1 abfragen. Dazu müssen wir die
config/routes.rb
abändern:
Blog::Application.routes.draw do
resources :posts do
resources :comments
end
end
Damit bekommen wir dann die gewünschten Routen:
MacBook:blog xyz$ rake routes
post_comments GET /posts/:post_id/comments(.:format) comments#index
POST /posts/:post_id/comments(.:format) comments#create
new_post_comment GET /posts/:post_id/comments/new(.:format) comments#new
edit_post_comment GET /posts/:post_id/comments/:id/edit(.:format) comments#edit
post_comment GET /posts/:post_id/comments/:id(.:format) comments#show
PUT /posts/:post_id/comments/:id(.:format) comments#update
DELETE /posts/:post_id/comments/:id(.:format) comments#destroy
posts GET /posts(.:format) posts#index
POST /posts(.:format) posts#create
new_post GET /posts/new(.:format) posts#new
edit_post GET /posts/:id/edit(.:format) posts#edit
post GET /posts/:id(.:format) posts#show
PUT /posts/:id(.:format) posts#update
DELETE /posts/:id(.:format) posts#destroy
MacBook:blog xyz$
Allerdings müssen wir in
app/controllers/comments_controller.rb
noch ein
paar Änderungen vornehmen. Damit stellen wir sicher, dass immer nur die
Comments
des angegebenen
Posts
angezeigt oder verändert werden (um die Übersicht zu verbessern, habe
ich den JSON-Teil herausgelöscht):
class CommentsController < ApplicationController
before_filter :find_post
def index
@comments = @post.comments
end
def show
@comment = @post.comments.find(params[:id])
end
def new
@comment = @post.comments.build
end
def edit
@comment = @post.comments.find(params[:id])
end
def create
@comment = @post.comments.build(params[:comment])
if @comment.save
redirect_to [@post, @comment], notice: 'Comment was successfully created.'
else
render action: "new"
end
end
def update
@comment = @post.comments.find(params[:id])
if @comment.update_attributes(params[:comment])
redirect_to [@post, @comment], notice: 'Comment was successfully updated.'
else
render action: "edit"
end
end
def destroy
@comment = @post.comments.find(params[:id])
@comment.destroy
redirect_to post_comments_path(@post)
end
private
def find_post
@post = Post.find(params[:post_id])
end
end
Leider ist das nur die halbe Miete, da in den Views noch auf die
alten Routen verwiesen wird. Wir müssen also jeden View entsprechend der
Nested Route anpassen.
app/views/comments/_form.html.erb
Bitte beachten Sie hier, dass der form_for
-Aufruf auf
form_for([@post, @comment])
geändert werden muss.
<%= form_for([@post, @comment]) do |f| %>
<% if @comment.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@comment.errors.count, "error") %> prohibited this comment from being saved:</h2>
<ul>
<% @comment.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :content %><br />
<%= f.text_field :content %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
app/views/comments/edit.html.erb
<h1>Editing comment</h1>
<%= render 'form' %>
app/views/comments/index.html.erb
<h1>Listing comments</h1>
<table>
<tr>
<th>Post</th>
<th>Content</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @comments.each do |comment| %>
<tr>
<td><%= comment.post_id %></td>
<td><%= comment.content %></td>
<td><%= link_to 'Show', [@post, comment] %></td>
<td><%= link_to 'Edit', edit_post_comment_path(@post, comment) %></td>
<td><%= link_to 'Destroy', [@post, comment], confirm: 'Are you sure?', method: :delete %></td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New Comment', new_post_comment_path(@post) %>
app/views/comments/new.html.erb
<h1>New comment</h1>
<%= render 'form' %>
app/views/comments/show.html.erb
<p id="notice"><%= notice %></p>
<p>
<b>Post:</b>
<%= @comment.post_id %>
</p>
<p>
<b>Content:</b>
<%= @comment.content %>
</p>
Bitte spielen Sie einmal mit den unter rake routes aufgeführten
URLs herum. So können Sie jetzt mit /posts/new
einen neuen
Post und mit /posts/:post_id/comments/new
einen neuen Comment
zu diesem Post generieren.
Bemerkungen zu
Nested Resources
Im Allgemeinen sollte man nie tiefer als eine Ebene "nesten" und
Nested Resources sollten sich "natürlich" anfühlen. Sie werden mit der
Zeit ein Gefühl dafür bekommen. Meiner Meinung nach ist das Wichtigste
an RESTful Routen, dass sie sich logisch anfühlen müssen. Wenn Sie mit
einem befreundeten Rails-Programmierer telefonieren und ihm sagen "Ich
habe da eine Resource Post und eine Resource Comment", dann sollte
beiden Seiten direkt klar sein, wie man diese Resourcen per REST
anspricht und wie man sie verschachteln kann.