#はじめに
railsでいろいろなオブジェクトの存在を確認したい場面があります。
どういう時にどういうメソッドを使えばいいのかよくわからなくなったので、あらためて調べて見ました。
※ちなみに一番下に素晴らしいまとめがありますので(リンク)、ほとんどの疑問は解決すると思います。
今回は、初心者の私がruby言語やrailsフレームワークについて理解が浅いということもあって、もう少し根本的なところから調べてみました。
よく見かける例
railsで頻繁に見かける書き方として、例えば以下のようなものがあります。
if params[:id] #params[:id]が存在するかどうか
if params.has_key(:page)? #params[:page]が存在するかどうか
if @user.age.present? #あるユーザーの特定のカラムが存在するか
if @user.comments.count == 0 #あるユーザーに関連付けられたモデルの要素が存在するか
if !@user.middle_name.empty? #特定のカラムが存在するか
if !User.nil? #ユーザーレコードが存在するか
さらに初心者を混乱させることに、
if @user.admin? #あるユーザーが特定の属性を持つか
if @user.image? #あるユーザーの画像が存在するか
などという、特定の用途のために用意されたヘルパーメソッドを使ったりもします。
コードが意図しているところはどれも理解できるのですが、似たような目的で使えそうなコードがたくさんあって、使い分けがどうすればいいのか迷います。
- なぜこのメソッドを使うのか?たとえば上記でメソッドを入れ替えて書くのはアリ?
- 各メソッドはどういう場面で使うのが適切なのか?逆に使わないほうが良いのはどういうとき?
まずは使い方
- ハッシュ
hoge
にあるキーkey
が存在するかどうかの確認
if hoge.has_key?(:key)
if hoge[:key]
if hoge[:key].present?
if !hoge[:key].nil?
この場合、この4つはほとんど同じ応答をします。
ざっくり言うと、keyがない場合はfalse、存在する場合はtrueとなる。
ただし、細かい例外があり・・・
# ラベル:keyが存在しない場合
if hoge.has_key?(:key) # => falseとして評価
if !hoge[:key].nil? # => falseとして評価
if hoge[:key] # => falseとして評価
if hoge[:key].present? # => falseとして評価
# キー:keyの値にnilが入っている場合
hoge[:key] = nil
if hoge.has_key?(:key) # => trueとして評価
if !hoge[:key].nil? # => falseとして評価
if hoge[:key] # => falseとして評価
if hoge[:key].present? # => falseとして評価
# boolean型のfalseが入る場合
hoge[:key] = false
if hoge.has_key?(:key) # => trueとして評価
if !hoge[:key].nil? # => trueとして評価
if hoge[:key] # => falseとして評価
if hoge[:key].present? # => falseとして評価
# " "が入る場合 (スペースは合ってもなくても結果は変わらない)
hoge[:key] = " "
if hoge.has_key?(:key) # => trueとして評価
if !hoge[:key].nil? # => trueとして評価
if hoge[:key] # => trueとして評価
if hoge[:key].present? # => falseとして評価
結論としては、この辺の微差が気にならないような処理ではどれを使っても良さげです。
(例えば、渡されるデータの範囲がある程度限られている時など)
なるべく意図が読みやすいコードになるように、もしくはよりエレガントな処理になるように、メソッドを選ぶ感じでしょうか。
ただハッシュにbooleanや空文字列が入る可能性がある場合は、意図に応じて書き分けたほうが良さそうです。
なぜこうなるかは後述します。
そもそもなぜこうなるのか
この辺の正確な理由を理解するのに手こずりました。
if hoge.has_key(:key)?
動作はシンプルで、例外なく「:keyが存在するか」ということを判別してくれる。
ただし、使える対象はハッシュに限定され、他のオブジェクトで使うとNoMethodErrorになる。
key? member? include?という表記も可能。(紛らわしい・・)
if hoge[:key]
存在するかわからないキーでハッシュを評価するとどうなるのか?
そもそもオブジェクト自体を真偽にかけるとどうなるのか?
色々と気になる書き方です。
-
まずハッシュ
hoge
にkey
というキーが存在しない場合、hoge[:key]はデフォルト値を戻します。つまり何も設定していなければnilを返します。もしキーが存在する場合は対応する値を返します(当たり前)。 -
rubyは、たった2つ**
nil
とfalse
だけをfalse**と判定します。**他のものは何であれ真(true)**です。
他言語から来た人は、『0は偽になるのでは?』とか、『空文字列''はどうなるの?』だとか、『真偽評価にかけられないものはないのか?』などと色々なことが気になりますが(←私です)、rubyのルールはシンプルですね。
余談ですが、真偽評価にかけると「真・偽」と判定されるオブジェクトを、それぞれtruthy/falseyと言ったりするようです。
何を持ってtruthy/falseyとするかの基準は、言語ごとにまちまちなようですが、rubyではnilとfalseを除いて、全てtrueとして評価されます。(つまり、falseyなオブジェクトは、nilとfalseだけと定義されている)
また論理演算子やif文にくっつけられたオブジェクトは、全て「真偽のいずれか一方」として強制的に評価され、エラーになることはありません。
C言語だと0がfalseyになっていたりして、ここが言語をまたぐ時に混乱するところかも。
!hoge[:key].nil?
nil?は文字通り、オブジェクトがnilであるときに限ってtrueを返します。
つまり、!hoge[:key].nil?
と、if hoge[:key]
とは、ほとんど挙動が変わりません。
結果が分かれるのは、hoge[:key]=falseの時と、ハッシュhogeのデフォルト値を変えている場合でしょうか。
hoge[:key].empty?について
配列やハッシュ、文字列と言った「いれもの」自体は存在するが、中の要素が空という時にtrueを返す。
empty?にまつわる注意点としては、適用範囲が限られるということ。
つまり配列・ハッシュ、または文字列といったオブジェクトにしか使えない。
上記以外のオブジェクトにempty?
を使うと、UndefinedMethodErrorが発生する。
hoge[:key].blank?
以下2つはrailsが用意するメソッド(rubyに付随するものではない)
blank?
は、ざっくり言うとnil?empty?の合せ技・・・のようだが、もう少しカバー範囲が広い。
実装はこんな感じでした。(初心者でもなんとか読める!)
class Object
def blank?
respond_to?(:empty?) ? !!empty? : !self
end
end
class String
BLANK_RE = /\A[[:space:]]*\z/
def blank?
empty? || BLANK_RE.match?(self)
end
end
<対象がString型のとき>
- 空白(タブや改行コード含む)だけからなる文字列の場合、
true
を返す - そうでなければ、
empty?
の結果が戻る
<対象が一般のオブジェクトのとき>
-
empty?
の対象になるオブジェクトの場合、empty?
の結果を返す - オブジェクトが
nil
かfalse
の場合、true
を返す - 上記のいずれにも当てはまらなければ
false
を返す
ざっくりまとめて言うと、
- 文字列の時は、空白文字列や空文字列のとき
true
、それ以外はfalse
- 配列やハッシュについては要素がない時に
true
- それ以外のオブジェクトについては、
nil
やfalse
の場合true
となる
hoge[:key].present?
hoge.present?
は上記blank
の逆の動作をします(正確に言うとblank
の「否定」)。
オブジェクトがnil
、false
、空白文字列の場合や、empty?
が真の場合にfalse
を返します。
それ以外
あと「存在確認をサイズの観点からアプローチする」という意味では、count
, size
, length
なども使われているようなのだけど、細かい挙動の違いまで追いかけきれなかったので、今回は対象外としました。
ざっくりとした理解としては、「対象となるいれもの」がすでに存在していて、中身の数が不特定な場合などに使えるようですね。特にActiveRecord周りで使うことが多いように思いました。
例えば、Userモデルと一対多で対応するCommentモデルがあったときに、コメントのあるなしで分岐させたりなんかする場合、count
が登場するのかな?
あとよく見るのは、gemに付随していたり、独自で作ったヘルパーメソッドなどですね。
で、何を使えばいいの?
ここまで調べてほぼ満足してしまったのですが(笑)、まだ肝心の結論が出ていません。
(以下、自分なりの推測です。もし何かおかしな点などあればぜひご指摘、アドバイスいただければ助かります。)
とても大雑把な理解としては、抽象度別に分けて考えればよいのかなあと思いました。
広いメソッド・・・広い対象をカバーできる、一般的なメソッド
狭いメソッド・・・特定の用途を想定した特殊メソッド
と勝手に命名するとします。
まずは目的にピッタリあった「狭いメソッド」を探したほうが、意図を読みやすく、正確な挙動をするコードが書けそうです。
つまりif params[:id]
やif params[:id].nil?
よりは、if params.has_key(:id)?
の方がぴったり来るような気がします。
同じく、if User.name.size==0
と書くよりは、if User.name.blank?
の方が、if User.admin.present?
よりはif User.admin?
の方が、正確な意図を反映できるように感じました。
一方、「狭い」メソッドは用途が限定されているので、自分の使いたい用途にぴったりのメソッドがない場合や、判定基準をカスタマイズしたいとなると、抽象度の高い広いメソッドが活躍できそうに思います。
またcount
やsize
などの使い方については、データベースの処理速度に関わるという別の側面もありそうですね。
さらにはコードとして美しいかどうか、rubyぽいかどうか、などというこだわりの部分もあると思うので、引き続き考えていきたいと思います。
参考にしたもの
下記の資料を盛大に参考にしました。
ありがとうございました。
-
簡潔に全体像をつかむにはこちらがすごくわかりやすかった。
http://qiita.com/somewhatgood@github/items/b74107480ee3821784e6 -
もう少し踏み込んで違いを確認したい場合。細かい挙動まで調べられている。
http://d.hatena.ne.jp/yk5656/20140317/1395559495 -
簡潔で必要十分な説明(英語)
https://railsless.blogspot.jp/2011/08/difference-between-nil-empty-blank.html -
truthy/falthyについてのわかりやすい説明(英語)
https://gist.github.com/jfarmer/2647362 -
countとsizeの挙動の違い
http://qiita.com/awakia/items/60467b95f92bee9a469a
(1/18) 下のコメント欄にて貴重なご指摘をいただいたので、いくつか追記しました。ありがとうございます。