ちょうどタイミング良く@maihaさんから、@yuguiさんが
近くに来てるという情報をもらったので、
@yamazさんも交えて、ネストしたリソースを扱うコントローラの問題の答えを得るべく、ミーティングをしました。

問題の定義:

モデル層で Post has_many Comment な関係にある時に、
Commentのリストとコメント投稿フォームを含むPosts#show画面(典型的な例としてはブログの一記事表示画面)から、Commentを投稿した場合に、

  1. Comments#createで受け取ると、Commentのsaveに失敗した時に、Post#showを表示したいが、redirect resource(@comment.post) すると、@comment.errorsの情報がロストしてしまう。
  2. Posts#create_commentなどで受け取ると、Commentリソースの処理をPostsコントローラで行う事になって責任の範囲が不明確になり、格好が悪い。

解決策

Merbベースでコンセプトを示します。
まずは、Postsコントローラの中で、以下のような包含関係を宣言するようにします。

ruby>>
class Posts < Application
has_many :comments
end

class Admin < Application
has_many :comments
end
<<--

これにより、コントローラ同士の協調関係を、コントローラ自身が知っているという事になります。
具体的にhas_manyがやることは、以下のようなbeforeフィルターをshowアクションに対してセットする事です。

ruby>>
class Posts < Application
before :only => :show do
controller = Comment.new(request)
@comment = case request.method
when "POST"; controller._dispatch(:create)
when "PUT"; controller._dispatch(:update)
when "DELETE"; controller._dispatch(:destroy)
end
end
<<--

:showアクションに対して、本来は使われない"POST", "PUT", "DELETE"
の各メソッドでのリクエストがあった場合に、Commentsコントローラに処理を回します。"GET"の場合は普通にPosts#showが行われます。
Commentsコントローラ側では、メソッドの返り値としてcommentオブジェクトを返します。

ruby>>
def create(comment)
Comment.create(comment)
end
<<--

作成に失敗した場合は、@comment.errorsにエラー情報が入っているので、
Posts#showの中から利用出来ます。

Posts#show内のComment投稿フォームは以下のような感じで、
Post#showに対してサブミットします。

html>>
<%= form_for @comment, :action => resource(@post) do %>
<%= partial "comments/form" %>
<% end =%>
<<--

コンセプトなので実際に動くかどうかまだ検証してないですが、
こんな感じで良いのではないかと。

MerbだとControllerがresponseの生成を担当していないので、
コントローラをまたいだ処理のネストが高速に行えます。
Railsの場合は、個々のアクションの実行がresponseの生成を伴うので、
この方法だとオーバヘッドが大きくて難しいかもしれません。

posted by genki genki on Tue 20 Jan 2009 at 02:03 with 0 comments