LoginSignup
3
0

More than 3 years have passed since last update.

【Rails】whereの戻り値に気を付けようという話

Last updated at Posted at 2020-11-09

はじめに

なぜ書いたか

  • whereやpluckの戻り値を理解しておらず、詰まった経験を共有します
  • .idでいけるだろ」と、ActiveRecordを乱用してしまうことに反省の意味を込めて

結論

  • whereやpluckの戻り値は配列である → pluckは配列で返す
  • whereは ActiveRecord::Relation を返す ※ 追記20/11/10
  • ActiveRecordのメソッドチェインの使い所を理解しよう

前提

環境

・ Ruby 2.6.5
・ Rails 6.0.3
・ devise(userモデル)を使用

ER図

スクリーンショット 2020-11-08 18.04.11.png

roomsテーブル: DMする部屋を管理します
user_roomsテーブル: userとroomの組み合わせを管理します

やりたいこと

スクリーンショット 2020-11-08 16.44.04.png
画像は user_rooms テーブルです。

<やりたいこと>
 DM相手(id=10) のユーザー情報を取得したい
<条件>
 DMするroomは room_id: 2
 自分(current_user)は user_id: 15

事象

下記のコードで実現しようとしました

rooms_controller.rb
class RoomsController < ApplicationController
  def show
      @room = Room.find(params[:id])
      current_user_room = @room.user_rooms.where.not(user_id: current_user.id)
      user_id = current_user_room.user_id
      @user = User.find(user_id)
  end
end

3行目:2人のユーザーが参加している@room(id:2)を定義します。
4行目:@room.user_rooms@roomとユーザーの組み合わせを取得します。(この時点でレコードは2つに絞られます。)さらに、where.not(user_id: current_user.id)で自分が含まれていない方のレコードに絞り込みます。
5行目:4行目で取得したレコードのうち、user_idを取得しようとしています。

ただし、4行目で以下のようなエラーになります。

[*] pry(main)> current_user_room.user_id

# 結果
NoMethodError: undefined method `user_id' for #<UserRoom::ActiveRecord_AssociationRelation:0x00007fbef39437e8>

???
とりあえず、current_user_roomを出力してみます

[**] pry(main)> current_user_room

# 結果
=> [#<UserRoom:0x00007fbeefb36518
  id: 2,
  user_id: 10,
  room_id: 2,
  created_at: Sat, 07 Nov 2020 04:00:28 UTC +00:00,
  updated_at: Sat, 07 Nov 2020 04:00:28 UTC +00:00>]

なぜ出来ないんだろう。試しにid=2を使ってfindで取得してみます

[**] pry(main)> user_room1 = UserRoom.find(2)

# 結果
=> #<UserRoom:0x00007fbeec46c960
 id: 2,
 user_id: 10,
 room_id: 2,
 created_at: Sat, 07 Nov 2020 04:00:28 UTC +00:00,
 updated_at: Sat, 07 Nov 2020 04:00:28 UTC +00:00>

はじめは同じ結果に見えていましたが、よく見るとcurrent_user_roomの方は[]がついていて、配列になってます。追記
current_user_roomの方は、whereメソッドで取得していました。
配列に対してメソッドチェインをあてても取れませんね。whereメソッドはデータ群を取るものですから当然です。
ちなみに、pluckメソッドでuser_idを取得しようとしましたが同様の結果でした。

解決策

配列から拾ってあげれば解決しそうです。
ということでRailsガイドを見てみると、takeメソッドが使えそうです。取得したいのは1個だと分かっているからです。

takeメソッドはレコードを1つ取り出します。どのレコードが取り出されるかは指定されません。

rooms_controller.rb
# 元のコード
current_user_room = @room.user_rooms.where.not(user_id: current_user.id)

# 対応コード(末尾に.takeを追加)
current_user_room = @room.user_rooms.where.not(user_id: current_user.id).take

※ または、末尾に配列の一個目を取得する[0]を付けても解消します。

修正後のコード

rooms_controller.rb
class RoomsController < ApplicationController
  def show
    @room = Room.find(params[:id])
    current_user_room = @room.user_rooms.where.not(user_id: current_user.id).take #.takeを追加
    user_id = current_user_room.user_id
    @user = User.find(user_id)

    @messages = @room.messages.includes(:user)
    @message = Message.new
  end
end

追記

whereが返すのは配列ではなく、ActiveRecord::Relationのインスタンスであることが分かりました。

# (where) current_user_room のクラスを確認
pry(main)> current_user_room.class
=> UserRoom::ActiveRecord_AssociationRelation

# (find) user_room1 の方も確認 
pry(main)> user_room.class
=> UserRoom(id: integer, user_id: integer, room_id: integer, created_at: datetime, updated_at: datetime)

まだまだ理解が浅いので ActiveRecordActiveRecord::Relation についてしっかり調べてみようと思います。

おわりに

ひとまず現状で出来る対応策をとりましたが、根っこから見直せばそもそもこの記述すらいらないのかもしれません。気付き次第直していきたいです。
ただし今回は、whereの挙動に触れることが出来て良い勉強になりました。
もっとレベルアップしていきたいので、不備や他の方法があればご指摘いただけると幸いです。

参考資料

Railsガイド
ActiveRecord の find と where の違い。 → 追記で非常に参考にさせて頂きました

3
0
2

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
3
0