Неопределенный метод 'reviews_path' для #‹#‹Class:0x3dfd490›:0x51970e0›

Ссылки и внешние ссылки

Ruby on Rails — настройка функций отзывов

NoMethodError в обсуждениях#new

http://ruby.about.com/od/rubyonrails/ss/blogpart4_4.htm

Фон

Я реализую функцию в своем приложении, которая позволяет пользователям оценивать и просматривать изображения.

Я использую модель отношений «Сообщения/Комментарии» для отношений «Изображения/Отзывы».

Модели

class Review < ActiveRecord::Base
  belongs_to :picture
end

class Picture < ActiveRecord::Base
  has_many :reviews
end

Выше я установил отношение «один ко многим» между фотографиями и отзывами.

Перенос отзывов

class CreateReviews < ActiveRecord::Migration
  def change
    create_table :reviews do |t|
      t.string :username
      t.text :body
      t.references :picture, index: true

      t.timestamps
    end
  end
end

Сопоставленные маршруты

match '/pictures/:id/reviews', to: 'reviews#show', via: 'get', :as => 'picture_reviews'
match '/pictures/:id/reviews/edit', to: 'reviews#edit', via: 'get'
match '/pictures/:id/reviews/new', to: 'reviews#new', via: 'get', :as => 'new_reviews'

Я назову маршрут для отзывов#edit после того, как исправлю эту проблему с отзывами#new.

Сообщение об ошибке

NoMethodError in Reviews#new
Undefined method 'reviews_path' for #<#<Class:0x45c1b00>:0x39ae810>
Extracted source (Around line #8):
5 <div class = 'edit-form'>
6   <div class = 'center'>
7 
8     <% form_for @review do |f| %>
9
10      <p>
11        <%= f.label :username %><br />

Я проверил, содержат ли какие-либо файлы «путь обзора», но все маршруты были названы правильно.

Маршруты

favorite_picture_path PUT    /pictures/:id/favorite(.:format)     pictures#favorite
pictures_path         GET    /pictures(.:format)                  pictures#index
                      POST   /pictures(.:format)                  pictures#create
new_picture_path      GET    /pictures/new(.:format)              pictures#new
edit_picture_path     GET    /pictures/:id/edit(.:format)         pictures#edit
picture_path          GET    /pictures/:id(.:format)              pictures#show
                      PATCH  /pictures/:id(.:format)              pictures#update
                      PUT    /pictures/:id(.:format)              pictures#update
                      DELETE /pictures/:id(.:format)              pictures#destroy
users_path            GET    /users(.:format)                     users#index
                      POST   /users(.:format)                     users#create
new_user_path         GET    /users/new(.:format)                 users#new
edit_user_path        GET    /users/:id/edit(.:format)            users#edit
user_path             GET    /users/:id(.:format)                 users#show
                      PATCH  /users/:id(.:format)                 users#update
                      PUT    /users/:id(.:format)                 users#update
                      DELETE /users/:id(.:format)                 users#destroy
sessions_path         POST   /sessions(.:format)                  sessions#create
new_session_path      GET    /sessions/new(.:format)              sessions#new
session_path          DELETE /sessions/:id(.:format)              sessions#destroy
contacts_path         POST   /contacts(.:format)                  contacts#create
new_contact_path      GET    /contacts/new(.:format)              contacts#new
root_path             GET    /                                    pictures#welcome
users_new_path        GET    /users/new(.:format)                 users#new
about_path            GET    /about(.:format)                     pictures#about
                      GET    /contacts(.:format)                  contacts#new
                      GET    /users/:id/favorites(.:format)       users#favorites
signup_path           GET    /signup(.:format)                    users#new
signin_path           GET    /signin(.:format)                    sessions#new
signout_path          DELETE /signout(.:format)                   sessions#destroy
picture_reviews_path  GET    /pictures/:id/reviews(.:format)      reviews#index
                      GET    /pictures/:id/reviews/edit(.:format) reviews#edit
new_reviews_path      GET    /pictures/:id/reviews/new(.:format)  reviews#new
updated_path          GET    /updated(.:format)                   pictures#new_updates
                      GET    /top-rated(.:format)                 pictures#high_ratings

Контроллер отзывов (часть 1)

class ReviewsController < ApplicationController
  before_action :set_review, only: [:show, :edit, :update, :destroy]

  def index
    @picture = Picture.find(params[:id])
    @review = Review.all
  end      

  def show
    @picture = Picture.find(params[:id])
    @review = Review.find(params[:id])
  end

  def new
    @review = Review.new
  end

  def edit
     @picture = Picture.find(params[:picture_id])
     @review = Review.find(params[:id])
  end

  def create
    @picture = Picture.find(params[:picture_id])
    @review = @picture.reviews.build(params[:review])

    if @review.save
      flash[:notice] = 'Review was successfully created.'
      redirect_to @picture
    else
      flash[:notice] = "Error creating review: #{@review.errors}"
      redirect_to @picture
    end
  end

Контроллер отзывов (часть 2)

  def update
    @picture = Picture.find(params[:picture_id])
    @review = Review.find(params[:id])

    if @review.update_attributes(params[:review])
      flash[:notice] = "Review updated"
      redirect_to @picture
    else
      flash[:error] = "There was an error updating your review"
      redirect_to @picture
    end
  end

  def destroy
    @picture = Picture.find(params[:picture_id])
    @review = Review.find(params[:id])
    @review.destroy
    redirect_to(@review.post)
  end

  private

    def set_review
      @review = Review.find(params[:id])
    end

    def review_params
      params.require(:review).permit(:username, :body, :picture_id)
    end
end

Отзывы#Индексная страница

<h3>Reviews for <%= "#{@picture.title}" %></h3>

<table>
  <thead>
  </thead>
  <tbody>
  </tbody>
</table>

<div class = 'center'>
  <p><%= link_to 'New Review', new_reviews_path(@review), :class => "btn btn-info" %></p>
  <p><%= link_to 'Back', picture_path, :class => "btn btn-info" %></p>
</div>

Ссылка на страницу Обзоры#новая

<p><%= link_to 'New Review', new_reviews_path(@review), :class => "btn btn-info" %></p>

Отзывы#Новая страница

<% @title = "New Review" %>

<h3>New Review</h3>

<div class = 'edit-form'>
  <div class = 'center'>

    <% form_for @review do |f| %>

      <p>
        <%= f.label :username %><br />
        <%= f.text_field :username %>
      </p>

      <p>
        <%= f.label :body %><br />
        <%= f.text_area :body %>
      </p>

      <p>
        <%= f.submit "Submit Review" %>
      </p>

    <% end %>

  </div>
</div>

<div class = 'center'>
  <%= link_to 'Back', picture_reviews_path(@picture) %>
</div>

Изображения#Показать страницу

<% @title = "#{@picture.title}" %>

<h4 class = 'indent'>Picture Statistics</h4>

  <ul id = 'view'>
    <li><strong>Title:</strong> <%= @picture.title %></li>
    <li><strong>Category:</strong> <%= @picture.category %></li>
    <li><strong>Rating:</strong> <%= pluralize(@picture.rating, 'Star') %></li>
    <li><strong>Favorited:</strong> By <%= pluralize(@picture.users.count, 'User') %></li></br>
  </ul>

  <% if @picture.rating > 4 %>

  <button class = 'top-picture'>Top Rated</button>

  <% end %>

<%= form_for @picture do |f| %>

  <div class = 'indent'>
    <p>
      <%= f.label :stars, 'Rating' %>
      <div class= "rating">
        1 &#9734;<%= f.radio_button :stars, '1' %>
        2 &#9734;<%= f.radio_button :stars, '2' %>
        3 &#9734;<%= f.radio_button :stars, '3' %>
        4 &#9734;<%= f.radio_button :stars, '4' %>
        5 &#9734;<%= f.radio_button :stars, '5' %>
      </div>
    </p>

    <p><input class="btn btn-info" type="submit" value="Rate"></p>
    <p><%= link_to 'Reviews', picture_reviews_path(@picture), :class => "btn btn-info" %></p>

  <% end %>

  <p><%= link_to 'Index', pictures_path, :class => "btn btn-info" %></p>
</div>

Я пробовал использовать вложенные ресурсы так

resources :pictures do
  put :favorite, on: :member
  resources :reviews
end

resources :users
resources :sessions, only: [:new, :create, :destroy]
resources :contacts, only: [:new, :create]

Это не сработало, потому что мои изображения направлялись с использованием :picture_id вместо стандартного поля :id. Поскольку он перенаправлялся на :picture_id, он не мог найти никаких изображений.

picture_reviews_path  GET     /pictures/:picture_id/reviews(.:format)     reviews#index
                      GET     /pictures/:picture_id/reviews/edit/:id(.:format)    reviews#edit
new_reviews_path      GET     /pictures/:picture_id/reviews/new(.:format) reviews#new

Столбцы изображений

Picture.column_names
=> ['id', 'title', 'category', 'stars', 'created_at', 'updated_at',
'ratings_count', 'ratings_total']

Проблема с вложенными маршрутами заключается в том, что он вызывает путь, используя имя_столбца, не найденное в таблице. Именно поэтому я решил вернуться к сопоставлению маршрутов.

Я считаю, что проблема заключается в моем ReviewsController, для которого может быть дублированный код.

before_action :set_review, only: [:show, :edit, :update, :destroy]
@review = Review.find(params[:id])

def set_review
  @review = Review.find(params[:id])
end

Я думаю, что мог бы удалить строку @review = Review.find из каждого метода, но меня больше всего беспокоит то, что метод set_review был определен как закрытый метод, поэтому он не мог быть возможным.

Помощь очень ценится и заранее спасибо.

Обновить

Я думаю, что проблема заключается в моем новом действии в моем ReviewsController.


person Darkmouse    schedule 04.08.2014    source источник


Ответы (3)


Это просто расширенная версия ответа @japed.

1. У вас нет маршрута к действию create или update

Оба действия работают с запросом POST, поэтому url_helpers сам по себе не скажет рельсам, что делать с запросом POST, когда он его получит. Что вам нужно, так это изменить ваши маршруты обратно на вложенные ресурсы (это было хорошо, как это было, ваша проблема была вызвана другим фрагментом кода). Итак, вам нужно:

resources :pictures do
  ...
  resources :reviews
end

Также удалите все другие маршруты для этого контроллера, так как они могут повлиять на ваши конечные маршруты. Не забудьте перезапустить сервер после изменения маршрутов.

2. Контроллер:

Во-первых, обратите внимание, что там очень много повторов — вы ставите @picture во всех действиях. В настоящее время ваша проблема заключается в том, что он использует params[:id] в одних действиях и params[:picture_id] в других. Это всегда должно быть picture_id, id должно быть зарезервировано для идентификатора обзора, так как вы находитесь внутри reviews_controller.

Лучший способ сделать это — создать еще один before_filter, который установит переменную @picture:

class ReviewsContorller < ApplicationController
  before_filter :set_picture
  # This is perfectly fine, but needs to be executed after :set_picture
  before_filter :set_review, only: [:show, :edit, :update, :destroy] 

  ...

  private

  ...

  def set_picture
    @picture = Picture.find(params[:picture_id])
  end

  def set_review
    @review = picture.reviews.find(params[:id])
  end

end

Обратите внимание, что @review вытягивается из ассоциации @picture — это важная проверка безопасности, если вместо этого вы использовали Review.find, все пользователи автоматически смогут просматривать, редактировать и создавать новые обзоры для всех фотографий, не зная, какую фотографию они на самом деле комментируют. . В вашем случае это не должно быть большой проблемой, но хорошо, чтобы это вошло в привычку.

3. Форма:

<% form_for @review do |f| %>

Казалось бы, все в порядке, но представьте, что вы являетесь своим приложением - как вы узнаете, какой правильный URL-адрес сообщения для этой формы? Rails — довольно умный фреймворк, и он пытается угадать его по предоставленным ресурсам. В этом случае вы передаете экземпляр класса Review, поэтому он попытается отправить от к review_path(@review.id). Проблема в том, что этого пути нет в ваших маршрутах, поэтому здесь вы получите undefined_method 'review_path'.

Также обратите внимание, что правильным маршрутом, который вам нужен, является /picture/:picture_id/reviews для новых отзывов или /picture/:picture_id/review/:idfor existing reviews. Hence rails will need the parent picture object to be passed as well to figure out the rightpicture_id`. Вы можете сделать это, передав массив ресурсов, причем тот, который форма действительно является последним, так:

<% form_for [@picture, @review] do |f| %>

Это укажет рельсам искать picture_reviews_path(@picture.id) для нового обзора или picture_review_path(@picture.id, @review.id) для существующих обзоров. Если у вас есть вложенные ресурсы в ваших маршрутах, оба они должны существовать.

4. Другие ссылки

Ваши текущие маршруты определяют именованный путь new_reviews, который больше не будет существовать после использования вложенных ресурсов — он будет переименован в new_picture_review, поэтому вам нужно изменить все вхождения new_reviews_path на new_picture_review(@picture)

person BroiSatse    schedule 18.08.2014

Поскольку вы делаете вложенные маршруты, вам нужно найти :picture_id, как вы только что нашли

class ReviewsController < ApplicationController
  before_action { @picture = Picture.find(params[:picture_id] }
end

Как говорит ваша ошибка, проблема в том, что reviews_path не существует, потому что вы его вложили

Так это

<% form_for @review do |f| %>

Хочет изменить на

<% form_for [@picture, @review] do |f| %>

Так что это идет к picture_reviews_path

Также это

<p><%= link_to 'New Review', new_reviews_path(@review), :class => "btn btn-info" %></p>

Хочет стать

<p><%= link_to 'New Review', new_picture_reviews_path(@picture, @review), :class => "btn btn-info" %></p>
person j-dexx    schedule 04.08.2014
comment
Вы заметили изменения в form_for? - person j-dexx; 04.08.2014
comment
Как попасть на страницу с формой отзыва? Вы ссылаетесь на него? Какую ссылку вы используете, чтобы туда попасть? - person j-dexx; 04.08.2014

Можно ли использовать неглубокие вложенные маршруты? То есть у вас будет вложенный ресурс там, где это необходимо, но при однозначности вы получите более короткий путь, всего с одним параметром для обзора. Вы все еще можете вернуться к картинке, используя picture_id в обзоре.

resources :pictures, shallow: true do
  put :favorite, on: :member
  resources :reviews, shallow: true
end

resources :users
resources :sessions, only: [:new, :create, :destroy]
resources :contacts, only: [:new, :create]

Затем улучшите модели, чтобы ассоциации лучше связывались, с помощью inverse_of:

class Review < ActiveRecord::Base
  belongs_to :picture, inverse_of: :reviews
end

class Picture < ActiveRecord::Base
  has_many :reviews, inverse_of: :picture
end

Это должно означать, что в памяти находится только одна копия изображения. А затем в ReviewsController:

class ReviewsController < ApplicationController
  before_action :set_review, only: [:show, :edit, :update, :destroy]

  def index
    @picture = Picture.find(params[:id])
    # reference @picture.reviews to get all reviews in the view
  end      

  def show
    @picture = Picture.find(params[:id])
    # use @picture.reviews to get all reviews in the view
  end

  def new
    # where will you get the picture this belongs to? 
    # Need to collect the picture_id param. and build the associated review
    @picture = Picture.find(param[:picture_id])
    @review = @picture.reviews.build()
  end

  def edit
     @picture = Picture.find(params[:picture_id])
     # use @picture.reviews in the view controller to get the associated reviews
  end

  def create
    @picture = Picture.find(params[:picture_id])
    @review = @picture.reviews.build(params[:review])

    if @review.save
      flash[:notice] = 'Review was successfully created.'
      redirect_to @picture
    else
      flash[:notice] = "Error creating review: #{@review.errors}"
      redirect_to @picture
    end
  end

Я думаю, что есть еще одна важная проблема. Вы продолжаете использовать такой фрагмент кода:

@review = Picture.find(id)

Но это возвращает ноль или более элементов. Это поможет вам лучше понять код, если вы вспомните, что обычно это массив:

@reviews = Picture.find(id)

Но еще лучше, не делайте этого. У вас есть ассоциации. Используйте их в представлении.

@picture.reviews

Это вернет массив. Если длина нулевая, отзывов нет. Если не ноль, это количество элементов обзора.

Тогда вы не совершите ошибку, выбирая переменную массива с именем @review, которая кажется единственной (это означает, что link_to @review имеет смысл, но не работает), а вместо этого используете массив:

<%- @picture.reviews.each do |review| %>
 <% link_to review ...%>

Надеюсь, это поможет!

person JezC    schedule 16.08.2014