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? |
---|---|---|---|---|
Objectクラス(モンキーパッチ) | -- | -- | ◯ | ◯ |
Ruby標準クラス | ◯ | ◯ Fileクラス | -- | -- |
ActiveRecord::Relation | ◯ | ◯ | ◯ | -- |
Rubyのいくつかのクラスで標準である。
"", {}, []なら空っぽとみなす。
true or falseを返す。
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
"".empty?
=> true
"hello".empty?
=> false
[].empty?
=> true
[1,2,3].empty?
=> false
{}.empty?
=> true
{a: 1, b: 2}.empty?
=> false
これは使えない。
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'
Book.all.empty?
Book Exists (1.7ms) SELECT 1 AS one FROM "books" LIMIT ? [["LIMIT", 1]]
=> false
DBに該当のレコードがあるかチェックしtrue or falseを返す。
省略
これは使えない。
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
Book.all.exists?
Book Exists (0.9ms) SELECT 1 AS one FROM "books" LIMIT ? [["LIMIT", 1]]
=> true
まず、空白とは何なのかを整理しましょう。
以下はRailsのガイドより抜粋です。
Railsでは以下を空白(blank)とみなす。
- nilとfalse
- 空白文字 (whitespace) だけで構成された文字列 (以下の注釈を参照)
- 空欄の配列とハッシュ
その他、empty?メソッドに応答するオブジェクトはすべて空白として扱われます。
# @return [true, false]
def blank?
respond_to?(:empty?) ? !!empty? : !self
end
まさかの empty? を利用していました。
しかしここで重要なのはObjectクラスでモンキーパッチしていることです。
Objectは全てのクラスのスーパークラスなので(つまりRubyのあらゆるクラスはObjectを継承している。)Objectにpresent?があるということはRubyのどのクラスでも blank?
が利用できます。
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
Book.new.blank?
=> false
次の使い方はやばいです。絶対やらない方がいいです。
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
# 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?はパフォーマンスの面でちょっと気をつけた方がいいケースがありました。😥