これは、問題に取り組む方法のアイデアとコードサンプルの束を含むかなり長い答えです。
try
Railsには、そのようにプログラムできるtryメソッドがあります。これは、その実装方法の一種です。
class Object
def try(*args, &b)
__send__(*a, &b)
end
end
class NilClass # NilClass is the class of the nil singleton object
def try(*args)
nil
end
end
次のようにプログラムできます。
fizz.try(:buzz).try(:foo).try(:bar)
よりエレガントなAPIをサポートするために、これを少し異なる方法で動作するように変更することもできます。
class Object
def try(*args)
if args.length > 0
method = args.shift # get the first method
__send__(method).try(*args) # Call `try` recursively on the result method
else
self # No more methods in chain return result
end
end
end
# And keep NilClass same as above
次に、次のことができます。
fizz.try(:buzz, :foo, :bar)
andand
そして、 NilClassサブクラスを直接インスタンス化できないという事実をハッキングして、より悪質な手法を使用します。
class Object
def andand
if self
self
else # this branch is chosen if `self.nil? or self == false`
Mock.new(self) # might want to modify if you have useful methods on false
end
end
end
class Mock < BasicObject
def initialize(me)
super()
@me = me
end
def method_missing(*args) # if any method is called return the original object
@me
end
end
これにより、次のようにプログラムできます。
fizz.andand.buzz.andand.foo.andand.bar
いくつかの派手な書き直しと組み合わせる
ここでも、この手法を拡張できます。
class Object
def method_missing(m, *args, &blk) # `m` is the name of the method
if m[0] == '_' and respond_to? m[1..-1] # if it starts with '_' and the object
Mock.new(self.send(m[1..-1])) # responds to the rest wrap it.
else # otherwise throw exception or use
super # object specific method_missing
end
end
end
class Mock < BasicObject
def initialize(me)
super()
@me = me
end
def method_missing(m, *args, &blk)
if m[-1] == '_' # If method ends with '_'
# If @me isn't nil call m without final '_' and return its result.
# If @me is nil then return `nil`.
@me.send(m[0...-1], *args, &blk) if @me
else
@me = @me.send(m, *args, &blk) if @me # Otherwise call method on `@me` and
self # store result then return mock.
end
end
end
何が起こっているのかを説明するために:下線付きのメソッドを呼び出すと、モックモードがトリガーされ、の結果は自動的にオブジェクト_meth
にラップされます。Mock
そのモックでメソッドを呼び出すと、そのモックがanil
を保持していないかどうかをチェックし、メソッドをそのオブジェクト(ここでは@me
変数に格納されています)に転送します。次に、モックは元のオブジェクトを関数呼び出しの結果に置き換えます。呼び出すmeth_
と、モックモードが終了し、の実際の戻り値が返されますmeth
。
これにより、次のようなAPIが可能になります(アンダースコアを使用しましたが、実際には何でも使用できます)。
fizz._buzz.foo.bum.yum.bar_
残忍なモンキーパッチアプローチ
これは本当に厄介ですが、エレガントなAPIを可能にし、アプリ全体のエラー報告を必ずしも台無しにするわけではありません。
class NilClass
attr_accessor :complain
def method_missing(*args)
if @complain
super
else
self
end
end
end
nil.complain = true
このように使用します:
nil.complain = false
fizz.buzz.foo.bar
nil.complain = true