199
171

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rails における nil?, empty?, blank?, present? の使い分けとBetter Practice

Last updated at Posted at 2018-09-09

背景

Rails において、 nil? , empty?, blank?, present? はよく使われる便利なメソッド群です。

しかし、これらの便利メソッドには、分かりづらい落とし穴が潜んでいます。例えば、String#blank? に対する以下のコードは、一見 true を返しそうですが、実は間違いなのです。

string.blank? == string.nil? || string.empty?

この記事においては、便利メソッドの使いわけを、ついつい引っかかりがちな落とし穴と一緒に解説し、より良いRailsのコードを書くためのBetter Practiceを紹介したいと思います。

TL; DR

String において、 nilと空白文字を同様に評価したいときのみ、blank?present? を使うのが Better Practice

より細かくは

  • Rubyで定義されているのが、 nil?, empty?, Railsの ActiveSupport で定義されているのが、 blank?, present?
  • nil? は レシーバがnilであるとき、 true を返す。nilに対して呼べる数少ないメソッド。
  • empty? はレシーバが**「空」**であるとき、trueを返す。
  • blank? はレシーバが**「空白」**であるとき、trueを返す。
    • 空白とは、空であることに加えて、nil, false, 空白文字で構成されているということを含む
  • present? は常に !blank? のことである。

表にまとめると以下のようになります。

レシーバ nil? empty? blank? present?
nil true NoMethodError true false
true false NoMethodError false true
false false NoMethodError true false
[] false true true false
{} false true true false
"" false true true false
" " false false true false

各メソッドの詳解

nil?

nil?はレシーバが 「nil」 であれば真を返すメソッドです。

nil.nil? #=> true
"".nil? #=> false
[].nil? #=> false
{}.nil? #=> false

https://docs.ruby-lang.org/ja/2.5.0/method/Object/i/nil=3f.html
https://docs.ruby-lang.org/ja/2.5.0/method/NilClass/i/nil=3f.html

empty?

empty?はレシーバが**「空」**であれば真を返すメソッドです。
例えば、ArrayHashであれば、要素数が0のとき、Stringであれば、長さが0の時に真を返します。
空白文字であっても、長さが0でなければ偽を返します。また、TrueClassFalseClassにはこのメソッドは実装されていません。

nil.empty? #=> NoMethodError
true.empty? #=> NoMethodError
false.empty? #=> NoMethodError
[].empty? #=> true
{}.empty? # => true
"".empty? #=> true
" ".empty? #=> false

https://docs.ruby-lang.org/ja/2.5.0/method/Hash/i/empty=3f.html
https://docs.ruby-lang.org/ja/2.5.0/method/Array/i/empty=3f.html
https://docs.ruby-lang.org/ja/2.5.0/method/String/i/empty=3f.html

blank?

blank? メソッドは**「空白」**であるか否かを返します。
より正確な実装は、nilfalse などの偽と判定されるオブジェクト、[:space:]の空白文字だけで構成されるオブジェクト、その他 empty? なオブジェクトに対して真を返します。

ドキュメントには明示されていませんが、Stringクラスに対しては、内部的には正規表現によるマッチが行われています。そのため、invalidなバイト列があれば、ArgumentErrorが発生することには注意してください。

nil.blank? #=> true
true.blank? #=> false
false.blank? #=> true
[].blank? #=> true
{}.blank? # => true
"".blank? #=> true
" ".blank? #=> true
"\xFF".blank? #=> ArgumentError: invalid byte sequence in UTF-8

present?

present? は常に !blank? になっています。

実践編 (Better Practice)

ここからは筆者の主観に基づいた Better Practice を紹介します。

empty? と blank? の使い分けについて

上述したように、「空」を表現したい場合には、 empty?, 「空白」を表現したい場合には、 blank? を使うことを、コードの可読性や不要なバグを防ぐためにおすすめします。
より具体的には、 String クラスを期待しており、なおかつ nil と 空白文字を同一に扱いたいという場合にのみ blank? を利用し、それ以外の場合は empty? を使うことをおすすめします。

String クラス以外について、 nil チェックの省略を行いたくなった場合には、以下の対策が有効だと思われます。

Safe Navigation Operator の利用

一般的なオブジェクトに対して、 blank?present? のように、 nil チェックを省略したい場合は Ruby 2.3.0 で導入された Safe Navigation Operator を利用することがおすすめです。

TrueClass, FalseClass

これらについては、truefalse を期待している場所に nil が入らないようにコードを改善するのが良いです。なぜなら、真偽値を評価する変数に対して、nilが入った場合には、それは偽として評価されてしまいます。しかし、nilというオブジェクトからは、それが真を表現したいのか、偽を表現したいのかが分からなくなるためです。

if saved? # ここが `nil` の場合は、`save!`されないが、本当に大丈夫なのか?
  load
else
  save!
end

Array, Hash

ArrayやHashを特定の「集合」を示す為に利用している場合は、 blank? を使って nil チェックを省略するのではなく、nilが代入されるようなコードを、空のオブジェクト ([]{})が代入されるように修正することが望ましいです。

これは、nilではなく、空のオブジェクトを利用したほうが、「空集合」を適切に表現できる為です。また、呼び出し側でも、空の状態を区別せずに処理できるので、コードの複雑度が削減されます。

一方、複数のattributeを持つ単体の概念をHashで表現している場合は、存在しないことをnilを使って表現するほうがしっくりくる場合も多いです。

# 空集合がnilによって表現されている場合
if people
  lucky_people = people.select(&:lucky?)
else
  lucky_people = []
end

# 空集合が、空のオブジェクトによって表現されている場合
lucky_people = people.select(&:lucky?)
# 複数のattributeを持つ単体の概念をHashで表現している場合
if prize_data
  prize_name = prize_data[:name]
else
  prize_name = nil
end

[落とし穴] Rubocop の Rails/Blank

blank?empty? とは大きく異なるということでしたが、2018年9月現在のRubocopにおいては、 Rails/Blank という cop における NilOrEmpty: true (default) では blank? への間違った変換が行われてしまいます。

# Converts usages of `nil? || empty?` to `blank?`

# bad
foo.nil? || foo.empty?
foo == nil || foo.empty?

# good (実はgoodではない!)
foo.blank?

この挙動が問題になるようであれば、Rubocop YAMLの設定から Rails/Blank を disable にしてしまいましょう。

[落とし穴] ActiveRecord の validation

ActiveRecord の validationにおいて、 presence: true や、その逆である absence: true というものが存在します。これも、Better Practiceの冒頭で述べたように、 String に対して、 nil と空白文字を同様に扱いたい場合にのみ使用するのが良いと思われます。

また、 allow_blank: true についても、 空白を許容する場所以外では利用せず、 allow_nil: true を積極的に使っていくことをおすすめします。

# Stringであり、空白も許容できない場合
validates :good_description, presence: true
# Stringであり、空白である必要がある場合
validates :bad_description, absence: true

# Numericであり、nilが許容される場合
validates :score, numericality: { only_integer: true }, allow_nil: true
# true / false の場合 (presence, absence, allow_blankでは正常動作しない)
validates :activated, inclusion: { in: [true, false] }

https://guides.rubyonrails.org/v5.2/active_record_validations.html#presence
https://guides.rubyonrails.org/v5.2/active_record_validations.html#absence
https://guides.rubyonrails.org/v5.2/active_record_validations.html#allow-blank

参考

199
171
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
199
171

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?