はじめに
タイトルのままですが、find_by_sql
を使ってデータを取得してみたので、やり方をメモしておきます。
やりたいこと
今回は、
あるユーザーの友達一覧を取得するものとしましょう。
用意したテーブルは
・users(カラムはid, display_name, enable_flag, created_at, updated_at)
・user_friends(カラムはid, user_id, friend_user_id, follow, followed_at[datetime], created_at, updated_at)
modelはこんな感じ
class User < ApplicationRecord
has_many :user_friends
enum enable_flag: {
disabled: 0,
enabled: 1
}
end
class UserFriend < ApplicationRecord
belongs_to :user
belongs_to :friend_user, class_name: 'User'
enum follow: {
not_followed: 0,
followed: 1
}
end
ユーザ①(users.id = 1)とユーザ②(users.id = 2)が友達であり、ユーザ①はユーザ②をフォローしてるけど、ユーザ②は①をフォローしてない場合はuser_friendsテーブルのデータはこうなっています。(友達かどうかとフォローする、しないは別物とします)
user_friendsテーブル
id|user_id|friend_user_id|follow|followed_at |created_at |updated_at
1| 1 | 2 | 1 |2019-10-07 00:31:45.224467|2019-10-07 00:31:31.314467|2019-10-07 00:31:45.224467
2| 2 | 1 | 0 | |2019-10-07 00:31:31.224467|2019-10-07 00:31:31.224467
find_by_sqlを使う ーその前に
find_by_sqlを使う前に、今回取得したい情報を整理しておきます。
1.主となるユーザーの友達のuser_id(friend_user_id)、
2.友達のdisplay_name(friend_user.display_name)、
3.友達が有効なユーザか(friend_user.enable_flag
)、
4.フォローしているかどうか(follow)、
5.フォローした日時(followed_at)、
6.友達からフォローされてるか(is_followed)、
7.友達からフォローされた日時(is_followed_at)
主となるユーザを①(users.id = 1)とすると、上記の6、7はuser_friends.friend_user_id = 1
の情報を持ってくることになります。。同じテーブルを2回結合する必要がありますね。
いよいよfind_by_sql
こんな感じで書いてみました。
#user_friendsテーブルにおいて、user_id = current_user.idのテーブルをfrom_user、
#friend_user_id = current_user_idのテーブルをto_userと'AS'を用い命名しjoin
sql = %{
SELECT from_user.friend_user_id, from_user.follow, from_user.followed_at,
to_user.follow AS is_followed, to_user.followed_at AS is_followed_at,
users.display_name, users.enable_flag
FROM user_friends AS from_user
JOIN users ON users.id = from_user.friend_user_id
JOIN user_friends AS to_user ON from_user.friend_user_id = to_user.user_id
WHERE from_user.user_id = #{current_user.id}
}
#current_userはどこかで取得したものとしましょう。。
friends_list = UserFriend.find_by_sql(sql)
SQLを記述して、find_by_sql()
の()内に渡せば良いです。
実行結果はこうなります。
UserFriend Load (1.1ms)
SELECT from_user.friend_user_id, from_user.follow, from_user.followed_at,
to_user.follow AS is_followed, to_user.followed_at AS is_followed_at,
users.display_name, users.enable_flag
FROM user_friends AS from_user JOIN users ON users.id = from_user.friend_user_id
JOIN user_friends AS to_user ON from_user.friend_user_id = to_user.user_id
WHERE from_user.user_id = 1
=> [#<UserFriend:0x000055df5b682438 id: nil, friend_user_id: 2, follow: "followed", followed_at: Mon, 07 Oct 2019 00:31:45 UTC +00:00>,
#<UserFriend:0x000055df5b6832a8 id: nil, friend_user_id: 5, follow: "not_followed", followed_at: nil>]
idはselectで指定してないのでnilです。from_userと名付けた、user_id = current_user.id のUserFriendの情報が取得できています。
SELECT で指定した名前で値が取れる!
え、display_nameとか、usersテーブルの値は取得できないのだろうか……。
とりあえず、friend_list[0]を出力してみます。
[28] pry(main)> friends_list[0]
=> #<UserFriend:0x000055df5b682438 id: nil, friend_user_id: 2, follow: "followed", followed_at: Mon, 07 Oct 2019 00:31:45 UTC +00:00>
#.followで値は取得できる
[29] pry(main)> friends_list[0].follow
=> "followed"
#enumで?つけてみる
[30] pry(main)> friends_list[0].followed?
=> true
期待を込めてdisplay_nameの値を取ってみる
[31] pry(main)> friends_list[0].display_name
=> "ほげ太郎"
…取得できました!
enabled?で真偽値を取りたい場合は、
#これで聞くと失敗する
[32] pry(main)> friends_list[0].enabled?
NoMethodError: undefined method `enabled?' for #<UserFriend:0x000055df5b673438>
from /usr/local/bundle/gems/activemodel-5.2.2.1/lib/active_model/attribute_methods.rb:430:in `method_missing'
#アソシエーションを使って聞くと取れる
[33] pry(main)> friends_list[0].friend_user.enabled?
User Load (3.6ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1
=> false
まとめ
・find_by_sql
を使う時は、SQLを記述して、find_by_sql()
の()内に渡す。
・selectで指定した名前で値が取得できる。
間違っている点等あればご指摘いただけますと幸いです。