ActiveRecord各メソッドのクエリ実行タイミングについて

## 概要
ActiveRecordの各検索メソッドがどのタイミングで実際にリクエストを投げるのか不明だったので調べてみた。
まず前提として、メソッドは大きく以下の二種類に分けられる。

- DBへのリクエストを発行するメソッド
- DBへのリクエストは発行せず、ActiveRecord::Relationのオブジェクトを返すメソッド


## 結論
まずはじめに結論から書くと、以下の違いが、リクエストの有無を決めている。

* ActiveRecord::FinderMethodsに実装されているメソッド
`find, find_by, take, first, last, exists?`
すぐにクエリを発行し、データベースにアクセスし、レコード(Modelのインスタンス or インスタンスの配列)を返す。

* それ以外の検索メソッド(where, limit, など)
ActiveRecord::Relationのオブジェクトを返し、実際にデータが必要になるタイミングまでデータベースにはアクセスしない。遅延評価(Lazy Evaluation)である。

ドキュメント: [ActiveRecord::FinderMethods](https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html)

ソースコード: [rails/finder_methods.rb at master · rails/rails · GitHub](https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/finder_methods.rb)

## そもそもActiveRecord::Relationとは？
クエリを生成するための情報を保持し、メソッドチェーンでつなげることができるため、再利用性が高く、便利なものになっている。
一方で、理解して使わないと、意図していないクエリを組み立ててしまい、パフォーマンスの悪化などを招くこともある。

内部実装としては、arelというライブラリをSQLの生成に用いている(元々は[外部のライブラリ](https://github.com/rails/arel)として開発され、取り込まれた。)

## 具体例: はまりがちなトラップ
### `to_a`の罠
以下のようなコードを書いた時、`where`で返ってきたRelationに対して`to_a`を呼び出しているため、その段階でRubyの配列になっているため、その段階でデータベースにアクセスが走ってしまうこと、また既に配列になってしまっているため、ActiveRecordのメソッドである`where`は使えないことに注意したい。

```rb
new_users = User.where('created_at > ?', "2019-04-01").to_a
new_users.where(...)
# => NoMethodError: undefined method `where' for #<Array:0x00007fa6c7575df8>
```

### limitとtakeの使い分け
上でも述べたようにtakeはActiveRecord::FinderMethodsのメソッドであり、データベースにアクセスを行い、レコードを取得する。
そのためlimitと同じようなものだと思って使うと誤った挙動を招くことがある。

rails consoleで実行してみると、limitはデータベースにアクセスをせず、takeはデータベースにアクセスをしていることがわかる。
(※メソッドの後に`;`をつけることで、即時評価されてしまうことを防いでいる。)

```ruby
> Book.limit(3);
> Book.take(3);
  Book Load (0.5ms)  SELECT  "books".* FROM "books" LIMIT $1  [["LIMIT", 3]]
```

例えば以下のようなケースを考えてみる。
2019年4月1日以降に作成されたレコードを更新日時順が新しい順に1000件取得し、さらにそのレコードを分析したいという要件があった時、以下のように書くことで再利用性を高めることができ、`limit`メソッドを使っているため、実際に評価されるまで、クエリは実行されない。

```
rel  = Book.where('created_at > ?', "2019-04-01").order(created_at: :desc).limit(1000);
```

しかし、以下のように`take`を使ってしまうと、その段階で内部で`to_a`されてしまうため、クエリが実行されてしまうのと、ActiveRecord::Relationではなくなってしまうため、メソッドチェーンが利用できなくなってしまう。

```
rel  = Book.where('created_at > ?', "2019-04-01").order(created_at: :desc).take(1000);
```

もちろん要件によっては、`limit`ではなく、`take`を使ったほうが正しいケースもあるが、両者の違いを認識して、正しく使う必要がある。


参考にした記事:
-  [ActiveRecord::QueryMethods#limitとActiveRecord::FinderMethods#take - Qiita](https://qiita.com/Naggi-Goishi/items/856a7698f167efdb2a17)

### (補足)takeのソースコード
find_take_with_limitの中で、`to_a`が呼ばれており、このタイミングで評価され、データベースにアクセスしていることがわかる。

```
    def take(limit = nil)
      limit ? find_take_with_limit(limit) : find_take
    end
(中略)
      def find_take_with_limit(limit)
        if loaded?
          records.take(limit)
        else
          limit(limit).to_a
        end
      end
```
Ref: [rails/finder_methods.rb at master · rails/rails · GitHub](https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/finder_methods.rb)


---
## まとめ
ActiveRecordは便利だが、挙動を勘違いして使うと無駄なアクセスが走り、パフォーマンスを低下させる恐れがある。
具体例に関しては、他にも例があげられそうなので、業務などで詰まることがあったら更新していきたい。

ActiveRecord周りの話はパフォーマンスにも大きな影響を与えるので、引き続き調べていきたい。
調べていく過程で面白かった記事も紹介しておく。

- [railsdm2018で「ActiveRecordデータ処理アンチパターン」を発表しました - Hack Your Design!](https://blog.toshimaru.net/rdm2018-active-record-anti-patterns/)
ActiveRecordであるあるなアンチパターンをまとめた発表。

- [http://gihyo.jp/dev/serial/01/ruby/0043:title]
- [http://labs.timedia.co.jp/2014/05/activerecord---arel-5-1.html:title]
ActiveRecord::Relationのクエリ生成を司るArelに関してまとめた記事。

- [https://qiita.com/wakasa51/items/3fe880d0e8e656044778:title]
今回の記事で取り上げたfinde_methodsのメソッドに関して、詳細にまとめてある記事。




## 参考にした記事
- 転載元: [ActiveRecord各メソッドのクエリ実行タイミングについて] (https://kysyogun.hatenablog.com/entry/2019/05/06/152549)
-  [https://www.theodinproject.com/courses/ruby-on-rails/lessons/active-record-queries](https://www.theodinproject.com/courses/ruby-on-rails/lessons/active-record-queries) 
- [ActiveRecord::Relationとは一体なんなのか | ActiveRecord | DoRuby](https://doruby.jp/users/whale/entries/ActiveRecord--Relation%E3%81%A8%E3%81%AF%E4%B8%80%E4%BD%93%E3%81%AA%E3%82%93%E3%81%AA%E3%81%AE%E3%81%8B)