rubyで存在しない要素にアクセスしたときに返すデフォルト値を指定するには？


coffeescriptで

[code]

    user = if Math.random() > 0.5
      {items:[{place:1000}]}
    else
      {items:[{place:1000},{place:2000}]} 
    
    place = user?.items?[1]?.place || 0
    console.log place
    
[/code]

とすると、userオブジェクトの中身がどんなに複雑でも、要素があればその値、無ければ0を取得するということが出来てとても便利だと思っているのですが、これをrubyで実現することは可能なのでしょうか。




ActiveSupport の Object#try を利用すると、同じような事が出来ます。これは、メソッドが無ければ nil を返し、そうで無ければ
send と同じ動きをするものです。

[code]

    require "active_support/all"
    user = {items:[{place: 1000}]}
    # => {:items=>[{:place=>1000}]}
    
    user[:items][1][:place]
    # => NoMethodError: undefined method `[]' for nil:NilClass
    
    user.try(:[], :items).try(:[], 1).try(:[], :place)
    # => nil
    user.try(:[], :items).try(:[], 1).try(:[], :place) || 0
    # => 0
    
    
    user = {items:[{place: 1000}, {place: 2000}]}
    user.try(:[], :items).try(:[], 1).try(:[], :place)
    # => 2000
    user.try(:[], :items).try(:[], 1).try(:[], :place) || 0
    # => 2000
    
[/code]

変数に対しては、`defined?` を利用することになります。(現実的には、変数自体が nil
であることがあっても、定義されていないというのはあまり無いと思いますが。)

[code]

    ((defined? user) ? user : nil).try(...)
    
[/code]

または

[code]

    user = nil if not defined? user
    
[/code]




もし遊び心気分なら、`method_missing`などの組み込み関数を使って、いたずらができます。

[code]

    # メソッドがないときに、このクラスが返されます。
    class DeadClass
      def method_missing(*args)
        self
      end
    
      def だ
        nil
      end
    
      def try_index(*args)
        self
      end
    end
    
    # ブラケットがメソッド名に表示するのは不正ですから、このクラスはインデックスのときに返されます。
    class Indexer
      def initialize(obj)
        @obj = obj
      end
    
      def [](*args)
        @obj.try_index(*args)
      end
    end
    
    class Object
      def method_missing(name, *args)
        if name.to_s[0] == 'か'
          realname = name.to_s[1..-1].to_sym
    
          if self.respond_to? realname
            self.send(realname, *args)
          else
            DeadClass.new
          end
        else
          raise NoMethodError.new("NoMethodError")
        end
      end
    
      def だ
        self
      end
    
      def か
        Indexer.new(self)
      end
    
      def try_index(*args)
        if self.respond_to? :"[]"
          self[*args]
        else
          DeadClass.new
        end
      end
    end
    
[/code]

使い方は、「か」とメソッド名前につけて、「だ」と行のあとにつけます。

[code]

    # 「だ」と`nil`か値になります。
    1.かsucc.かsucc.だ
    # => 3
    
    nil.かsucc.かsucc.だ
    # => nil
    
    user = {items:[{place: 1000}]}
    user.か[:items].か[1].か[:place].だ || 0
    # => 0
    
    user.か[:items].か[0].か[:place].だ || 0
    # => 1000
    
[/code]

Coffeescriptの方がいいけど、上のようにするとRubyでも多かれ少なかれ似せることができます。少なくとも構文がActiveSupportよりいいと思います。

