Rails 2.0 で map.resource のネスト
Rails 2.0 で簡単なサンプルアプリを作っている。 作りたいのはよくある ToDo リストなんだけど、「複数のユーザで使える」ことが前提。 これが思ったより苦戦した。
まず、普通に scaffold で作ったときの URL はこうなる。
http://example.com/todos/ ← すべての ToDo リスト http://example.com/todos/new ← 新しい ToDo を作成 http://example.com/todos/1/ ← id:1の ToDo を表示 http://example.com/todos/1/edit ← id:1の ToDo を編集
でもこれじゃ、ユーザごとに分かれていない。 かといって、
http://example.com/todos/?user_id=1
とするのも格好悪い。 以下のような URL にしたいんだよね。
http://example.com/users/1/todos/ ← ユーザ1のすべての ToDo リスト http://example.com/users/1/todos/new ← ユーザ1の新しい ToDo を作成 http://example.com/users/1/todos/3 ← ユーザ1のid:3の ToDo を表示 http://example.com/users/1/todos/3/edit ← ユーザ1のid:3の ToDo を編集
RESTful 本を見ていたら、 routes.rb で path_prefix を使う方法が書かれていた(P.189, 7.3.11 実装: route.rb ファイル)。 時期的にたぶん Rails 1.2 かな。
user_base = base + '/users/:username' map.resources :bookmarks, :path_prefix => user_base
それでやってみたんだけど、どうも view 周りでエラーがでる。
Showing todos/index.html.erb where line #16 raised: todo_url failed to generate from {:controller=>"todos", :user_id=>#<Todo id: 4, user_id: 1, body: "テスト", status: "テスト", created_at: "2008-01-04 16:37:36", updated_at: "2008-01-04 16:38:38">, :action=>"show"}, expected: {:controller=>"todos", :action=>"show"} Extracted source (around line #16): 16: <td><%= link_to 'Show', todo %></td>
エラーが出ている箇所で、以下のように user_id と id を指定すれば、一応動くようになるけど、これは面倒。
<td><%= link_to 'Show', todo_path(:user_id => todo.user_id, :id => todo.id) %></td>
map.resource でのネスト
Module: ActionController::Resources を見てお勉強。 そうすると、 routes.rb を以下のように書けることが分かった。
map.resources :users do |user| user.resources :todos end
これで、 /users/1/todos/ という書き方ができる。 でも、やっぱり view でエラーが起きる。 URL を生成するヘルパーである todo_path を user_todo_path に変えないとダメみたい。
RESTful 本を参考にしつつ、以下のようにした。
app/controllers/todos_controller.rb
フィルタで @user にオブジェクトを格納しておく。
before_filter :must_user_present def must_user_present @user = User.find(params[:user_id]) end
create では @todo.save の代わりに @user.todos << @todo を使う。 @todo.save のままだとリクエストパラメータを詐称して違う user_id に差し替えることができちゃうから。 それから、生成後のリダイレクト先で user_todo_url を使うように書き換え。 これは destroy でも同じ。
def create @todo = Todo.new(params[:todo]) respond_to do |format| if @user.todos << @todo flash[:notice] = 'Todo was successfully created.' format.html { redirect_to(user_todo_url(@user, @todo)) } format.xml { render :xml => @todo, :status => :created, :location => user_todo_url(@user, @todo) } else format.html { render :action => "new" } format.xml { render :xml => @todo.errors, :status => :unprocessable_entity } end end end
app/views/todo/*.erb
- link_to で以下のヘルパを使うように書き換え
- index: user_todos_path(@user)
- show: user_todo_path(@user, @todo)
- edit: edit_user_todo_path(@user, @todo)
- form_for で、以下のように url 属性を追加する
form_for(@todo, :url => user_todo_path(@user, @todo))
これで動くようになった。 scaffold でここまで genarate してくれると楽だなぁ。 もっと正しい使い方があるのかも?