Railsでのいいね機能の実装ではまったところ findとfind_byの違い

今日は、画像投稿アプリのいいね機能を実装していました。

User,Postとのアソシエーションは以下のように1対nになっています。

User 1-n Like n-1 Post

したがって、likesテーブルには、user_idとpost_idの組み合わせがレコードとして挿入されるような実装になります。 Likeテーブルのcreateメソッドで正しい値がはいらず苦戦しました。気づいてみれば単純なことですが、findとfind_byの仕様の違いでした。

以下のような実装をしていました。

実装内容

_like.html.slim

= link_to likes_path(post_id: post.id), method: :post, remote: true do = icon 'far', 'heart', class: 'fa-lg'

likes_controller.rb

class LikesController < ApplicationController
  def create
    binding.pry
    @post = Post.find(params[:post_id])
    current_user.like(@post)
  end
end

※ likeというUserモデル内のメソッドでLikeテーブルに挿入するようにしています。

確認したこと

post_id が2のPostについて、いいねのボタンを押して実行したところ、railsサーバーのコンソール表示では以下のようなレコードが挿入されました。

Like Create (3.3ms) INSERT INTO `likes` (`user_id`, `post_id`, `created_at`, `updated_at`) VALUES (2, 1, '2019-10-27 19:07:58', '2019-10-27 19:07:58') 

post_idが2のPostにLikeをしたはずなのに、ずれた値が挿入されています。

よくよくソースをみかえしたところ、controllerで

@post = Post.find_by(params[:post_id]) 

の部分を

@post = Post.find(params[:post_id])

にしなければいけないことに気づきました。

上記のInsert分の前にfindしている部分のSQLのSELECT文をみると

 Post Load (2.0ms) SELECT `posts`.* FROM `posts` WHERE (2) LIMIT 1

のようになっています。これはエラーにならずにWHERE句の文がtrueになって一番最初のPostが返却されるようになっていました(だから post_id が1) find_byの部分をfindに変えたところ、以下のように変化して、正常にInsertされるのが確認できました。

Post Load (6.9ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 2 LIMIT 1

「findはidを検索するもの、find_byは引数を指定して検索するもの」 という理解はしていたのですが、いろいろ試行錯誤しているうちにfind_byのままにしてしまったようです。

間違えた場合に、エラー制御してくれると暗黙的に期待していたようで、気づくのに時間がかかりました。アソシエーションがやや複雑でidが複数あるので、それを整理するのを先にやったほうがよかったかもしれません。

テストを先に書くようにするか、そうでなければ発行されるSQLをよくよく観察するという習慣が必要だと感じました。

(所要時間 45分)