【技術】rails よくある 投稿にコメントつけるときのバリデーション処理
こんにちは、技術ネタを書くのも楽しいですね。
今回も初学者向けのネタを書いていきます。
今回の内容は私が独学を初めていろんな教材(私の場合は◯ットインストール)
を見ながらやっていた時に
カバーされていなかった(初学者にとってややこしいから割愛された?)
ところを初学者目線で書ければと思います。
今回は読んだままやれば
できるように書きますのでどうぞお楽しみください!
ご指摘どんどん受け付けます!
概要
ネストされたテーブルの入力フォームでエラー表示をする。
例)投稿テーブルPostsにコメントテーブルCommentsが親子関係のリレーションで定義されている状態で
投稿詳細画面にコメント入力フォームをおいた時にバリデーションエラーが出るようにする。
下記のような状態
前提
- rails sでローカル環境にサービスの画面が出力できる
- 初学者のかた
よくあるオンライン学習教材で勉強されている方だと理解が早いと思います。
*今回はコメントの作成(create)に焦点を絞っています。更新(update)がしてたい人は
こちらの記事を参考に修正を加えれば実装できるはずです。
準備
まずはこれから
$rails new app_name
scaffoldで投稿モデルとコメントモデルを作っちゃいましょう
CommentモデルはPostモデルに子として定義しておきましょう
$rails g scaffold Post title:string body:text $rails g scaffold Comment body:string post:references
マイグレート
$bin/rake db:migrate
各モデルにリレーションを定義しましょう
class Post < ActiveRecord::Base has_many :comments end class Comment < ActiveRecord::Base belongs_to :post validates :body, presence: true end
投稿に対して複数のコメント属するようにします
今回コメントを入力した時にエラーを出したいので
commentモデルのbodyカラムにはvalidationを入れておきましょう
config/routes.rbでcommentをpostにネストしておきます
resources :posts do resources :comments end
実装
UI(入力フォーム)
先の画像であったようなフォームを作りましょう
投稿の詳細(posts/show.html.erb)に作りたい
Point:form_forに与える引数でコメントは投稿にネストしているので
第1引数に今の投稿の情報を持った@post
第2引数に@post.comments.newで作った@commentを入れておきます
*あとでコントローラーでこの処理は書きます
<%= form_for([@post,@comment]) do |f| %> <% if @comment.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(@comment.errors.count, "error") %> prohibited this comment from being saved:</h2> <ul> <% @comment.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div> <% end %> ・ ・ ・ <% end %>
コントローラーの処理
PostとCommentでそれぞれ処理を書いていきましょう
投稿詳細のページを表示する時に使われるshowアクションで@postに紐づいた@commentをnew
を作成
*UIで書いた@commentはこれと同じ
class PostsController < ApplicationController before_action :set_post, only: [:show, :edit, :update, :destroy] ・ ・ ・ def show @comment = @post.comments.new end ・ ・ ・
次はCommentのcreateアクション
(必要ないものは削除したので丸ごと貼っておきます)
こちらはscaffoldで作られたコードとほとんど変わりないので解説は割愛で
class CommentsController < ApplicationController before_action :set_post, only: [:show, :edit, :create, :update, :destroy] def create @comment = @post.comments.new(comment_params) respond_to do |format| if @comment.save format.html { redirect_to @post, notice: 'Comment was successfully created.' } else format.html { render 'posts/show' } end end end private def set_post @post = Post.find(params[:post_id]) end # Use callbacks to share common setup or constraints between actions. # Never trust parameters from the scary internet, only allow the white list through. def comment_params params.require(:comment).permit(:body) end end
コメントを表示
投稿詳細画面にコメント一覧を表示しましょう
コメント入力フォームはrennderして使っています。
Point:postsのshowアクションですでにDBに保存されていなく情報が空の
コメントオブジェクトを作っているので下記の処理を入れて表示されないようにします
この処理がないと下記の画像のようになります(かっこ悪いですね)
*メソッドの意味等は調べてみましょう
<% next unless comment.persisted? %>
・ ・ ・ <%= render 'comments/form' %> <% @post.comments.each do |comment| %> <% next unless comment.persisted? %> <div class="comment-item"> コメント<br /> <%= comment.body %> </div> <% end %> ・ ・ ・
さて!これでコメント入力に対するエラーは実装できました!!
理解を深めるために
ちなみに実装方法は多岐にわたるので今回の方法だけが正解ではありません。
私が学習した時はコメント入力フォームを作る
>||form_for|
の引数に
>|ruby|[@post, @post.comments.build]|
と書いていました。
今回@post.comments.buildの代わりにコントローラーであらかじめ作成しておいた
@commentを使っていたと思います。
両者の挙動はかなり違うものになります。
それはなぜでしょうか?
自分でコードを変えながら答えを探るとデバッグの練習にもなると思います。
ヒント:両者とも入力対象のカラムが1つでvalidationがpresenceの場合にのみ
挙動が同じになります。
そしてコメントに文字制限がある場合、コメントにタイトルの入力が増えた場合は
挙動が変わるはずです。
では回答に
form_for[@post,@post.comment.build]
で実装した方で下記を実行した場合
ヒント:両者とも入力対象のカラムが1つでvalidationがpresenceの場合にのみ
挙動が同じになります。
そしてコメントに文字制限がある場合、コメントにタイトルの入力が増えた場合は
挙動が変わるはずです。
バリデーションエラーの表示は出ないはずです。
DBに保存する際に失敗はしていますがユーザーがみる画面ではエラーが伝えられないようになってしまいます。
理由はコメントをcreateする時、失敗すると@commentの[:errors]にエラー情報が格納されてrenderした時に
viewが呼び出されてエラー表示の処理内に[:errors]があるか確認するものがあります。
しかし、viewを呼び出すときに@post.comment.buildでエラー情報のないcommentオブジェクトを新たに作成してしまうので
エラーがない状態と判断されてしまう結果、エラー表示は出ないことになります。
わかりにくかもしれませんが今回のコードはgithubに公開してあるのでcloneして遊んでみてください。
まとめ githubリポジトリ
私がこちらのバグとはかなり時間をかけて闘いました、もちろんググったりもしましたし
ログで送信パラーメーターを調べたりと初心者なりに踏み込んだデバッグ作業をしいられました笑
結果的にpresenceをチェックするバリデーションのエラー表示を実装できましたが、これがそれ以外の
バリデーションに対応できないことを知ってショックを受けました。
同時に時間をかけて闘ってきたバグをやっつけて目的の処理ができた時にもう一度それが
本当に正しい挙動かどうかを一度落ち着いて考える必要があると感じました。
ここまで読んでいただいた初学者のかたの役に立てれば幸いです。
記事に対するご指摘アドバイス等はどしどし受け付けますので
私のTwitterにDMもしくはこちらの記事にコメントいただければ幸いです。
それでは!