なぜany?はpresent?より高速と言われるのか

# はじめに
「present?メソッドとany?メソッドだったらany?メソッドが高速なのか・・・なぜだろう？」
この疑問を解決すべく、present?メソッドとany?メソッドの処理の違いについて調べてみました。

# 環境
macOS Catalina Version 10.15.4
Ruby: 2.7.0
Ruby on Rails: 6.0.2.2

# present?メソッド、any?メソッドの違い
どちらもテーブルにデータが存在するかを確認するメソッドです。
けれども内部処理はどちらも異なっています。

侍エンジニアブログさんの、any?メソッド記事の一部を抜粋します。

>present?メソッドとany?メソッドとの違いについて紹介します。
結論からいうとany?メソッドの方が高速です。
present? → 全てのデータを取得する
any? → 1件のみデータを取得する
Railsが実行する
SQLを比較して違いを確認してみましょう。

```
> Sample.where(name:"侍1").present?
Sample Load (0.4ms)  SELECT `samples`.* FROM `samples` WHERE `samples`.`name` = '侍1'
=> true
> Sample.where(name:"侍1").any?
Sample Exists (0.4ms)  SELECT  1 AS one FROM `samples` WHERE `samples`.`name` = '侍1' LIMIT 1
=> true
```
>このように、SQLの最後にLIMIT 1 が付与されています。

any?メソッドがpresent?よりも優れているように見えます。
ではなぜこのような処理になっているのか、Ruby on Railsのコードを追ってみました。

なお、modelは侍エンジニアブログさんと同様のSampleモデル(stringのname属性のみ)を作成しています。

# present?メソッドの内部処理
present?メソッド実行時のコードの場所はblank.rbとなっています。
このコードを追ってみます。

```
>Sample.where(name: "侍1").method(:present?).source_location
=> ["/Users/xxxxx/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/activesupport-6.0.2.2/lib/active_support/core_ext/object/blank.rb", 25]
```

```blank.rb
def present?
  !blank?
end
```

```relation.rb
alias :loaded? :loaded

def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
  @klass  = klass
  @table  = table
  @values = values
  @offsets = {}
  @loaded = false
  @predicate_builder = predicate_builder
  @delegate_to_klass = false
end

def records # :nodoc:
  load
  @records
end

def load(&block)
  unless loaded?
    @records = exec_queries(&block)
    @loaded = true
  end

  self
end

def blank?
  records.blank?
end
```

よってpresent?メソッドを呼び出した場合、クエリ未発行ならば、exec_queryメソッドでクエリを発行します。
このクエリは、指定されたテーブルの全レコード検索となります。
その後、結果に応じてblank?を行います。
もしクエリ発行済であれば、クエリを発行せずにblank?を行います。


# any?の内部処理
any?メソッド実行時のコードの場所はrelation.rbとなっています。
このコードを追ってみます。

```
>Sample.where(name: "侍1").method(:any?).source_location
=> ["/Users/xxxxx/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/activerecord-6.0.2.2/lib/active_record/relation.rb", 277]
```

```relation.rb
def empty?
  return @records.empty? if loaded?
  !exists?
end

def any?
  return super if block_given?
  !empty?
end

def load(&block)
  unless loaded?
    @records = exec_queries(&block)
    @loaded = true
  end

  self
end
```

```finder_method.rb
def exists?(conditions = :none)
  if Base === conditions
    raise ArgumentError, <<-MSG.squish
      You are passing an instance of ActiveRecord::Base to `exists?`.
      Please pass the id of the object by calling `.id`.
    MSG
  end

  return false if !conditions || limit_value == 0

  if eager_loading?
    relation = apply_join_dependency(eager_loading: false)
    return relation.exists?(conditions)
  end

  relation = construct_relation_for_exists(conditions)

  skip_query_cache_if_necessary { connection.select_one(relation.arel, "#{name} Exists?") } ? true : false
end
```

よってany?メソッドを呼び出した場合、毎回exists?メソッドにて、テーブルにレコードが1件あるかどうかクエリを発行します。
その後、結果を確認しています。
もしload変数がtrueになった場合は、クエリ発行せずに結果を確認するのみとなります。


# まとめ
最初に記載した疑問の答えです。
>any?メソッドがpresent?よりも優れているように見えます。
ではなぜこのような処理になっているのか、Ruby on Railsのコードを追ってみました。

present?メソッドの場合、初回のみ全レコードを取得するクエリを発行して判定します。
2回目以降の呼び出しはクエリを発行せずに判定します。

any?メソッドの場合、基本的に毎回、テーブルから1件レコードを取得するクエリを発行して判定します。

このため、全レコードを取り出さないany?メソッドが高速と言われているだと思われます。


ご指摘等あればコメントにご記載をお願い致します。


# 参考記事
[【Rails入門】any?メソッドの便利な使い方を紹介](https://www.sejuku.net/blog/73197#anypresent)
[RailsのActiveRecord::FinderMethodsのSQLクエリ発行の有無について調べる](https://qiita.com/wakasa51/items/3fe880d0e8e656044778)
[ActiveRecord::QueryMethodsのselectメソッドについて深掘りしてみた](https://techracho.bpsinc.jp/shin1rok/2019_08_20/79317)
[週刊Railsウォッチ（20191216前編）Rails 6.0.2がリリース、平成Ruby会議01開催、古いRailsのfindメソッド置き換えほか](https://techracho.bpsinc.jp/hachi8833/2019_12_16/84622)
[RailsのArelを調査してみた](https://qiita.com/valley/items/9ac200acbccc46b2ea44)