LoginSignup
0
0

More than 1 year has passed since last update.

AcitveRecordの `exists?` と `empty?` について

Last updated at Posted at 2023-03-22

概要

この記事は、ActiveRecordの中身を見ながら下記の疑問を解消する記事です。
( UserApplicationRecord を継承したモデルクラスだと思ってください。)

# これはできる
User.all.exists?
# User Exists? (0.6ms)  SELECT 1 AS one FROM "users" LIMIT $1  [["LIMIT", 1]]

# これもできる
User.all.empty?
# User Exists? (1.7ms)  SELECT 1 AS one FROM "users" LIMIT $1  [["LIMIT", 1]]

# なんでこれはできるのに
User.exists?
# User Exists? (0.6ms)  SELECT 1 AS one FROM "users" LIMIT $1  [["LIMIT", 1]]

# これはできない?
User.empty?
# NoMethodError

環境

  • Ruby 3.2.1
  • Rails 7.0.4.3

前提

こんな感じのテーブルとモデルです。

db/schema.rb(抜粋)
  create_table "users", force: :cascade do |t|
    t.string "name", null: false
    t.integer "age"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end
app/models/user.rb
class User < ApplicationRecord
  validates :name, presence: true
end

本題

exists?

User.all.exists? を実行すると、下記のメソッドが呼び出されます。

User.exists? を実行した時は、下記を経由して 上記の exists? メソッドが呼び出されます。
(User.method(:exists?).source_location を使ってたどりました。)

activerecord/lib/active_record/querying.rb
    QUERYING_METHODS = [
      :find, :find_by, :find_by!, :take, :take!, :sole, :find_sole_by, :first, :first!, :last, :last!,
      :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!,
      :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!,
      :exists?, :any?, :many?, :none?, :one?,
      :first_or_create, :first_or_create!, :first_or_initialize,
      :find_or_create_by, :find_or_create_by!, :find_or_initialize_by,
      :create_or_find_by, :create_or_find_by!,
      :destroy_all, :delete_all, :update_all, :touch_all, :destroy_by, :delete_by,
      :find_each, :find_in_batches, :in_batches,
      :select, :reselect, :order, :regroup, :in_order_of, :reorder, :group, :limit, :offset, :joins, :left_joins, :left_outer_joins,
      :where, :rewhere, :invert_where, :preload, :extract_associated, :eager_load, :includes, :from, :lock, :readonly,
      :and, :or, :annotate, :optimizer_hints, :extending,
      :having, :create_with, :distinct, :references, :none, :unscope, :merge, :except, :only,
      :count, :average, :minimum, :maximum, :sum, :calculate,
      :pluck, :pick, :ids, :async_ids, :strict_loading, :excluding, :without, :with,
      :async_count, :async_average, :async_minimum, :async_maximum, :async_sum, :async_pluck, :async_pick,
    ].freeze # :nodoc:
    delegate(*QUERYING_METHODS, to: :all)

delegate で  User.all に委譲できるようになっているので、 User.exists? という呼び出し方ができるようになっています。

ここに記載されているメソッドは全て委譲されているので、
User.idsUser.sum なども実行できるというわけですね。

empty?

一方、 User.all.empty? を実行すると、下記のメソッドが呼び出されます。

empty? は↑の exists? のように委譲はされていないので、
User.empty? は実行できないです。

おわりに

疑問を持ってから2年くらい経ちましたが、
記事にできてよかったです。
empty? も delegate してよくない?と思いました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0