LoginSignup
5
0

More than 3 years have passed since last update.

Rails find_by_sqlでSQLクエリを実行してみた

Last updated at Posted at 2019-10-07

はじめに

タイトルのままですが、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はこんな感じ

user.rb
class User < ApplicationRecord
  has_many :user_friends

  enum enable_flag: {
    disabled: 0,
    enabled: 1
  }
end
user_friend.rb
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で指定した名前で値が取得できる。

間違っている点等あればご指摘いただけますと幸いです。

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