はじめに
Active Recordで表現しきれないクエリを記述するときに活躍するfind_by_sqlですが、find_by_sqlを呼び出したモデル以外のテーブルのカラムを取得する際に少し悩んだのでそちらを共有します。
(※実際にfind_by_sqlを使用する場合は複雑なテーブル構造やクエリが想定されますが、今回は簡単のため単縦なテーブル構造とクエリを使用します。)
テーブル構成
usersテーブル
id | name | age |
---|---|---|
1 | taro | 20 |
2 | hanako | 22 |
postsテーブル
id | title | user_id |
---|---|---|
1 | taro’s post | 1 |
2 | hanako's post | 2 |
結合先テーブルのカラムを取得する
以下のように結合先のテーブルのカラムも取得するSELEC文を記述し、Post.find_by_sql
を実行して見ると戻り値はpostテーブルのidカラムとtitleカラムのみになってしまいます。
sql = <<~SQL.squish
SELECT posts.title, users.name
FROM posts JOIN users ON posts.user_id = users.id
SQL
Post.find_by_sql(sql)
# => [#<Post "id"=>nil, "title"=>"taro's post">, #<Post "id"=>nil, "title"=>"hanako's post">]
postモデルのfind_by_sqlメソッドを使用しているから結合先の別モデルのカラムは取得できないのか...と思っていたのですが違いました。
attributesを見てみるとしっかり属性としては持っているようでした。
Post.find_by_sql(sql)[0].attributes
# => {"id"=>nil, "title"=>"taro's post", "name"=>"taro"}
そのため、通常通りカラム名で値にアクセスすることができます。
sql = <<~SQL.squish
SELECT posts.title, users.name
FROM posts JOIN users ON posts.user_id = users.id
SQL
posts_with_user = Post.find_by_sql(sql)
# => [#<Post "id"=>nil, "title"=>"taro's post">, #<Post "id"=>nil, "title"=>"hanako's post">]
posts_with_user[0].name
# => "taro's post"
また、AS句を使用して別名でSELECTすることで、その別名で値にアクセスすることもできます。
sql = <<~SQL.squish
SELECT posts.title AS new_title, users.name AS new_name
FROM posts JOIN Users ON posts.user_id = users.id
SQL
posts_with_user = Post.find_by_sql(sql)
posts_with_user[0].new_title
# => "taro's post"
posts_with_user[0].new_name
# => "taro"
終わりに
ご覧いただきありがとうございました。
少しでも誰かの悩む時間を減らせていたら嬉しいです!