Rails empty? exists? blank? present? まとめ

Rails empty? exists? blank? present? まとめ

June 24th, 2021

目次

この記事は?

empty? exists? blank? present? の違いを確認したものです。

Railsでは標準でオブジェクトの存在、空白確認ができるメソッドが用意されています。

なかでも混乱するのが empty? exists? blank? present? の4つのメソッドでしょう。

一つ一つ定義元を参照したり、コード検証をしてみました。

注意書き

また本記事ではBookというモデルを利用したコードにしています。 このBookは RailsのMVCのModelに相当する app/models/book.rb ということで進めていきます。必要であれば好きなモデル名に置き換えて読み進めてください。

ActiveRecord::Relationのコード例を全て Book.all として表現しています。 必要であればwhereなどに置き換えて読み進めてください。

empty?とexists?とblank?とpresent?の対応表

定義元empty?exists?blank?present?
Objectクラス(モンキーパッチ)----
Ruby標準クラス◯ Fileクラス----
ActiveRecord::Relation--

empty?(空っぽですか?)

定義元(Rubyのいくつかの標準のクラス)

Rubyのいくつかのクラスで標準である。

"", {}, []なら空っぽとみなす。

true or falseを返す。

定義元(ActiveRecord::Relation)

ActiveRecord::Relationにも存在する。

RubyのString、Array、Hashなどに標準で empty? メソッドというものがありますが、これはActiveRecord::Relationにも定義されています。

https://github.com/rails/rails/blob/main/activerecord/lib/active_record/relation.rb#L274-L281

# Returns true if there are no records.
def empty?
	if loaded?
		records.empty?
	else
		!exists?
	end
end

検証

  • String
"".empty?
=> true

"hello".empty?
=> false
  • Array
[].empty?
=> true

[1,2,3].empty?
=> false
  • Hash
{}.empty?
=> true

{a: 1, b: 2}.empty?
=> false
  • ActiveRecord::Base

これは使えない。

Book.new.empty?
NoMethodError: undefined method `empty?' for #<Book:0x00007fdb4632d538>
from /Users/kazuki/.anyenv/envs/rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/activemodel-5.2.4.3/lib/active_model/attribute_methods.rb:430:in `method_missing'
  • ActiveRecord::Relation
Book.all.empty?
Book Exists (1.7ms)  SELECT  1 AS one FROM "books" LIMIT ?  [["LIMIT", 1]]
=> false

exists?(存在する?)

DBに該当のレコードがあるかチェックしtrue or falseを返す。

定義元(Rubyの標準のFileクラス)

省略

定義元(ActiveRecord::RelationのFinderMethods)

https://github.com/rails/rails/blob/9a2c639a0c/activerecord/lib/active_record/relation/finder_methods.rb#L300-L347

検証

  • ActiveRecord::Base

これは使えない。

Book.new.exists?
  NoMethodError: undefined method `exists?' for #<Book:0x00007fdb461dcee0>
  from /Users/kazuki/.anyenv/envs/rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/activemodel-5.2.4.3/lib/active_model/attribute_methods.rb:430:in `method_missing
  • ActiveRecord::Relation
Book.all.exists?
  Book Exists (0.9ms)  SELECT  1 AS one FROM "books" LIMIT ?  [["LIMIT", 1]]
=> true

blank?(空欄ですか?)

まず、空白とは何なのかを整理しましょう。

以下はRailsのガイドより抜粋です。

blank?とpresent?

Railsでは以下を空白(blank)とみなす。

  • nilとfalse
  • 空白文字 (whitespace) だけで構成された文字列 (以下の注釈を参照)
  • 空欄の配列とハッシュ

その他、empty?メソッドに応答するオブジェクトはすべて空白として扱われます。

定義元(Objectクラスにモンキーパッチ)

https://github.com/rails/rails/blob/9a2c639a0c24c12490a8f9ef324395a36949e15c/activesupport/lib/active_support/core_ext/object/blank.rb#L17-L20

# @return [true, false]
def blank?
  respond_to?(:empty?) ? !!empty? : !self
end

まさかの empty? を利用していました。

しかしここで重要なのはObjectクラスでモンキーパッチしていることです。

Objectは全てのクラスのスーパークラスなので(つまりRubyのあらゆるクラスはObjectを継承している。)Objectにpresent?があるということはRubyのどのクラスでも blank? が利用できます。

定義元(ActiveRecord::Relation)

https://github.com/rails/rails/blob/9a2c639a0c/activerecord/lib/active_record/relation.rb#L761-L764

# Returns true if relation is blank.
def blank?
  records.blank?
end

検証

  • ActiveRecord::Base
Book.new.blank?
=> false
  • ActiveRecord::Reation(使えるがやめた方がいい。。。)

次の使い方はやばいです。絶対やらない方がいいです。

blank?かをどうかを確認したいだけなのに、該当のレコードを全て取得してオブジェクトに変換した上でblank?メソッドを利用しています。

しかもblank?はtrue or falseしか返さないので、その後すぐに生成したオブジェクトが破棄されるわけです。最悪ですね。🙏😌🙏

# これは使わない方がいい。
Book.all.blank?
  Book Load (37.7ms)  SELECT "books".* FROM "books"
=> false

# 代わりにexists?で十分。 (exists? はオブジェクトを生成しない。)
Book.all.exists?
  Book Exists (0.9ms)  SELECT  1 AS one FROM "books" LIMIT ?  [["LIMIT", 1]]
=> true

既に存在するオブジェクトに対して使うのであれば、問題ないです。

# これは問題ない。
books.blank?
=> false

present?(存在しますか?)

定義元(Objectクラスでモンキーパッチ)

https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/object/blank.rb#L22-L27

# An object is present if it's not blank.
#
# @return [true, false]
def present?
  !blank?
end

ただのblank?の否定版を利用しているだけでした。

なのでこれもblank?で指摘した内容と同じく注意する点があります。

# これはヤヴァイ (true or false返すためにわざわざ無駄にオブジェクトを生成する羽目に。。。)
Book.all.present?
  Book Load (29.9ms)  SELECT "books".* FROM "books"
=> true

# 代わりにempty?で十分。 (empty? はオブジェクトを生成しない。)

既に存在するオブジェクトに対して使うのであれば、問題ないです。

books.present?
=> true

まとめ

どこに定義されてるか見ると、使うケースがわかる。

blank?とpresent?はパフォーマンスの面でちょっと気をつけた方がいいケースがありました。😥