Недефиниран метод 'reviews_path' за #‹#‹Class:0x3dfd490›:0x51970e0›

Препратки и външни връзки

Ruby on Rails - Настройване на функцията за прегледи

NoMethodError in Discussions#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'

Ще назова маршрута за reviews#edit, след като поправя този проблем с reviews#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 />

Проверих дали някои файлове съдържат „review-path“, но всички маршрути бяха правилно именувани.

Маршрути

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

ReviewsController (Част 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 сам по себе си няма да каже на rails какво да прави с 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| %>

Това ще каже на rails да търсят 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