save!やdestroy!の使い方

掲示板の作成時にsaveとsave!、掲示板の削除の時にdestroyとdestroy!どちらを使用したらいいのだろうと疑問に思ったので、勉強した内容をまとめます。

目次


掲示板作成時のsaveとsave!について

saveメソッドは、データベースの登録・更新を行う前に自動的に検証を行い、検証エラーがあればfalseを返します。 例えば、掲示板投稿内容が記載されていない、文字数がオーバーしているなどの処理が失敗する可能性が想定される時に使われます。

save!メソッドは、検証エラー時にfalseを返すのではなく例外を発生させるメソッドです。save!を使った場合は、例外が出なければデータベースへの登録・更新を行われたと考えることがで、処理が失敗する可能性が想定されない時に使われます

このことから、掲示板作成時には、入力内容の抜けや文字数オーバーのエラーが多いに考えられるので、saveを使うのが妥当でしょう。


掲示板削除時のdestroyとdestroy!

先ほどのsaveとsave!の説明をdestroyにも当てはめることができます。

掲示板の削除は、処理が失敗する可能性が想定されるでしょうか。掲示板を削除するだけなので、いちいちvalidatesを書かないですよね。 掲示板削除昨日は、処理が失敗する可能性を想定されていないので、destroy!を使うのが妥当でしょう。


最後に

最後まで読んでいただき誠にありがとうございました。参考になれば幸いです。

投稿者にのみ編集と削除リンクを表示させる。(判定ロジック)

def own?(object)
  object.user_id == id
end

user.rbにこのコードを記載することで、掲示板のコメントページに遷移した時、自分が投稿した掲示板なら編集と削除リンクを表示させ、自分が投稿した掲示板ではないなら表示させない機能を作ります。

目次


コードの解説

<% if @board.user_id == @current_user.id %>
  <% link_to('編集', board_path) %>
  <% link_to('削除', board_path, method: :delete) %>
<% end %>

この書き方でもOKですが、判定ロジックは様々なところで使用され、メンテナンス時に一つ一つ変更を加なければいけないのでかなり面倒です。


user.rb

def own?(object)
  object.user_id == id
end

ここでuser.rbに上記を加えます。 ロジックをControllerやViewではなく、Modelに記載することで、メンテナンス時にModelのみの変更ですみます。

<% if current_user.own?(board) %>
  <% link_to('編集', board_path) %>
  <% link_to('削除', board_path, method: :delete) %>
<% end %>


コードの中身を確認

object.user_id == id

こちらのidには、selfが省略されています。このselfは、current_userのオブジェクトを指すので、current_user.idとなります。 objectにboardが代入され、board.user_id == current_user_id(掲示板のuser_idとサインインしているユーザーのuser_idが同じ)なら、このメソッドはtrueを返します。

mergeについて

掲示板に対し、コメントを投稿する機能を追加しました。その時にcomments_controller.rbで使用したmergeについて解説します。


comments_controller.rb内の内容

def comment_params
  params.require(:comment).permit(:body).merge(board_id: params[:board_id])
end

ストロングパラメータの最後に記載されているmergeってなんだろう? プロを目指す人のためのRuby入門(通称チェリー本)で調べてみました。


チェリー本より引用

h = { us: 'dollar', india: 'rupee' }
{ japan: 'yen' }.merge(h) #=> { :japan=>"yen", :us=>"dollar", :india=>"rupee" }

変数hのキーと値を展開させると書いていました。


mergeする理由

レコード作成時に追加したい値はmergeメソッドで追加しておけば、合わせてそれの処理してくれます。 Image from Gyazo
コメントする時に、bodyにコメント内容が、board_idにコメントするボードのidが入ります。

ユーザーをブックマークする機能

user.rbに

def bookmark(board)
  bookmark_boards << board
end

というメソッドを追加しました。このメソッドについて勉強したのでアウトプットします。


@user = User.firstとし、Bookmark create(user_id: 1, board_id: 1~5)を入力しました。次に、@user.bookmark_boardsでuser_id: 1がブックマークした掲示板の詳細を出します。

Image from Gyazo


def bookmark(board)
  bookmark_boards << board
end

これは引数にboard(掲示板)という引数を渡したら、その掲示板を自分がブックマークした掲示板一覧の配列に追加するという意味です。


参考URL

railstutorial.jp

uniqueness:scopeについて

今まで、uniquenessにscopeを使って、一意になる範囲を限定する方法を勉強したので記載していきます。

読者の皆様に少しでも参考になれば幸いです。

 

:scopeオプション

class Bookmark < ApplicationRecord
 validates :user_id, uniqueness: { scope: :board_id }
end

  :scopeオプションは、一意性チェックの範囲を限定してくれます。

つまり、:scopeをつけることで、1人のユーザーが、1つの掲示板に対して1いいねを押すことができるのです。

 

:scopeオプションを外すと

class Bookmark < ApplicationRecord
 validates :board_id, uniqueness: true
end
class Bookmark < ApplicationRecord
 validates :user_id, uniqueness: true
end

board_idとuser_idにそれぞれvalidatesをかけ、scope:オプションを外して、uniqueness: trueにすると結果はどうなると思いますか? boardは掲示板モデルです。

validates :board_id, uniqueness: trueの場合

board_idに対してuniqueness: trueをかけているので、1つの掲示板に対して1いいねしかつけることができません。 つまり、早い者勝ちになってしまい、1人のユーザーが最初にいいねを押すと、それ以上同じ掲示板に対していいねができなくなります。

validates :user_id, uniqueness: trueの場合

1人1回しか掲示板にいいねを押せなくなります。user_idに対してuniqueness: trueをかけているので、1人のユーザーが2回いいねを押してしまうと、validatesに引っかかってしまいます。

最後に

実際に自身のローカル環境で試してみてください。より理解が深まると思います。 最後まで読んでくださり、誠にありがとうございます。

参考URL

310nae.com

ActionView::Missing Template エラー 

 

https://i.gyazo.com/b5afe847f107d6bdd5fac9354c41f3d7.png

 部分テンプレートのviewを記載し、renderで呼び出したらActionView::Missing Template エラー が出てしまいました。

 

原因と解決

https://i.gyazo.com/eb78cd4efc0ad00b48af902c1678ea99.png

 Missing partialというエラーが出ていたことと、以下のrender部分にエラーが出ていたので該当ファイルを確認。

 

https://i.gyazo.com/0bd306a734a7c194f215f5cb1dceec45.png

views/bookmarks/_bookmark_area.html.erb

<% if current_user.bookmark?(board) %>
<%= render 'bookmarks/unbookmark', { board: board } %>
<% else %>
<%= render 'bookmarks/bookmark', { board: board } %>
<% end %>

_bookmark_area.html.erbにエラーで表示された<%= render 'bookmark', { board: board } %>が記載されており、_bookmarkをrenderで呼び出しています。

同じ階層内だから'bookmark'で合っているはずなんだけどなーと悩んでいました。

 

renderの親元を辿ると、

https://i.gyazo.com/e036d896b860c9d4162a03a613bfaa95.png

boards/_board/html.erb

<%= render 'bookmarks/bookmark_area', board: board %>

 

views/bookmarks/_bookmark_area.html.erb

<% if current_user.bookmark?(board) %>
<%= render 'bookmarks/unbookmark', { board: board } %>
<% else %>
<%= render 'bookmarks/bookmark', { board: board } %>
<% end %>

boardsファイル内にrenderされていたので、上記のように記載する必要があった。

 

他の解決方法

bookmarksファイル内のerbをboardsファイル内に全て移せば、最初の書き方でもOK

 

最後に

エラー分の内容を修正できてOKだけではなく、他に解決できる方法がないか模索することが大事ですね。

 

renderの繰り返し表示

renderを使っていて、<%= render 'comments/comments', { comments: @comments } %>という記載方法がありました。

 

なんだこれ!と思ったので、勉強した内容をアウトプットしていきます。

読者の皆様に少しでも参考になれば幸いです。

 

 

実装内容

掲示板に対し、連続したコメントを作成する。

boards(掲示板)モデルとcommentsモデルを1:多の関係で紐付けしています。

 

コメントエリアの作成

views/boards/show.html.erb

<!-- コメントエリア -->
<%= render 'comments/comments', { comments: @comments } %>

'comments/comments'は、commentsファイルにある_comments.html.erbという部分テンプレートを呼び出しています。

 

{ comments: @comments }は、部分テンプレートであるviews/comments/_comment.html.erb内でcommentという変数を使っているのでこのように記載します。

 

※views/boards/show.html.erb内でcommentsと複数形で定義してます。これには理由があるので、後ほど説明いたします。

 

partial

部分テンプレートを呼び出す時にはpartialを使って、<%= render partial: 'comments/comments' %>と記載できますが、呼び出している部分テンプレートを強調しているものなので、省略しても構いません。

しかしlocalオプションを使用している時は、partialを付けないとエラーになってしまうので注意してください。

 

 

views/comments/_coomments.html.erb

<div class="row">
<div class="col-lg-8 offset-lg-2">
<table id="js-table-comment" class="table">
<%= render comments %>
</table>
</div>
</div>

ここでrenderの呼び出しがcommentsとなっています。これは、render commentsの複数形でcommentパーシャルをコメントの件数分eachして表示しているのです。

 

 

<% @comment.each do |comment| %>
<%= render 'comment', comment: @comment %>
<% end %>
<!--これを省略すると-->
<%= render partia: 'comment', collection: @comments %>
<!--さらに省略すると-->
<%= render @comments %>

これは、renderでオブジェクトの集合(コレクション)を指定する記法の省略形です。

なるほど、each文が隠れていたからコメントが連続して表示されるのか。

ここまで調べるのにかなりの時間がかかりました笑

 

参考ページ

railsguides.jp

pikawaka.com

 

最後に

Railsの規約を前提とした実装は現場でもよく出てくるみたいなので、勉強する必要性を感じますね。

 

最後まで読んでいただき誠にありがとうございました。