「それ、ActiveRecord_Relationだよ?」〜Rails初心者がハマるデータ取得の罠〜
はじめに
こんにちは、ラブグラフでエンジニアインターンをしているうらっしゅです!
この記事はRuby初心者が勘違いをするであろうActiveRecord_Relationについて語っていきたいと思います!!
以下の出力ってどうなる?
User.create(id: 1, name: "urassh", email: "urassh@example.com")
User.create(id: 2, name: "taro", email: "taro@example.com")
User.create(id: 3, name: "syunsuke", email: "syunsuke@example.com")
# Userクラスじゃない...?
pp User.where(id: 1).class
いえ、違います。User::ActiveRecord_Relationでございます。🥴
もちろん、このuserのインスタンスメソッドは実行することができません!!
ActiveRecord_Relationとは
ActiveRecord_Relation
とは、RubyでSQLを発行してくれるライブラリです。
以下の2段階を経てデータのやり取りが行われています。つまり、Viewなどで初めてそのオブジェクトが必要になった時に読み取りが行われるエコな感じになっています!
- SQL文を生成する。 (
ActiveRecord_Relation
型) - SQL文を実行する。 (
オブジェクト型
)
なぜActiveRecord_Relationが必要なんだろう
ActiveRecord_Relationhが必要な理由の一つとして、SQL文をカスタマイズする段階
例えば、以下のような場合です。
・メソッドチェーンの絞り込みをした上でSQL文を構築したい。
・SQL文の遅延実行 (Builderパターンみたいな事) ができる。
・JOINクエリを使いたい。(ActiveRecordが便利すぎてN+1に気づきにくいかもしれませんが、発行されたSQLを見たら明らかです。)
ActiveRecord_Relationを返すメソッド (一例)
こうして見ると、where句などの絞りだったり、JOINしたりといった 条件
にまつわるメソッドが多く構成されていますね。
- all
- joins
- where
- having
- limit
- offset
- from
- distinct
- includes
中にはSQL生成と実行を同時に行うものもある。
こちらは、条件というよりは、直接オブジェクトを指定できる
ものだったり集計
に関するメソッドが多く構成されていますよね。
- find
- find_by
- select
- take
- first
- last
- second
- third
- fourth
- exists?
- ids
- pluck
- sum
- maximum
- minimum
- average
- count
- find_each
ActiveRecord_Relationの実行タイミング
先ほど、ActiveRecord_Relation
型はまだSQL文を作っている段階でSQL実行はされていません。
以下のように、必要になったタイミングで始めて実行されるのです。
class UsersController < ApplicationController
def index
@users = User.where(is_active: true)
end
end
# ここでクエリが発行される!
<% @users.each do |user| %>
<%= user.name %>
<% end %>
ActiveRecord_Relationのプロパティとその役割
ActiveRecord_Relation型のload
とrecords
と、loaded
について解説します。
load
このプロパティは現在構築されているクエリを実行し、records
プロパティに結果を格納するメソッドです。
> user = User.where(id: 1)
TRANSACTION (0.1ms) BEGIN
User Load (0.1ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1
> user.load
[#<User:0x0000ffff12345678
id: 1,
name: "うらっしゅ"
created_at: Mon, 01 Aug 2016 12:03:53.000000000 JST +09:00,
updated_at: Fri, 10 Jun 2022 19:15:48.863320000 JST +09:00,
]
records
このプロパティはクエリを実行し、読み込んだデータが格納されています。
> user = User.where(id: 1)
TRANSACTION (0.1ms) BEGIN
User Load (0.1ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1
> user.records
[#<User:0x0000ffff12345678
id: 1,
name: "うらっしゅ"
created_at: Mon, 01 Aug 2016 12:03:53.000000000 JST +09:00,
updated_at: Fri, 10 Jun 2022 19:15:48.863320000 JST +09:00,
]
ちなみに、ActiveRecord_Relation型で読み込んだ実体はrecordsプロパティに格納されているのでこのように直接アクセスすることもできます
user.records.first.name
loaded
このプロパティは既にデータベースから読み込みが行われたかのフラグ変数です。
loaded
がtrueになっていることで、records
に格納されているオブジェクトを読みにいくようになるので、SQLの実行が一度で済むようになっています。
> user = User.where(id: 1)
TRANSACTION (0.1ms) BEGIN
User Load (0.1ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1
> user.loaded
=> true
まとめ
このようにActiveRecord_RelationはSQL文をカスタマイズするためにすごく役立っていることがわかりますね。
loaded
など普段の開発であんまり触れない部分にも触れることで、ActiveRecordがうまくDBとやりとりする秘密に触れることができました!
ActiveRecord_Relation
をきちんと理解して使いこなして行きましょー!!
Discussion