DB操作を伴うアプリケーションを作成するときに、ルーティングでresourcesを使うと、DB操作に必要な8つのルーティングをまとめて生成してくれます。
resourcesで生成されるルート一覧
アクション | HTTP動詞 | パス | 内容 |
---|---|---|---|
index | GET | /コントローラ名 | 一覧を表示 |
create | POST | /コントローラ名 | データ追加処理 |
new | GET | /コントローラ名/new | データ追加用の画面 |
edit | GET | /コントローラ名/:id/edit | 指定したデータを変更する画面 |
show | GET | /コントローラ名/:id | 指定したデータの詳細画面 |
update | PATCH | /コントローラ名/:id | 指定したデータの更新処理(変更箇所のみ) |
update | PUT | /コントローラ名/:id | 指定したデータの更新処理(全体を更新) |
destroy | DELETE | /コントローラ名/:id | 指定したデータの削除処理 |
(参考)【Rails】ルーティングのresourcesとは?意味と使い方
このresourcesはネストして使うこともできます。
resourcesをネストさせると、例えば、1つの製品(product)に対して複数のレビュー(review)を持たせるといった操作ができるようになります。
Youtubeやtwitterの1つの投稿に対して、複数のレビューが紐づくイメージです。
このようなアプリケーションを作る解説はWEB上でたくさん見つけることができますが、レビューで実行できるアクションが限られている場合があります。
ここでは、2つのresources(上記の例であれば、製品とレビュー)それぞれで、投稿・編集・一覧・詳細・削除ができるアプリケーションを作成する方法を実例で解説しています。
なお、ファイルの生成はDB操作時に便利なscaffoldを使っています。ですが、デフォルトで生成されたファイルをそのまま使おうとするとエラーが大量発生します。これらのエラーの解消方法をまとめて解説しています。
ネストしたresourcesで作成するアプリケーションの概要
製品のデータを持つproductsテーブルと、各商品毎の複数のレビュー持つreviewsテーブルを作成します。
productsテーブルと、revirewsテーブルは1対多の関係(1つの製品に複数のレビューがつけられる関係)にします。
テーブルの関係性に合わせて、ルーティングもproductsの配下にreviewsがくるようにします。
ある商品のレビュー一覧を見るときは /products/:products_id/reviews
その中の一つのレビューの詳細ページを見るとき /products/:products_id/reviews/:id
のようにします。
モデル、マイグレーションファイル、コントローラ、ビューなど必要なファイルはRailsの超便利なscaffoldコマンドを使って生成します。
scaffoldの使い方や生成されるファイルの詳細については下記をご参考ください。
【Rails】超便利コマンドscaffoldの使い方|何が起こっているかを完全理解
必要なファイルの作成
早速、scaffoldで必要なファイルを作成します。scaffoldの基本構文は次のようになります。
rails g scaffold モデル名 <カラム名1:型1> <カラム名2:型2>,,,
製品(product)関連のファイルを作成
prodcut関連のファイルを作成します。productsテーブルには、文字列型のnameカラムと、整数型のpriceカラムを作ります。
# rails g scaffold product name:string price:integer
▼実行例
# rails g scaffold product name:string price:integer
Running via Spring preloader in process 197
invoke active_record
create db/migrate/20210731102424_create_products.rb
create app/models/product.rb
invoke test_unit
create test/models/product_test.rb
create test/fixtures/products.yml
invoke resource_route
route resources :products
invoke scaffold_controller
create app/controllers/products_controller.rb
invoke erb
create app/views/products
create app/views/products/index.html.erb
create app/views/products/edit.html.erb
create app/views/products/show.html.erb
create app/views/products/new.html.erb
create app/views/products/_form.html.erb
invoke resource_route
invoke test_unit
create test/controllers/products_controller_test.rb
create test/system/products_test.rb
invoke helper
create app/helpers/products_helper.rb
invoke test_unit
invoke jbuilder
create app/views/products/index.json.jbuilder
create app/views/products/show.json.jbuilder
create app/views/products/_product.json.jbuilder
invoke assets
invoke scss
create app/assets/stylesheets/products.scss
invoke scss
identical app/assets/stylesheets/scaffolds.scss
必要なファイルをまとめて生成することができました。とても便利です。
レビュー(review)関連のファイルを作成
review関連のファイルを作成します。productsテーブルに所属する関係になるので、カラムと型に、product:references
を指定します。
併せて、整数型のrateカラムと、text型のreviewカラムを生成します。
referencesやテーブルに1対多の関係を持たせる方法の詳細については下記をご参考ください。
【Rails】references型とは何か?わかりやすく解説
# rails g scaffold review product:references rate:integer review:text
▼実行例
# rails g scaffold review product:references rate:integer review:text
Running via Spring preloader in process 172
invoke active_record
create db/migrate/20210731102330_create_reviews.rb
create app/models/review.rb
invoke test_unit
create test/models/review_test.rb
create test/fixtures/reviews.yml
invoke resource_route
route resources :reviews
invoke scaffold_controller
create app/controllers/reviews_controller.rb
invoke erb
create app/views/reviews
create app/views/reviews/index.html.erb
create app/views/reviews/edit.html.erb
create app/views/reviews/show.html.erb
create app/views/reviews/new.html.erb
create app/views/reviews/_form.html.erb
invoke resource_route
invoke test_unit
create test/controllers/reviews_controller_test.rb
create test/system/reviews_test.rb
invoke helper
create app/helpers/reviews_helper.rb
invoke test_unit
invoke jbuilder
create app/views/reviews/index.json.jbuilder
create app/views/reviews/show.json.jbuilder
create app/views/reviews/_review.json.jbuilder
invoke assets
invoke scss
create app/assets/stylesheets/reviews.scss
invoke scss
identical app/assets/stylesheets/scaffolds.scss
モデルファイルの編集
product.rbの編集
productsテーブルとreviewsテーブルが 1対多 の関係になるようにProductモデルのファイルを編集します。
class Product < ApplicationRecord
has_many :reviews
end
review.rbの編集
reviewモデルはscaffoldの実行時にreferences型を指定したことで、belongs_to :product
が自動で記載されているので、編集不要です。
もし、references型を指定していない場合は、以下のようにします。
class Review < ApplicationRecord
belongs_to :product
end
マイグレーションの実行
rails db:migrate
を実行し、マイグレーションファイルの内容をDBに反映させます。
# rails db:migrate
== 20210731102330 CreateProducts: migrating ===================================
-- create_table(:products)
-> 0.0558s
== 20210731102330 CreateProducts: migrated (0.0560s) ==========================
== 20210731102424 CreateReviews: migrating ====================================
-- create_table(:reviews)
-> 0.0808s
== 20210731102424 CreateReviews: migrated (0.0810s) ===========================
なお、productとreviewのマイグレーションファイルは次のようになっています。scaffoldの生成時にカラムを指定し忘れたり、referencesを指定し忘れたときは手動での編集が必要になります。
productsテーブル用のマイグレーションファイル
productsテーブル用のマイグレーションファイルは次のようになっています。
class CreateProducts < ActiveRecord::Migration[6.1]
def change
create_table :products do |t|
t.string :name
t.integer :price
t.timestamps
end
end
end
reviewsテーブル用のマイグレーションファイル
reviewsテーブル用のマイグレーションファイルは次のようになっています。
class CreateReviews < ActiveRecord::Migration[6.1]
def change
create_table :reviews do |t|
t.references :product, null: false, foreign_key: true
t.integer :rate
t.text :review
t.timestamps
end
end
end
t.references :product, null: false, foreign_key: true
が、scaffoldを実行するときにreferences型を指定したことで追記された部分です。
productテーブルと関連付けを行い、foreign_keyをtrueにしています。
ルーティングの作成と確認
ルーティングの作成
ルーティングファイルのネストしたresourcesを追加します。
config > routes.rb を以下のように編集します。scaffoldで自動生成されたルーティングは不要なので削除します。
Rails.application.routes.draw do
resources :products
resources :reviews
end
↓ 編集後
Rails.application.routes.draw do
resources :products do
resources :reviews
end
end
これで、productsの配下にreviewsがつくルーティングにすることができました。
ルーティングの確認
ターミナルで、rails routes
コマンドを実行すると、現在割り当てられているルーティングの一覧を確認することができます。
# rails routes
Prefix Verb URI Pattern Controller#Action
product_reviews GET /products/:product_id/reviews(.:format) reviews#index
POST /products/:product_id/reviews(.:format) reviews#create
new_product_review GET /products/:product_id/reviews/new(.:format) reviews#new
edit_product_review GET /products/:product_id/reviews/:id/edit(.:format) reviews#edit
product_review GET /products/:product_id/reviews/:id(.:format) reviews#show
PATCH /products/:product_id/reviews/:id(.:format) reviews#update
PUT /products/:product_id/reviews/:id(.:format) reviews#update
DELETE /products/:product_id/reviews/:id(.:format) reviews#destroy
products GET /products(.:format) products#index
POST /products(.:format) products#create
new_product GET /products/new(.:format) products#new
edit_product GET /products/:id/edit(.:format) products#edit
product GET /products/:id(.:format) products#show
PATCH /products/:id(.:format) products#update
PUT /products/:id(.:format) products#update
DELETE /products/:id(.:format) products#destroy
ネストしていない場合とは異なり、reivewsのルートがproductsの配下に配置されていることがわかります。
コントローラの編集
resourcesをネストさせたことで、ルーティングが通常の状態から大きく変化しています。これに合わせてコントローラも変更する必要があります。
reviewsコントローラの編集
reviewsのルートは、/products/:product_id/reviews
や、/products/:product_id/reviews/:id
のように、product_idが必要になっています。
また、reviewsで表示するページにどの製品に属するページかを示すために、productのデータを渡すようにします。
class ReviewsController < ApplicationController
before_action :set_review, only: %i[ show edit update destroy ]
# GET /reviews or /reviews.json
def index
@reviews = Review.where(product_id:params[:product_id])
@product = Product.find(params[:product_id])
end
# GET /reviews/1 or /reviews/1.json
def show
end
# GET /reviews/new
def new
@review = Review.new
@product = Product.find(params[:product_id])
end
# GET /reviews/1/edit
def edit
end
# POST /reviews or /reviews.json
def create
@review = Review.new(review_params)
respond_to do |format|
if @review.save
format.html { redirect_to product_reviews_url, notice: "レビューを作成しました。" }
format.json { render :show, status: :created, location: @review }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @review.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /reviews/1 or /reviews/1.json
def update
respond_to do |format|
if @review.update(review_params)
format.html { redirect_to product_reviews_url, notice: "レビューを更新しました。" }
format.json { render :show, status: :ok, location: @review }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @review.errors, status: :unprocessable_entity }
end
end
end
# DELETE /reviews/1 or /reviews/1.json
def destroy
@review.destroy
respond_to do |format|
format.html { redirect_to product_reviews_url, notice: "Review was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_review
@review = Review.find(params[:id])
@product = Product.find(params[:product_id])
end
# Only allow a list of trusted parameters through.
def review_params
params.require(:review).permit(:product_id, :rate, :review)
end
end
reviewsコントローラの変更点は以下です。
- newアクションに、@prodcutを追加
- set_reviewsメソッドに、@productを追加(show, edit, update, destoryアクションに渡す)
- redirect_toのリダイレクト先を、product_reviews_urlに変更(reviews_urlは存在しないのでエラーになる)
newアクションに@prodcutを追加
reviewsコントローラのnewアクションを実行したときに、レビューの一覧情報だけでなく、対象の製品情報も渡すようにします。
def new
@review = Review.new
@product = Product.find(params[:product_id])
end
Product.find(id番号)
は引数にid番号を渡すと、そのデータをproductsテーブルから抽出してくる処理です。
params[:product_id]
は、URIに含まれている :product_idにマッチするパラメータを取得します。パラメータの場所や名前はルーティングで定義されています。
例:/products/:product_id/reviews
set_reviewsメソッドに@productを追加
reviewsコントローラのshow, edit, update, destoryアクションを実行したときに、レビューの詳細情報だけでなく、対象の製品情報も渡すようにset_reviewメソッドに追記します。
なお、set_reviewメソッドは、冒頭のbefore_actionで実行されます。
def set_review
@review = Review.find(params[:id])
@product = Product.find(params[:product_id])
end
redirect_toのリダイレクト先をproduct_reviews_urlに変更
updateとdestroyアクション実行後に、redirect_toでリダイレクト先のパスが指定されています。デフォルトではreviews_urlが指定されています。
ですが、resourcesでネストさせたことでパスが変わっているので、それに合わせて修正します。
format.html { redirect_to product_reviews_url, notice: "レビューを更新しました。" }
format.html { redirect_to product_reviews_url, notice: "Review was successfully destroyed." }
format.htmlについては以下をご参考ください。
productsコントローラの編集
productsのルートはネストせずデフォルトの状態なので、productsコントローラを編集しなくても問題なく動きます。
ここでは、productsの詳細ページにアクセスしたときに、その製品のレビューが存在すれば、レビュー一覧を合わせて表示したいので、showアクションにreviewsのデータを渡すように編集します。
class ProductsController < ApplicationController
before_action :set_product, only: %i[ show edit update destroy ]
# GET /products or /products.json
def index
@products = Product.all
end
# GET /products/1 or /products/1.json
def show
@reviews = Review.where(product_id:params[:id])
end
# GET /products/new
def new
@product = Product.new
end
# GET /products/1/edit
def edit
end
# POST /products or /products.json
def create
@product = Product.new(product_params)
respond_to do |format|
if @product.save
format.html { redirect_to @product, notice: "Product was successfully created." }
format.json { render :show, status: :created, location: @product }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @product.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /products/1 or /products/1.json
def update
respond_to do |format|
if @product.update(product_params)
format.html { redirect_to @product, notice: "Product was successfully updated." }
format.json { render :show, status: :ok, location: @product }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @product.errors, status: :unprocessable_entity }
end
end
end
# DELETE /products/1 or /products/1.json
def destroy
@product.destroy
respond_to do |format|
format.html { redirect_to products_url, notice: "Product was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_product
@product = Product.find(params[:id])
end
# Only allow a list of trusted parameters through.
def product_params
params.require(:product).permit(:name, :price)
end
end
変更箇所はshowアクションのみです。
def show
@reviews = Review.where(product_id:params[:id])
end
各製品のreviewsのデータを渡すには、whereメソッドを使ってその製品のレビュー一覧を取得します。
モデルクラス.where(カラム名: 値)
とすれば、指定したカラムの値が一致するデータを取得します。
whereメソッドの詳細については下記をご参考ください。
【Rails】whereメソッドの使い方まとめ。モデルを使って取得するテーブルの検索条件の絞り込みをする方法
Productsのビューファイルの編集
productsのページにアクセスしたときに、各製品ごとにレビューのリンクを表示したり、詳細ページでレビューの投稿をするために、views > prodcutsディレクトリ配下のビューファイルを編集します。
products > index.html.erb
製品一覧ページの各製品の横に、対応するレビュー一覧ページへのリンクを設置します。
<p id="notice"><%= notice %></p>
<h1>Products</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th colspan="4"></th>
</tr>
</thead>
<tbody>
<% @products.each do |product| %>
<tr>
<td><%= product.name %></td>
<td><%= product.price %></td>
<td><%= link_to 'Show', product %></td>
<td><%= link_to 'Edit', edit_product_path(product) %></td>
<td><%= link_to 'Reviews', product_reviews_path(product) %></td>
<td><%= link_to 'Destroy', product, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Product', new_product_path %>
テーブルに、詳細ページへのリンクであるReviewsを追加します。<%= link_to 'Reviews', product_reviews_path(product) %>
product_reviews_path は /products/:product_id/reviews に対応します。引数で渡しているproductには、@products = Product.allを eachでひとつづつ抜き出したデータが入っています。
Railsの_pathヘルパーは引数に、モデル経由で取得した1つのデータを入れると、それに対応するパラメータのページへのリンクを作成してくれます。
また、最後のセルの中に4つのリンクが入るので、4つのセルを結合するよう、colspanの値を4にします。
実際の表示例は次のようになります(データ登録後)。
products > show.html.erb
製品詳細ページにレビューの一覧も合わせて表示するようにします。レビューが存在しない場合には、レビューのブロックを表示しないように条件分岐も入れます。
また、ページ下部に、製品レビュー作成ページへのリンクも追加しています。
<p id="notice"><%= notice %></p>
<p>
<strong>Name:</strong>
<%= @product.name %>
</p>
<p>
<strong>Price:</strong>
<%= @product.price %>
</p>
<% if @reviews.count != 0 then %>
<div>
<p><strong>Reviews</strong></P>
<table>
<thead>
<tr>
<th>Rate</th>
<th>Review</th>
</tr>
</thead>
<tbody>
<% @reviews.each do |review| %>
<tr>
<td><%= review.rate %></td>
<td><%= review.review %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% end %>
<br>
<%= link_to 'Edit', edit_product_path(@product) %> |
<%= link_to 'Back', products_path %><br>
<%= link_to 'Write Review', new_product_review_path(params[:id]) %>
レビュー一覧を表示するロジック
テーブルに、詳細ページへのリンクであるReviewsを追加します。<% if @reviews.count != 0 then %>
から<% end %>
までが追加した処理です。
@reviewsは、productsコントローラのshowアクションに追記した、@reviews = Review.where(product_id:params[:id])
です。
if @reviews.count != 0 then
は取得したレビューの数をカウントして、0以外(レビューがある場合)は、レビューの一覧を表示する条件分岐です。
レビュー作成ページへのリンク
末尾の<%= link_to 'Write Review', new_product_review_path(params[:id]) %>
で対象製品のレビュー作成ページへのリンクを設置しています。
new_product_review_pathは、/products/:product_id/reviews/newに対応するルートです。
パラメータ:product_idとして、引数で現在のURLの:id部分を渡しています。(/products/:id の :id部分が渡されます)
実際の表示例は次のようになります(データ登録後)。
▼レビューがないページにアクセスした場合
レビューが存在しない場合は、レビューのブロック自体が表示されないようにしています。
reviewsのビューファイルの編集
reviewsのページにアクセスしたときに、表示するレビュー一覧や詳細ページ、また、レビューの編集・削除ページのビューファイルを編集します。
特に、scaffoldで生成したビューファイルのルーティングはネストする前の状態になっているので、_urlヘルパや_pathヘルパで指定するprefixは、新しいルーティングに合わせてすべて修正する必要があります。(修正しないとエラーになります)
▼変更前と変更後のprefix
変更前 | 変更後 | パス |
---|---|---|
reviews | product_reviews | /products/:product_id/reviews |
new_review | new_product_review | /products/:product_id/reviews/new |
edit_review | edit_product_review | /products/:product_id/reviews/:id/edit |
review | product_review | /products/:product_id/reviews/:id |
また、それぞれで渡す必要があるパラメータにも注意が必要です。(:product_idだけか、:idも渡す必要があるか)
reviews > index.html.erb
各製品毎のレビュー一覧ページである、reviews > index.html.erbを以下のように編集します。
<p id="notice"><%= notice %></p>
<h1>Reviews for <%= @product['name'] %></h1>
<table>
<thead>
<tr>
<th>Rate</th>
<th>Review</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @reviews.each do |review| %>
<tr>
<td><%= review.rate %></td>
<td><%= review.review %></td>
<td><%= link_to 'Show', product_review_path(review.product_id, review.id) %></td>
<td><%= link_to 'Edit', edit_product_review_path(review.product_id, review.id) %></td>
<td><%= link_to 'Destroy', product_review_url(review.product_id, review.id), method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Review', new_product_review_path %><br>
<%= link_to "#{@product['name']}の詳細ページに戻る", product_path(params[:product_id]) %><br>
<%= link_to 'Top (All Products)', products_path %>
このビューへは、reviewsコントローラのindexアクションから、@reviewsと@productが渡されます。
def index
@reviews = Review.where( product_id:params[:product_id] )
@product = Product.find(params[:product_id])
end
冒頭のh1には何のページかわかるように、@product['name']
で製品名を表示します。
あとは、各レビューの横に表示する、詳細・編集・削除のlink_toのパスを修正します。例えば、詳細ページであれば、product_review_path(review.product_id, review.id)
とします。
URIには、product_idとidの2つのパラメータが必要です。_pathの引数に2つ値を渡せば、入力順にパラメータにデータが渡ります。
最後に、レビューページから製品一覧や製品詳細ページにいけるようにリンクを設置します。
<%= link_to "#{@product['name']}の詳細ページに戻る", product_path(params[:product_id]) %><br>
<%= link_to 'Top (All Products)', products_path %>
ここでは日本語と英語が混在しているので、適宜、日本語化を行ってください。
▼ 実際の表示例は次のようになります(データ登録後)
新規にレビューを追加した場合は、コントローラのcreateアクションで指定したnoticeが表示されます。
reviews > show.html.erb
次に、商品毎のレビューの詳細ページを編集します。
ここでは、製品名の表示と、末尾のリンクのパスの修正を行います。
<p id="notice"><%= notice %></p>
<p>
<strong><%= @product['name'] %>のレビュー詳細</strong>
</p>
<p>
<strong>Rate:</strong>
<%= @review.rate %>
</p>
<p>
<strong>Review:</strong>
<%= @review.review %>
</p>
<%= link_to 'Edit', edit_product_review_path(@product['id'], @review['id']) %><br>
<%= link_to "Back to reviews for #{@product['name']}", product_reviews_path %>
このビューへは、reviewsコントローラのindexアクションから、@reviewと@productが渡されます。
どちらもbefore_actionで設定されている、set_reviewメソッドの実行結果です。
def set_review
@review = Review.find(params[:id])
@product = Product.find(params[:product_id])
end
▼ 実際の表示例は次のようになります(データ登録後)
reviews > _form.html.erb
reviewsの編集ページ(edit.html.erb)と新規登録ページ(new.html.erb)の中では、renderを使ってパーシャル _form.html.erbを呼び出しています。
この2つのページを正しく表示するためには、_form.html.erbの修正が必須です。
編集と新規登録を実行したときのメソッドとアクセス先は以下のようになります。
- 新規登録: GETメソッド、 /products/:product_id/reviews/new
- 編集: PATCHメソッド、/products/:product_id/reviews/:id/edit
そのためには、form_withヘルパで生成されるformタグのアクションが、上記ルートになるようにする必要があります。
具体的には、form_withを以下のように修正します。
<%= form_with(model: [product, review]) do |form| %>
(省略)
これで、コンパイルすれば、formaタグのactionの値が、productsの配下にreviewsが来るURIとなります。
↓ コンパイル例
<form action="/products/1/reviews/15" accept-charset="UTF-8" method="post"><input type="hidden" name="_method" value="patch"><input type="hidden" name="authenticity_token" value="A_EtsR7JhUdMtt1HKkW7UBUzTU64FkK3Jqz4vkJ27sMoVVH__Bgf0dVZ7h0S8X5tWHaQWt-MmFtkfgV0IKof2Q">
<input value="1" type="hidden" name="review[product_id]" id="review_product_id">
<div class="field">
<label for="review_rate">Rate</label>
<input type="number" value="5" name="review[rate]" id="review_rate">
</div>
(省略)
</form>
action="/products/1/reviews/15
とすることができています。
また、type=”hidden”で生成されるinputタグのnameとidも、name="review[product_id]"
、id="review_product_id"
のように指定したproductとreivewに対応したものになります。
▼コードの全体像
<%= form_with(model: [product, review]) do |form| %>
<% if review.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(review.errors.count, "error") %> prohibited this review from being saved:</h2>
<ul>
<% review.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<%= form.hidden_field :product_id, value: product['id'] %>
<div class="field">
<%= form.label :rate %>
<%= form.number_field :rate %>
</div>
<div class="field">
<%= form.label :review %>
<%= form.text_area :review %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
reviews > edit.html.erb
_form.html.erbでデータをproductとreviewの2つのデータを受け取る準備ができたので、商品毎のレビューの編集画面を編集します。
ここでは、製品名の表示と、パーシャルに渡すデータにproductの追加、末尾のリンクのパスの修正を行います。
<h1>Editing Review for <%= @product['name'] %></h1>
<%= render 'form', {review: @review, product: @product} %>
<%= link_to 'レビュー詳細を見る', product_review_path(@review['product_id'], @review['id']) %><br>
<%= link_to "戻る(Product#{params[:product_id]}のレビュー一覧)", product_reviews_path(params[:product_id]) %>
renderでパーシャル _form.html.erbを読み込んでいます。 デフォルトではreviewsのデータしか渡さない状態なので、edit.html.erbと_form.html.erb 間で製品データ(product)を受け渡す必要があります。
renderを以下のようにしています。
<%= render 'form', {review: @review, product: @product} %>
▼ 実際の表示例は次のようになります(データ登録後)
reviews > new.html.erb
edit.html.erbと同様に、new.html.erbを編集して、_form.erb.htmlにproductとreviewのデータを渡せるようにします。
<h1>New Review</h1>
<%= render 'form', { review: @review, product: @product} %>
<%= link_to 'Back', product_reviews_path %>
renderを以下のようにしています。
<%= render 'form', {review: @review, product: @product} %>
▼ 実際の表示例は次のようになります(データ登録後)
Jbuilderファイルの編集
scaffoldを使うと、JSON形式でデータを出力することができるJbuilderのファイルも生成されます。
Jbuilderのファイルは、index.html.erbとshow.html.erbに対応する、index.json.jbuilderとshow. json.jbuilderがあります。
この2つのファイルは特に編集する必要がないのですが、中で呼び出しているJbuilderのパーシャル、_review.json.jbuilderでエラーが発生します。
このため、_review.jsonjson.jbuilderを次のように修正します。
json.extract! review, :id, :product_id, :rate, :review, :created_at, :updated_at
json.url review_url(review, format: :json)
↓ 修正後
json.extract! review, :id, :product_id, :rate, :review, :created_at, :updated_at
json.url product_review_url( params[:product_id], review, format: :json)
なお、エラー発生個所はjson.url review_url(review, format: :json)
の部分です。これは、JSONデータに対象のURLを "url": "URL(絶対パス)"
として追加するための処理です。
デフォルトでは_urlヘルパのprefixや引数で渡すデータがproductに対応していないので、これを修正します。
▼ 実際の表示例は次のようになります(データ登録後)
レビュー一覧の場合(URIの例:/products/1/reviews.json)
レビュー詳細の場合(URIの例:/products/1/reviews/15.json)
reviewsのJSONデータを表示することができました。
以上で、ネストしたresourcesを使った実際のプログラムが完成です。
日本語化やレイアウトの調整は行っていないので、適宜対応してみてください。