Railsでツイートアプリに「ツイート詳細画面」を実装していたときのこと。
本来なら、見たいツイートの「詳細」リンクを押す→そのツイートだけが表示された詳細画面へ移動、となるはずだったのですが、
なぜかどのツイートを選択しても、同じid=1のツイートの詳細画面が表示されるようになってしまいました。。。
エラーの原因を一言で言うと「文法ミス」だったのですが、解決までの試行錯誤がとても勉強になったのでその流れをまとめておきます。
リンク先とルーティングが正しいか見てみる
まず、「詳細」リンクのリンク先が正しいか確認しました。
tweet/詳細を見たいツイートのid
でHTTPメソッドはget
。
ルーティングにも特におかしなところはなさそうです。
<ul class="more_list">
<li>
<%= link_to '詳細', "/tweets/#{tweet.id}", method: :get %>
</li>
#ほか省略
Rails.application.routes.draw do
get 'tweets/:id' => 'tweets#show'
#ほか省略
end
じゃあコントローラーだ!
う〜ん、こっちも特に変なところは見当たらないような、、、
しかし、今起こっている「選択したツイートと表示されるツイートが違う」という問題はつまりは
@tweet
に代入されて後にビューで表示されることになるTweet.find(params: [:id])
にエラーの原因があるのでは?と考えました。
class TweetsController < ApplicationController
def show
@tweet = Tweet.find_by(params[:id])
end
#ほか省略
end
binding.pryしてみる
メンターさんにアドバイスいただき、binding.pryをしてTweet.find(params: [:id])
の中身を確認することにしました。
class TweetsController < ApplicationController
def show
@tweet = Tweet.find_by(params[:id])
binding.pry #ここで処理を止めてみる
end
#ほか省略
end
ターミナルに表示された結果は下記のようになりました。
params
はidを正しく受け取れているのに、Tweet.find_by(params[:id])
でデータベースから取得してくるツイート内容が正しくないことが分かりました。
30: def show
31: @tweet = Tweet.find_by(params[:id])
=> 32: binding.pry
[1] pry(#<TweetsController>)> params #まず、paramsの中身を確認
=> {"_method"=>"get",
"authenticity_token"=>
"xH+TtqRP6lPkGVrodQmlFpdOJ0LsCKQcBT2gJuicKBC0D6rjEsAUIb+Jjp7VQfhLNlJ/C26vhFofgJkcENK51g==",
"controller"=>"tweets",
"action"=>"show",
"id"=>"13"} #ツイートidは正しく受け取れている
[2] pry(#<TweetsController>)> Tweet.find_by(params[:id])
#次に、Tweet.find_by(params[:id])の中身を確認。
#なぜかツイートid=1の内容が出力されている、、、!
CACHE (0.0ms) SELECT `tweets`.* FROM `tweets` WHERE (13) LIMIT 1
=> #<Tweet:0x007faa2febb758
id: 1,
text: "こんにちは!",
image: "https://sample.jpg",
updated_at: Mon, 27 Aug 2018 04:35:24 UTC +00:00,
updated_at: Mon, 27 Aug 2018 04:40:11 UTC +00:00,
user_id: 1>
原因はfind_byメソッドにあった
SQL文を再度よく読むと、SELECT `tweets`.* FROM `tweets` WHERE (13) LIMIT 1
つまり「tweetsテーブルから、13という条件のデータを、ひとつ取得する」というような内容になっていました。この条件は意味不明ですし、全く正しくありません。
そこで、もともとのコードをfind_by
メソッドからfind
メソッドに修正し、Tweet.find(params[:id])
とすることで正しくデータを取得できるようになりました!
findメソッドとfind_byメソッドの比較
エラーが無事解決したところで、また新たな疑問が。「どうしてfind
だとうまく行ってfind_by
だとうまくいかないのか?」というわけで復習です。
-
find
メソッドは「特定のidのデータを取得する」 →今回のようにidが分かっている場合はこちらを利用するほうがベター。 -
find_by
メソッドは「特定のカラムの値からひとつのデータを取得する」 →必ずどのカラムから値を見つけるか指定しないといけない。今回はそれができていなかった。
find
と find_by
の両方のパターンで書いてみると、下記のようになります。しかし、条件にあうデータがない場合の返り値が異なるようです。
今回の場合は、idを持たないツイートは基本的に存在しませんし、もし万が一nil
が@tweet
に代入されてしまった場合、正しくツイートが表示されないエラーに気づくことが難しいため、やはりfind
メソッドを利用する方向でよいと思いました。
#どちらも同じデータを取得できる
@tweet = Tweet.find(params[:id])
#条件にあうデータがない場合は、エラー
@tweet = Tweet.find_by(id: params[:id])
#条件にあうデータがない場合は、nilを返す
まとめ
- エラーの原因を見つけたらそのコードの直後でbinding.pryしてみよう!
- 解決策だけでなく、うまくいかなかった理由を検討することで学びが深まる! (メンターさんありがとうございました。)
参考
findとfind_byの使い分けについて、とても分かりやすく解説されています。
【Rails初心者必見!】ひたすら丁寧にデータ取得を説明(find, where)