質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.12%

Reviewを投稿できない

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 1,513

hrc

score 50

レストランクチコミサイトを作ろうとしています。
レストランページからReviewを投稿できるようにしたいのですが
undefined local variable or method `review_create_path'  とエラーが出て投稿できません。

以下ViewとRoute定義とControllerですが、何が問題なのでしょうか?

/app/views/restaurant/show.html.slim

= form_for :post, url: review_create_path do |f|
    = f.label :title, "title"
    = f.text_field :title
    = f.label :comment, "comment"
    = f.text_area :comment
    = f.submit

route.rbはこちらです。

# Locale Information
scope "(:locale)", locale: /en|ja/ do
   get '/' => 'frontpage#index'
   get 'restaurant/' => 'restaurant#index'
   get 'restaurant/:id' => 'restaurant#show'
   get 'menu/' => 'menu#index'
   get 'menu/:id' => 'menu#show'
   get 'area/' => 'area#index'
   get 'area/:id' => 'area#show'

   resources(:review)
   post 'review/' => 'review#create', as: :review_create_path

   devise_for :users, skip: [:omniauth_callbacks]

end

rake routesの結果はこうでした。

review_index GET      (/:locale)/review(.:format)              review#index {:locale=>/en|ja/}
                         POST     (/:locale)/review(.:format)              review#create {:locale=>/en|ja/}
              new_review GET      (/:locale)/review/new(.:format)          review#new {:locale=>/en|ja/}
             edit_review GET      (/:locale)/review/:id/edit(.:format)     review#edit {:locale=>/en|ja/}
                  review GET      (/:locale)/review/:id(.:format)          review#show {:locale=>/en|ja/}
                         PATCH    (/:locale)/review/:id(.:format)          review#update {:locale=>/en|ja/}
                         PUT      (/:locale)/review/:id(.:format)          review#update {:locale=>/en|ja/}
                         DELETE   (/:locale)/review/:id(.:format)          review#destroy {:locale=>/en|ja/}
      review_create_path POST     (/:locale)/review(.:format)              review#create {:locale=>/en|ja/}

こちらはコントローラーです。

class ReviewController < ApplicationController
  def create
    @review = Review.new(review_params)
    respond_to do |format|
      if @review.save
        format.html { redirect_to @review, notice: 'Review was successfully created.' }
        format.json { render :show, status: :created, location: @review }
      else
        format.html { render :new }
        format.json { render json: @review.errors, status: :unprocessable_entity }
      end
    end
  end

  def review_params
    params.require(:review).permit(:title, :comment)
  end
end

初歩的ですみませんが、どうかよろしくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

routes.rbの記述が間違ってます。

そもそも、resources :reviewで指定しているので、createアクションはすでに定義されています。
提示いただいた以下の部分は、rake routesの結果かと思いますが、2行目にばっちり書かれています。

review_index GET  (/:locale)/review(.:format) review#index {:locale=>/en|ja/}
             POST (/:locale)/review(.:format) review#create {:locale=>/en|ja/}
# 以下略


呼び出し側では、

<%= form_for :post, review_path, method: :post do %>


というように呼び出します。(すみません、slim苦手です)

ちなみに、review_create_pathでundefined errorが出るのは、asオプションの指定が間違っているからです。

post 'review/' => 'review#create', as: :review_create_path
# これだと、review_create_pathというメソッドが割り当てられていて、
# helperから呼び出すときは、review_create_path_pathと呼び出す事になる

# どうしてもこの形式にこだわるのであれば、以下のようにします。
resources :review, except: [:create]
# パス指定が重複するので、通常のresourcesでreview POSTのパスが生成されないようにする
post 'review/' => 'review#create', as: :review_create
# これで、ヘルパでreview_create_path method: :postで無事review#createが呼び出される

特別な理由がない限り、デフォルトのresourcesで生成されるパスを利用される事を勧めます。
デフォルトで生成されるパスは、RESTfulなパスですから、出来るだけそれに従う方がわかりやすいと思います。

routesの設定に関しては、Reilsガイドに詳しく乗っていますから、こちらを参考にされると良いでしょう。

コメントでのやりとりで、ネストしたリソースでパスを生成する方法の話題で、以下の返答を頂いたので、ちょっと追記します。

URLは/restaurant/(id)のままで当該レストランに対するReviewを投稿できるようにしたい

一つのリソース(Restaurant)にReviewを含むその他のリソースを操作するパスは、もちろん作成可能なのですが、この場合、どんどんRestaurantControllerが肥大化してゆく問題があります。
また、リソース(Model)と操作(Controller)が直結していないと、その処理がどこで行われているのかわかりにくくなる可能性もあります。
ただ、他にもいろいろな要件が絡んでくるので、どちらが正解ともいえないので、ケースバイケースで使い分ける事になるかと思います。

# リソースをネストさせない場合

# routes.rb

resources :restaurant do
  member do
    post 'create_review'
    patch 'update_review'
    delete 'destroy_review'
  end
end
# この状態なら、以下のパスが生成されるはず
# restaurantのCRUDは省略
#  create_review_restaurant POST   /restaurant/:id/create_review   restaurant#create_review
#  update_review_restaurant PATCH  /restaurant/:id/update_review   restaurant#update_review
#  delete_review_restaurant DELETE /restaurant/:id/destroy_review  restaurant#destroy_review

# ヘルパを使って呼び出すときはこんな感じ
<%= form_for :review, create_review_restaurant(@restaurant) do %><% end %>
<%= form_for :review, update_review_restaurant(@restaurant, review_id: @review), method: :patch do %><% end %>
<%= link_to 'delete', destroy_review_restaurant(@restaurant, review_id: @review), method: :delete do %><% end %>


この場合、全てRestaurantControllerで処理するので、引き回す:idは@restaurantのものです。
update_reviewアクションとdestroy_reviewアクションでは、パスの設定には記述されていませんが、review_idをパラメータに含めて引き回す必要が出てきます。

一方、ネストしたりソースを使う場合は以下のようになるかと思います。

# リソースをネストさせる場合

# routes.rb

resources :restaurant do
  resources :review, only: [:create, :update, :destroy]
end
# 生成されるパスはこんな感じ
# restaurantのCRUDは省略
# restaurant_reivew  POST   /restaurant/:restaurant_id/review     review#create
# restaurant_review  PATCH  /restaurant/:restaurant_id/review/:id review#update
# restaurant_reivew  DELETE /restaurant/:restaurant_id/review/:id review#destroy

# 呼び出すときはこう
<%= form_for :review, restaurant_review_path(@restaurant, @review) do %><% end %>
<%= form_for :review, restaurant_review_path(@restaurant, @review) do %><% end %>
<%= link_to 'delete', restaurant_review_path(@restaurant, @review), method: :delete %>
# もっとすっきりした書き方もありますが、わかりやすさ優先で。詳しくはRailsガイドを見てください


見比べてみるとわかるかと思いますが、ネストしたりソースとして表現した方が、すっきりしている上、責任の所在もわかりやすいかと思います。

RailsはRailに乗っている状態だとすっきりわかりやすい記述になるように出来ています。
つまり、なんだかアクロバティックな事をしている、あるいは泥臭い処理をしている状態というのは、Railから外れてきているという事になります。

状況によっては、あえてRailから外れてやらねばならない場合も出てくるかもしれませんが、ごく一般的な処理の場合はあえてRailから外れる必要はなく、Railに乗って(Railsの推奨するやり方に従って)書き進める方が、よりわかりやすく、より書きやすい状態を維持出来ると思います。

ソースを書いていて、なんだか怪しいにおいがしてきたら、ドキュメントなどを調べてみて、本来のやり方が内かどうかをチェックすると、最終的に楽をしてきれいなソースを書けるかと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/07/08 21:23

    > undefined method `create_review_restaurant' for xxxとなりました。
    すみません、ヘルパの呼び出しに関しては、ちょっと想像で書いている部分があって、実際のヘルパが何という名前で呼ぶ出すのかは、rake routesで確認してみてください。

    >ここだとURLはhttps://teratail.com/questions/40195となっていますが、
    コメント欄の方のフォームのパス、コメントの編集のパスも同じですが、hidden_fieldに別なidが設定されており、さらにデータタイプを指定するためのname属性も設定されています。(多分、投稿の方は回答のID、編集の方はコメントのID)
    teratailはRailsで実装されているのではないと思いますが、これが見通しが良いかどうかは、判断が分かれるところではないかと思います。
    とかくURL設計は設計者の好みや考えが反映される部分ではあるので、そこは好きなようにやってみれば良いと思いますが、好きなようにやるのであれば尚更、パスの生成等に関してよく勉強する必要があると思います。

    キャンセル

  • 2016/07/08 21:27

    なるほど!承知しました。考え方の問題ってことですね。ありがとうございました。もう少し自分で頑張ってみます。

    キャンセル

  • 2016/07/08 21:31

    URL設計には正解がない、ともいえますが、わざわざRailsで自動的にパスを生成するための仕組みあるのには理由がある、という事です。
    この辺は、「どうしてわざわざそんなものを作っているのだろう?」と考えてみるのは良い事だと思います。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.12%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る