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

##はじめに
タイトルのままですが、`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、７は`user_friends.friend_user_id = 1`の情報を持ってくることになります。。同じテーブルを２回結合する必要がありますね。

##いよいよfind_by_sql
こんな感じで書いてみました。

```ruby
#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]を出力してみます。

```ruby

[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の値を取ってみる

```ruby
[31] pry(main)> friends_list[0].display_name
=> "ほげ太郎"
```

…取得できました！

enabled?で真偽値を取りたい場合は、

```ruby
#これで聞くと失敗する
[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で指定した名前で値が取得できる。

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