Nils and method chaining

2019-03-26 05:05发布

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.

9条回答
一夜七次
2楼-- · 2019-03-26 05:19

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 }
查看更多
爷、活的狠高调
3楼-- · 2019-03-26 05:26

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
查看更多
可以哭但决不认输i
4楼-- · 2019-03-26 05:31

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?
查看更多
啃猪蹄的小仙女
5楼-- · 2019-03-26 05:33

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
查看更多
一夜七次
6楼-- · 2019-03-26 05:35

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 }
查看更多
仙女界的扛把子
7楼-- · 2019-03-26 05:37

"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
查看更多
登录 后发表回答