🎉

「それ、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などで初めてそのオブジェクトが必要になった時に読み取りが行われるエコな感じになっています!

  1. SQL文を生成する。 (ActiveRecord_Relation型)
  2. 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実行はされていません。
以下のように、必要になったタイミングで始めて実行されるのです。

users_controller.rb
class UsersController < ApplicationController
  def index
    @users = User.where(is_active: true)
  end
end
index.html.erb
# ここでクエリが発行される! 
<% @users.each do |user| %>
  <%= user.name %>
<% end %>

ActiveRecord_Relationのプロパティとその役割

ActiveRecord_Relation型のloadrecordsと、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