scaffoldで作成したCRUDのshowページにscaffoldをネストしたときの変更点についてまとめました。リンクなど変更が必要な箇所が結構あり大変です。
やりたいこと
productにproduct_documentをネストする。
前提
- productのCRUDは作成済
- product_documentには、以下3つのカラムを作る
name:string
content:text
public_level:integer
いざ実装へ
$ rails g scaffold ProductDocument user:references product:references name:string content:text public_level:integer
ここで、既にscaffoldで親のCRUDを作成しているためconflictが発生する。
上書き(overwrite)をする必要は無いため、noのnキーを入力
migrationファイルを確認し問題がなければ、
$ rails db:migrate
routes
resources :products do
resources :product_documents
end
Models
rails g scaffoldの際にアソシエーションが作成されているはずのため、確認
class ProductDocument < ApplicationRecord
belongs_to :user
belongs_to :product
end
親側にはhas_manyを追加
class Product < ApplicationRecord
has_many :product_documents, dependent: :destroy
end
class User < ApplicationRecord
has_many :product_documents
end
Controllers
class ProductDocumentsController < ApplicationController
before_action :set_product_document, only: %i[ show edit update destroy ]
def index
# @product_documents = ProductDocument.all
@product = Product.where(id: params[:product_id]).first
@product_documents = @product.product_documents.all
end
def show
end
def new
# @product_document = ProductDocument.new
@product = Product.where(id: params[:product_id]).first
@product_document = @product.product_documents.build
end
def edit
end
def create
# @product_document = ProductDocument.new(product_document_params)
@product = Product.where(id: params[:product_id]).first
@product_document = @product.product_documents.build(product_document_params)
@product_document.user_id = current_user.id
respond_to do |format|
if @product_document.save
format.html { redirect_to [@product, @product_document], notice: "Product document was successfully created." }
format.json { render :show, status: :created, location: @product_document }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @product_document.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if @product_document.update(product_document_params)
# format.html { redirect_to @product_document, notice: "Product document was successfully updated." }
format.html { redirect_to [@product, @product_document], notice: "Product document was successfully updated." }
format.json { render :show, status: :ok, location: @product_document }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @product_document.errors, status: :unprocessable_entity }
end
end
end
def destroy
@product_document.destroy
respond_to do |format|
# format.html { redirect_to product_documents_url, notice: "Product document was successfully destroyed." }
format.html { redirect_to product_product_documents_url, notice: "Product document was successfully destroyed." }
format.json { head :no_content }
end
end
private
def set_product_document
# @product_document = ProductDocument.find(params[:id])
@product = Product.where(id: params[:product_id]).first
@product_document = @product.product_documents.where(id: params[:id]).first
end
def product_document_params
params.require(:product_document).permit(:user_id, :product_id, :name, :content, :public_level)
end
end
scaffoldのデフォルトを#でコメントアウトしています。
ここがポイント!
@product = Product.where(id: params[:product_id]).first
.firstは不要では?と思ったけど、無くすとエラーになる。idがproduct_idにマッチするのは1個しかないけど、where句は条件にマッチした全件を取得するというメソッドのため、エラーになる。
ならばと、、find_byを使って以下のように書くこともできた。
@product = Product.find_by(id: params[:product_id])
Views
<p id="notice"><%= notice %></p>
<h1>Product Documents</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Content</th>
<th>Public level</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @product_documents.each do |product_document| %>
<tr>
<td><%= product_document.name %></td>
<td><%= product_document.content %></td>
<td><%= product_document.public_level %></td>
<!-- <td><%= link_to 'Show', product_document %></td> -->
<td><%= link_to 'Show', product_product_document_path(@product, product_document) %></td>
<!-- <td><%= link_to 'Edit', edit_product_document_path(product_document) %></td> -->
<td><%= link_to 'Edit', edit_product_product_document_path(@product, product_document) %></td>
<!-- <td><%= link_to 'Destroy', product_document, method: :delete, data: { confirm: 'Are you sure?' } %></td> -->
<td><%= link_to 'Destroy', product_product_document_path(@product, product_document), method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<!-- <%= link_to 'New Product Document', new_product_document_path %> -->
<%= link_to 'New Product Document', new_product_product_document_path %>
rails g scaffold時にresourcesで作成したカラムのフォームが自動生成されるためその部分は削除する。
<h1>Editing Product Document</h1>
<!-- <%= render 'form', product_document: @product_document %> -->
<%= render 'form', { product: @product, product_document: @product_document } %>
<!-- <%= link_to 'Show', @product_document %> | -->
<%= link_to 'Show', product_product_document_path(@product, @product_document) %> |
<!-- <%= link_to 'Back', product_documents_path %> -->
<%= link_to 'Back', product_product_documents_path %>
<h1>New Product Document</h1>
<!-- <%= render 'form', product_document: @product_document %> -->
<%= render 'form', { product: @product, product_document: @product_document } %>
<!-- <%= link_to 'Back', product_documents_path %> -->
<%= link_to 'Back', product_product_documents_path %>
<p id="notice"><%= notice %></p>
<p>
<strong>Name:</strong>
<%= @product_document.name %>
</p>
<p>
<strong>Content:</strong>
<%= @product_document.content %>
</p>
<p>
<strong>Public level:</strong>
<%= @product_document.public_level %>
</p>
<!-- <%= link_to 'Edit', edit_product_document_path(@product_document) %> | -->
<%= link_to 'Edit', edit_product_product_document_path(@product, @product_document) %> |
<!-- <%= link_to 'Back', product_documents_path %> -->
<%= link_to 'Back', product_product_documents_path %>
indexと同様に、rails g scaffold時にresourcesで作成したカラムのフォームが自動生成されるが、不要のためその部分は削除する。
実はpathの後の、(@product, @product_document)の順番はどちらでもいい。
<!-- <%= form_with(model: product_document, local: true) do |form| %> -->
<%= form_with(model: [product, product_document], local: true) do |form| %>
<% if product_document.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(product_document.errors.count, "error") %> prohibited this product_document from being saved:</h2>
<ul>
<% product_document.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :name %>
<%= form.text_field :name %>
</div>
<div class="field">
<%= form.label :content %>
<%= form.text_area :content %>
</div>
<div class="field">
<%= form.label :public_level %>
<%= form.number_field :public_level %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
こちらも同様に、rails g scaffold時にresourcesで作成したカラムのフォームが自動生成されるが、不要のためその部分は削除する。
親の変更点
product_documentのindexに飛ぶためのlinkを追加
<%= link_to '資料', product_product_documents_path(@product.id) %>
まとめ
そもそものテーブルの命名が悪く、product_product_documentなどといった、冗長なコードとなってしまっていること失礼しました。
コメント