Ruby
Rails
activesupport

Railsの拡張メソッド覚書

はじめに

この業界に入り研修開けて半年、アウトプットも大切なのでテスト代わりにまとめようと思い、ちょこちょこ更新していきます。
一個一個確かめながら書くので長いし時間かかりそう。
指摘や改善点がありましたらよろしくお願いします。

実行環境

macOS 10.13.2
ruby 2.5.0
activesupport 5.1.4
pry

Active Support

blank? present?

require 'active_support'
require 'active_support/core_ext'

test = nil
test.blank? #=> true
test.present? #=> false
test.empty? #=> ”NoMethodError: undefined method `empty?' for nil:NilClass”

test = ""
test.blank? #=> true
test.present? =#> false

test = 0
test.blank? #=> false
test.present? #=> true

blank?とpresent?です。nil、falseとそれ以外を判定します。

blank?とempty?を混ぜて覚えてましたが、empty?はNilClassには使えません。

1/25追記
コメントで指摘されましたが、blank?は空白文字も空と判定しますので
empty?とは動作が違います。

自分の言い回しだとNilClassで使えるか否かのみに読み取れますね。ありがとうございます。

せっかくなのでもう少し深く掘り下げて説明しようと思います。

RailsGuidesの説明だと以下の通りです。

Railsアプリケーションは以下の値を空白(blank)とみなします。

  • nilとfalse

  • 空白文字 (whitespace) だけで構成された文字列 (以下の注釈参照)

  • 空欄の配列とハッシュ

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

配列とハッシュにも使えるそうです。

実際に定義元も見てみましょう。

rails/activesupport/lib/active_support/core_ext/object/blank.rb
class Object
  # An object is blank if it's false, empty, or a whitespace string.
  # For example, +false+, '', '   ', +nil+, [], and {} are all blank.
  #
  # This simplifies
  #
  #   !address || address.empty?
  #
  # to
  #
  #   address.blank?
  #
  # @return [true, false]
  def blank?
    respond_to?(:empty?) ? !!empty? : !self
  end

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

まずはObjectClassです。

present?は!blank?というのが読み取れます。ソースにコメントで例を書いてるのがありがたいですね。

rails/activesupport/lib/active_support/core_ext/object/blank.rb
class NilClass
  # +nil+ is blank:
  #
  #   nil.blank? # => true
  #
  # @return [true]
  def blank?
    true
  end
end

class FalseClass
  # +false+ is blank:
  #
  #   false.blank? # => true
  #
  # @return [true]
  def blank?
    true
  end
end

class TrueClass
  # +true+ is not blank:
  #
  #   true.blank? # => false
  #
  # @return [false]
  def blank?
    false
  end
end

次はNilClass、FalseClass、TrueClassの定義です。しっかりNilClassにも定義されています。

rails/activesupport/lib/active_support/core_ext/object/blank.rb
class Array
  # An array is blank if it's empty:
  #
  #   [].blank?      # => true
  #   [1,2,3].blank? # => false
  #
  # @return [true, false]
  alias_method :blank?, :empty?
end

class Hash
  # A hash is blank if it's empty:
  #
  #   {}.blank?                # => true
  #   { key: 'value' }.blank?  # => false
  #
  # @return [true, false]
  alias_method :blank?, :empty?
end

ArrayとHashはempty?が呼ばれていますね。

rails/activesupport/lib/active_support/core_ext/object/blank.rb
class String
  BLANK_RE = /\A[[:space:]]*\z/
  ENCODED_BLANKS = Concurrent::Map.new do |h, enc|
    h[enc] = Regexp.new(BLANK_RE.source.encode(enc), BLANK_RE.options | Regexp::FIXEDENCODING)
  end

  # A string is blank if it's empty or contains whitespaces only:
  #
  #   ''.blank?       # => true
  #   '   '.blank?    # => true
  #   "\t\n\r".blank? # => true
  #   ' blah '.blank? # => false
  #
  # Unicode whitespace is supported:
  #
  #   "\u00a0".blank? # => true
  #
  # @return [true, false]
  def blank?
    # The regexp that matches blank strings is expensive. For the case of empty
    # strings we can speed up this method (~3.5x) with an empty? call. The
    # penalty for the rest of strings is marginal.
    empty? ||
      begin
        BLANK_RE.match?(self)
      rescue Encoding::CompatibilityError
        ENCODED_BLANKS[self.encoding].match?(self)
      end
  end
end

指摘されたStringClassです。RailsGuidesにも注釈がありましたが、
文字列判定に[:space:]が使用されています。こちらで確認しましたらスペースやタブ、改行などが含まれるそうです。
それら以外がある場合はfalseになるようです。

rails/activesupport/lib/active_support/core_ext/object/blank.rb
class Numeric #:nodoc:
  # No number is blank:
  #
  #   1.blank? # => false
  #   0.blank? # => false
  #
  # @return [false]
  def blank?
    false
  end
end

class Time #:nodoc:
  # No Time is blank:
  #
  #   Time.now.blank? # => false
  #
  # @return [false]
  def blank?
    false
  end
end

最後はNumericClassとTimeClassです。数字と時間にはblankはないため必ずfalseが返ってくると書いてあります。

blank? present?のまとめ

  • blank?は[:space:]に含まれる文字とnilとfalseに対してtrueを返す。
  • present?はblank?の反対だよ。
  • String#empty?はlengthが0の場合にtrueを返すよ!

presence

require 'active_support'
require 'active_support/core_ext'

test = ""
foo = test.presence || "こっちが入るよ"
p foo #=> "こっちが入るよ"

test.presence #=> nil

test = "今回はこっち"
foo = test.presence || "こっちが入るよ"
p foo #=> "今回はこっち"

presenceメソッドはガイドみて知りました。

定義元も見ましょう。

rails/activesupport/lib/active_support/core_ext/object/blank.rb
class Object
  # Returns the receiver if it's present otherwise returns +nil+.
  # <tt>object.presence</tt> is equivalent to
  #
  #    object.present? ? object : nil
  #
  # For example, something like
  #
  #   state   = params[:state]   if params[:state].present?
  #   country = params[:country] if params[:country].present?
  #   region  = state || country || 'US'
  #
  # becomes
  #
  #   region = params[:state].presence || params[:country].presence || 'US'
  #
  # @return [Object]
  def presence
    self if present?
  end
end

コメントを見ればわかりますが、
present?をして
trueならそのオブジェクト自身、
falseならnilを返します。

使い方としては値が入っていればその値を使い、nilなら初期値を入れるみたいな使い方ですね。

# この書き方を
if foo.present?
  bar = foo
else
  bar = "初期値"
end

# 一行で書けてわかりやすい!
bar = foo.presence || "初期値"

presenceを覚えれば簡潔に書けそうです。

例にもあるようにparamsの代入でよく使えそうです。

presenceのまとめ

  • 自身に対してpresent?をしてるよ
  • trueなら自信を返すよ
  • falseならnilを返すよ
  • 初期値の代入とかで使えるよ

参考

Rails Guides Active Support コア拡張機能
Rubyのリファレンスみたいに書いてないのかな?と思ったらしっかり書いてありました。
Rails Guidesの情報量すごい

ブラケット表現 (Bracket Expression)
blank?で使われる[:space:]の説明