Ruby on Rails - レビュー機能のセットアップ

概要

Ruby on Rails アプリに、ユーザーが写真をレビューできる機能をセットアップしようとしています。

このガイドを参考にして実行しました。

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

他の Ruby on Rails プロジェクトに取り組んだ経験から、ここでは投稿/コメント関係モデルを写真/レビュー関係に使用できると思います。

まずは足場を作りました。

rails g scaffold review name:string body:text picture:references

各写真ページに個別のレビュー用のページを設けたいと考えています。

レビュー コントローラーにはインデックス ページが必要ないので、routes.rb ファイルからこの行を削除しました。

resources: reviews

それをルートを作成することで置き換えました

match '/pictures/:id/reviews', to: 'reviews#show', via: 'get'
match '/pictures/:id/reviews/edit', to: 'reviews#edit', via: 'get'
match '/pictures/:id/reviews/new', to: 'reviews#new', via: 'get'

ここでの私のパスには、写真の中にレビューを入れ子にすることが含まれています。

ルート

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
                        GET     /pictures/:id/reviews(.:format)     reviews#show
                        GET     /pictures/:id/reviews/edit(.:format)    reviews#edit
                        GET     /pictures/:id/reviews/new(.:format) reviews#new
updated_path            GET     /updated(.:format)              pictures#newest_updates
                        GET     /top-rated(.:format)            pictures#high_ratings

レビューコントローラー

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

  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

  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

ReviewsController からインデックス アクションを削除しました。

モデル

class Review < ActiveRecord::Base
  belongs_to :picture
end

class Picture < ActiveRecord::Base
  has_many :reviews
end

上記では、写真とレビューの間に 1 対多の関係を確立しました。

レビューの移行

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

Rails に関する私の理解に基づくと、これは機能するはずです。

写真#ページを表示

<% @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| %>

  <p>
    <%= f.label :stars, 'Rating', class: 'indent' %>
    <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 class = 'indent'>
   <input class="btn btn-info" type="submit" value="Review">
  </p>

  <a href = "/pictures/:id/reviews">Reviews</a>

<% end %>
<p class = 'indent'>
  <a class="btn btn-info" href="/pictures" role="button">Index</a>
</p>

ただし、Pictures/:id(show page) 内のリンクをクリックすると、

<a href = "/pictures/:id/reviews">Reviews</a>

レコードが見つからないエラー

Active Record::RecordNotFound in ReviewsController#show
Couldn't find Review with id=:id

Extracted source (around line #54):
53 def set_review
54  @review = Review.find(params[:id])
55 end
56
57 def review_params

RecordNotFound エラーが発生したため、問題は ReviewsController にあり、おそらくパラメータにあるのではないかと思います。

私は正しい考えを持っていると信じていますが、どこかで間違いを犯しました。フィードバックやご批判をお待ちしております。愚かな質問に聞こえるかもしれませんが、私は Ruby があまり得意ではありません。

解決策

ルート

後世のために、次のようにルートを設定するのが最善です。

#config/routes.rb
resources :pictures do
   resources :reviews, only: [:show, :edit, :new]
end

Rails でルートを作成するときは常に、フレームワーク全体が「オブジェクト」/「リソース」を中心に構築されていることを覚えておく必要があります。これが、ルートがリソースフル ルートとして知られる理由 (およびリソース ディレクティブがある理由) です。ルートを使用すると、アプリケーションのさまざまなリソースに関するルートを定義できます。

ネストされたリソース構造を使用することをお勧めします。

ヘルパー

あなたの問題は、Santosh などによって提供されたコードを使用して解決されました。 IE:

<%= link_to "Your Link", your_link_path(@object) %>

これがどのように機能するかを理解する必要があります。 Rails でルート ヘルパー (link_to ヘルパー内) を使用するたびに、ルートを調べて必要な詳細を見つけます。

次のパスを参照していました: photos/:id/reviews - 発見されたように、Rails はレンダリング時にリンクを構築する以外にリンクの URL に何の関係もないので、これは間違っています。

Rails がステートレスな HTTP ベースのフレームワークであることを考慮すると、Rails はリクエストごとにデータを照合する必要があります。つまり、リンクを構築したい場合は、レンダリング時に Rails にリンクを構築させ、静的なデータセットをバックエンドのコントローラーに渡す必要があります。

これがお役に立てば幸いです?