LoginSignup
1
1

More than 5 years have passed since last update.

ツイートアプリ: ツイートの詳細画面が正しく表示されない【find と find_byの比較】

Last updated at Posted at 2018-09-03

Railsでツイートアプリに「ツイート詳細画面」を実装していたときのこと。

本来なら、見たいツイートの「詳細」リンクを押す→そのツイートだけが表示された詳細画面へ移動、となるはずだったのですが、
なぜかどのツイートを選択しても、同じid=1のツイートの詳細画面が表示されるようになってしまいました。。。

エラーの原因を一言で言うと「文法ミス」だったのですが、解決までの試行錯誤がとても勉強になったのでその流れをまとめておきます。

リンク先とルーティングが正しいか見てみる

まず、「詳細」リンクのリンク先が正しいか確認しました。
tweet/詳細を見たいツイートのidでHTTPメソッドはget
ルーティングにも特におかしなところはなさそうです。

index.html.erb
<ul class="more_list">
  <li>
    <%= link_to '詳細', "/tweets/#{tweet.id}", method: :get %>
  </li>
#ほか省略
routes.rb
Rails.application.routes.draw do
  get 'tweets/:id' => 'tweets#show'
  #ほか省略
end

じゃあコントローラーだ!

う〜ん、こっちも特に変なところは見当たらないような、、、
しかし、今起こっている「選択したツイートと表示されるツイートが違う」という問題はつまりは
@tweetに代入されて後にビューで表示されることになるTweet.find(params: [:id])にエラーの原因があるのでは?と考えました。

tweets_controller.rb
class TweetsController < ApplicationController
  def show
    @tweet = Tweet.find_by(params[:id])
  end
  #ほか省略
end

binding.pryしてみる

メンターさんにアドバイスいただき、binding.pryをしてTweet.find(params: [:id])の中身を確認することにしました。

tweets_controller.rb
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メソッドは「特定のカラムの値からひとつのデータを取得する」 →必ずどのカラムから値を見つけるか指定しないといけない。今回はそれができていなかった。

findfind_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)

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1