开发者

Nils and method chaining

I'm just breaking into the ruby world and I could use a helping hand.

Suppose b is nil.

I'd like the following code to return nil instead of a "NoMethodError: undefined method"

a.b.c("d").e

The first thing I tried was to overload 开发者_如何学编程NilClass's missing_method to simply return a nil. This is the behaviour I want except I don't want to be this intrusive.

I'd love it if I could do something like this

SafeNils.a.b.c("d").e

So it's like a clean way to locally overload the NilClass's behaviour.

I'd love to hear some ideas or great resources for digging in on this. I'm also quite open to other approaches as long as it's fairly clean.

Thank you very much.


You can use the inline rescue:

x = a.b.c("d").e rescue nil

x will be nil if b is nil.

Or, as someone commented in that link, you can use the andand gem (see docs):

x = a.andand.b.andand.c("d").andand.e


Starting from ruby v2.3.0 there is another way built into the language, the safe navigation operator (&.) You can write: a&.b&.c&.d and you will safely get nil if one of the calls in the chain returns nil. You can read more about it here: http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/


I think you can find a great solution in rails but that solution follows a different approach. Take a look at the try method. It's a clean approach.


"try" is very clean, as lucapette said. More generally, you could also use a begin-rescue-end block too, depending on your situation.

begin
  a.b.c("d").e
rescue NoMethodError=>e
  return nil
end


Check Ick's maybe:

a.b.maybe.c("d").maybe.e

or using a block:

a.b.maybe { |b| b.c("d").e }


Remark in advance: b is a method, not a variable. So b 'is' not nil, it returns nil.

When 'b' is a method, why not modify b, so it returns something, what can handle nil.

See below for an example.


You may define the missing methods:

class A
  def b
    nil
  end
end
class NilClass
  def c(p);nil;end
  def e;nil;end
end
a = A.new

a.b.c("d").e

But I think, a rescue may fit your need better:

class A
  def b
    nil
  end
end
a = A.new
x = begin a.c.c("d").e 
rescue NoMethodError
  nil
end

An example, how you may define a nil-like example.

class A
  def b
    MyNil.new
  end
end

class MyNil
  def method_missing(m, *args, &block)
      if nil.respond_to?(m)
        nil.send(m)
    else
      self
    end
  end
  #Simulate nils bahaviour.
  def nil?;true;end
  def inspect;nil.inspect;end
  def to_s;nil;end
end

a = A.new
x = a.b.c("d").e 
p x
puts x
p x.nil?


To use a safe_nils similar to that you wrote:

def safe_nils &blk
  return blk.call
rescue NoMethodError
  return nil
end

safe_nils { a.b.c("d").e }


I've made the may_nil gem for this. https://github.com/meesern/may_nil

Like @Sony Santos's safe_nils answer it uses a block to wrap the method chain and rescues NoMethodError, but will check for nil (and so will properly raise an exception for other classes).

require `may_nil`
may_nil {a.b.c("d").e}   => nil (or the end result)
may_nil {0.b.c("d").e}   => Exception: NoMethodError (b on Fixnum)

Unlike andand or ike's maybe you don't need to pepper the method chain.

a.andand.b.andand.c("d").andand.e
==>
may_nil{ a.b.c("d").e }


One approach is to use inline assignment to local variables:

a && (ab = a.b) && (abcd = ab.c("d")) && abcd.e

For as long a chain as you've got here it isn't very elegant, but for a shorter chain it can be useful:

def date_updated(entry)
  (updated = entry.updated) && updated.content
end
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜