Railsのリソースネスト


ネストされたリソース
[size=medium][/size]
ネストされたリソースを適用すると、RESTの開発がより面白くなります.この章では、簡潔なURLの重要性をより理解し、RESTの理念をより明確に理解することができます.
ネストされたリソース、つまり親-子関係というリソースです.Railsではmodelの関係である1対多の関係である.私たちのこのontrackの例プロジェクトでは、projectsとiterationsの関係のようです.ネストされたREST controllerは、リソースを処理する操作を担当しますが、サブcontrollerの場合、親リソースの情報も取得する必要があります.
複雑に聞こえますが、この章を読むと、すぐに完全にわかります.
RailsのREST方式によれば、Railsはリソースのこのような主な関係をURLに反映し、URLの簡潔さという重要な特性を維持している.このontrack例では,この点を2つのリソースprojectとiterationで説明する.
まず、iterationというリソースを作成し、iterationsというテーブルを作成します.
> ruby script/generate scaffold_resource iteration name:string \
start:date end:date project_id:integer
> rake db:migrate

ProjectsとIterationsは「1対多」の関係なのでmodelを修正します.
Listing 1.4: ontrack/app/models/project.rb
class Project < ActiveRecord::Base
has_many :iterations
end

Listing 1.5: ontrack/app/models/iteration.rb
class Iteration < ActiveRecord::Base
belongs_to :project
end

モデル、controller、viewを作成したほか、ジェネレータはconfig/routesにある.rbでは、ルーティングの定義項目が作成されます.
map.resources :iterations

このルーティング項目はリソースプロジェクトと非常に似ていますが、iterationとprojectの関係を忘れないでください.しかし,このルーティング項目はこの点を考慮していないことは明らかである.例えばnew_iteration_pathメソッドはURL「/iterations/new」を生成し、このiterationがどのプロジェクトに属するべきかという重要な情報は含まれていません.だから、「親」リソースがなければ、「子」リソースには何の意味もないことに気づくべきです.
Railsはこのようなマスター-スレーブの関係をURLに反映するので、デフォルトで生成されたルーティング項目を変更する必要があります.
map.resources :projects do |projects|
projects.resources :iterations
end

このルーティング項目はネストされたリソースになり、iterationというリソースを操作するにはprojectというリソースに基づいていなければなりません.これに対応するURLは次のようになります.
/project/:project_id/iterations
/project/:project_id/iterations/:id

例えば、このURLを入力したら
http://localhost:3000/projects/1/iterations

IterationControllerのindexメソッドが呼び出されます.このメソッドでは、コミットされたパラメータ:project_を使用することもできます.idはリソースプロジェクトを得る.URLがリソースに関連付けられているという特性は、実際には次の関係に等しいことに注意してください.
/projects/1/iterations <=> Project.find(1).iterations

ネストされたURLは依然として簡潔なURLである――URLにはactionではなくリソースのみが表示されている.簡単に言えば、1つのリソースが2つのRESTスタイルのURLで構成されている場合、それはネストされたリソースである.以下のshow actionを呼び出すURLは、この点を明確に理解することができます.
http://localhost:3000/projects/1/iterations/1

ネストされたリソースのコントロールのコード
新しく生成されたIterationControllerは、ネストされたリソースを処理する必要があることを知りません.これは、各メソッドが少なくとも「親」リソースプロジェクトを得るべきであることを意味します.したがって、現在のindexメソッドは、URLがプロジェクトの下のすべてのiterationsを表示すべきであることを示しているにもかかわらず、すべてのiterationsを表示します.
Listing 1.6: ontrack/app/controllers/iterations controller.rb
def index
@iterations = Iteration.find(:all)
respond_to do |format|
format.html # index.rhtml
format.xml { render :xml => @iterations.to_xml }
end
end

プロジェクトの下のiterationsだけを持つことを保証するためにindexメソッドを書き直さなければなりません.
Listing 1.7: ontrack/app/controllers/iterations controller.rb
def index
project = Project.find(params[:project_id])
@iterations = project.iterations.find(:all)
...
end
コントロールのすべての方法を/projects/:project_で動作させる必要があります.idは接頭辞のURLにあります.これは,indexメソッド,create,updateなどのメソッドを修正するだけでなく,修正しなければならないことを意味する.次の章では、徐々に紹介します.
「path」と「url」helperメソッドでパラメータを使用する
config/routes.rbに新たに追加されたリソースは,新しいルーティングの定義を追加するだけでなく,自動的に新しいhelperメソッドを追加する.定義されたルーティングのように、新しいhelperメソッドにはパラメータとしてproject-idが必要です.例えば、「iterations_path」というhelperメソッドにより、あるプロジェクトの下のすべてのiterationsが得られる.Helperメソッドの名前はネストされた名前ではなく,伝達されるパラメータが異なるだけである.ネストされたリソースの場合、「サブ」リソースのhelperメソッドのパラメータは、通常、「親」リソースのリソースidであり、この例ではprojectのidである.
次に例として、プロジェクトの下のすべてのiterationsを表示できるリンクを作成します.
link_to "Iterations", iterations_path(project)
=>
Iterations

ここでiterations_pathのパラメータ「project」はリソースのオブジェクトです.この方法の役割をよりよく理解するために、Listing 1.8:ontrack/app/viewsを1つのページに配置してみましょう.
/projects/index.rhtml
...
<% for project in @projects %>

<%=h project.name %>
<%=h project.desc %>
<%= link_to "Iterations", iterations_path(project) %>
<%= link_to "Show", project_path(project) %>
<%= link_to "Edit", edit_project_path(project) %>
<%= link_to "Destroy", project_path(project),
:confirm => "Are you sure?", :method => :delete %>

<% end %>

...
ではiterationsに伝えたらパスエラーのパラメータはどうなりますか?それはすべての機能が実効性があり、ページの表示も正常ではありません.たとえば、次の現実のすべてのiterationsのページです.
Listing 1.9: ontrack/app/views/iterations/index.rhtml
...
<% for iteration in @iterations %>

<%=h iteration.name %>
<%=h iteration.start %>
<%=h iteration.end %>
<%= link_to "Show", iteration_path(iteration) %>
<%= link_to "Edit", edit_iteration_path(iteration) %>
<%= link_to "Destroy", iteration_path(iteration),
:confirm => "Are you sure?", :method => :delete %>

<% end %>

...
最初のパラメータは現在iterationオブジェクトであることがわかります.これはすべての方法を失効させた--原因は明らかだ/config/routes.rbでは、iteration idではなくproject idを定義します.このページを正常に表示するには、次のような変更が必要です.
Listing 1.10: ontrack/app/views/projects/index.rhtml
...
<% for iteration in @iterations %>

<%=h iteration.name %>
<%=h iteration.start %>
<%=h iteration.end %>
<%= link_to "Show", iteration_path(iteration.project,
iteration) %>
<%= link_to "Edit", edit_iteration_path(iteration.project,
iteration) %>
20 1 RESTful Rails
<%= link_to "Destroy", iteration_path(iteration.project,
iteration), :confirm => "Are you sure?",
:method => :delete %>

<% end %>
...

パラメータの順序を正しくするには、指定したパラメータを別の方法で表示することもできます.
iteration_path(:project_id => iteration.project, :id => iteration)
オブジェクトをパラメータとして使うのがはっきりしていないと思ったら、この方法を考えてみてください.
新しい情報を追加
私たちは依然として現在の例でこの機能を追加しています.この機能を実現するには、ProjectControllerのindexを簡単に修正する必要があります.rhtml :
Listing 1.11: ontrack/app/views/projects/index.rhtml
...
<% for project in @projects %>

<%=h project.name %>
<%=h project.desc %>
<%= link_to "Iterations", iterations_path(project) %>
<%= link_to "Show", project_path(project) %>
<%= link_to "Edit", edit_project_path(project) %>
<%= link_to "Destroy", project_path(project),
:confirm => "Are you sure?", :method => :delete %>
<%= link_to "New Iteration", new_iteration_path(project)
%>

<% end %>
...

ここでは「new_iteration_path」というhelperメソッドを用い,projectというオブジェクトをパラメータとして渡した.このhelperメソッドは、次のhtml文を生成します.
link_to "New Iteration", new_iteration_path(project)
=>
New iteration

このリンクをクリックすると、IterationControllerのnewメソッドが呼び出され、このメソッドではproject id(この例では「1」)が得られます.これにより、新しいiterationを作成するためのformは、このproject idを使用することができます.
Listing 1.12: ontrack/app/views/iterations/new.rhtml
<% form_for(:iteration,
:url => iterations_path(params[:project_id])) do |f| %>
...
<% end %>
=>

この「params[:project_id]」は実は省略できますが、Railsはこの変数を自動的に処理します.つまり、上のコードは、下のコードと等価です.form_for(:iteration,:url=>iterations_path)私たちは以前/config/routesにいたからです.rbにルーティングが定義されているので、post方式で「/projects/1/iterations」リンクをコミットすると、必ずIterationControllerのcreateメソッドが呼び出されます.
次に、IterationControllerのcreateメソッドを変更して、作成したiterationがプロジェクトに基づいていることを保証します.
Listing 1.13: ontrack/app/controllers/iterations controller.rb
1 def create
2 @iteration = Iteration.new(params[:iteration])
3 @iteration.project = Project.find(params[:project_id])
4
5 respond_to do |format|
6 if @iteration.save
7 flash[:notice] = "Iteration was successfully created."
8 format.html { redirect_to iteration_url(@iteration.project,
9 @iteration) }
10 format.xml { head :created, :location =>
11 iteration_url(@iteration.project, @iteration) }
12 else
13 format.html { render :action => "new" }
14 format.xml { render :xml => @iteration.errors.to_xml }
15 end
16 end
17 end

「3」行目では「project_id」というパラメータを使用し、「8」行目と「11」行目では「url」helperメソッドを使用しました.
次に、iterationとprojectを関連付けなければならないため、iterationのリンクを編集する表示を変更する必要があります.
Listing 1.14: ontrack/app/views/iterations/show.rhtml
...
<%= link_to "Edit", edit_iteration_path(@iteration.project, @iteration)
%>
<%= link_to "Back", iterations_path(@iteration.project) %>

編集
iterationを編集できるようにするには、少なくとも2つの場所を修正する必要があります.1〉ビュー内のform_forメソッドのパラメータは,現在のパラメータはiterationの1つのみであり,projectidを追加する必要がある.
form_for(:iteration,
:url => iteration_path(@iteration),
:html => { :method => :put }) do |f|

次のように変更する必要があります.
form_for(:iteration,
:url => iteration_path(params[:project_id], @iteration),
:html => { :method => :put }) do |f|

updateメソッドを変更する必要があります.変更の目的は同じです.
Listing 1.15: ontrack/app/controllers/iterations controller.rb
1 def update
2 @iteration = Iteration.find(params[:id])
3
4 respond_to do |format|
5 if @iteration.update_attributes(params[:iteration])
6 flash[:notice] = "Iteration was successfully updated."
7 format.html { redirect_to iteration_url(@iteration) }
8 format.xml { head :ok }
9 else
10 format.html { render :action => "edit" }
11 format.xml { render :xml => @iteration.errors.to_xml }
12 end
13 end
14 end

7行目は次のように変更する必要があります.
format.html { redirect_to iteration_url(@iteration.project,@iteration) }

これまで、新しく追加されたリソースIterationの操作の大部分は正常に動作していましたが、まだいくつかの詳細が処理されていません.次回はアクションをカスタマイズする方法についてお話しします.
--women and men(
http://www.hezubbs.cn/html/ruby/200901/yingyongRailsjinxingRESTkaifa-wu-_366.html )