链式方法调用Ruby

时间:2015-04-18 05:00:35

标签: ruby

在Ruby中有“优势继续”的优雅方式吗?像这样:

method1(a, b)
 .and_then(method2)
 .and_then(method3)
 .fail { |x| 'something is wrong'}

5 个答案:

答案 0 :(得分:5)

我认为没有办法完全按照您展示的方式进行操作,但您可以使用beginrescue块。

所以代码看起来像这样:

begin
    method1(a,b).method2.method3
rescue
    p "something is wrong"
end

在这三种方法中的任何一种方法中,只需调用

即可引发某种异常
raise "Something is Wrong"

这将停止执行并运行救援块。如果要从发生raise调用的执行上下文中获取某种数据,则需要实现自己的错误类型或使用现有类型。如果您希望这样做,救援声明需要更改如下

rescue => e

或者如果您有类型

rescue ArgumentError => e

这有点复杂,所以可以找到好的文章here

这仅适用于您实际调用的方法raise例外情况。

答案 1 :(得分:4)

使用tap,您可以“点击”方法链,以便对链中的中间结果执行操作。在tap区块内,您可以放置​​条件。

 method1(a, b).tap{|o| o.method2 if cond1}.tap{|o|o.method3 if cond2}

答案 2 :(得分:1)

如果您愿意保持开放的心态(想想方法返回值是成功还是失败),您可以这样做

https://github.com/pzol/deterministic

def works(ctx)
  Success(1)
end

def breaks(ctx)
  Failure(2)
end

def never_executed(ctx)
  Success(99)
end

Success(0) >> method(:works) >> method(:breaks) >> method(:never_executed) # Failure(2)

甚至几乎可以使用问题中的确切伪语法!

require 'deterministic'

method1 = proc { |a, b| Deterministic::Result::Success(a * b) }
method2 = proc { Deterministic::Result::Success(2) }
method3 = proc { Deterministic::Result::Failure(3) } # error here, or whatever
error_handler = proc { |x| puts 'something is wrong'; Deterministic::Result::Success('recovered') }

res = method1.call(5, 6).
           and_then(method2).
           and_then(method3).
           or_else(error_handler)
res # => Success("recovered")
# >> something is wrong

答案 3 :(得分:0)

通过"失败",我假设您的意思是其中一个链式方法返回一个对象(例如nil),其方法在链中不包含以下方法。例如:

def confirm_is_cat(s)
  (s=='cat') ? 'cat' : nil
end

然后:

confirm_is_cat('cat').upcase
  #=> "CAT" 
confirm_is_cat('dog').upcase
  # NoMethodError: undefined method `upcase' for nil:NilClass

我们希望在第一种情况下返回"CAT",但要避免在第二种情况下引发异常。

一种方法是捕获异常,这已在其他答案中提及。有时可见的另一种方法是使用Enumerable#reduce(又名inject)。我不能用这种方法提供一般的解决方案,但我会举一个例子,告诉你一般的想法。

假设我们向Fixnum添加了四种方法。如果接收者是特定值(由方法名称指示),则每个方法返回nil;否则它返回其接收器:

class Fixnum
  def chk0; (self==0) ? nil : self; end
  def chk1; (self==1) ? nil : self; end
  def chk2; (self==2) ? nil : self; end
  def chk3; (self==3) ? nil : self; end
end

我们希望从给定的整数n开始并计算:

n.chk0.chk1.chk2.chk3

但如果chk0chk1chk2返回nil,请停止计算。我们可以这样做:

meths = [:chk0, :chk1, :chk2, :chk3]

def chain_em(meths,n)
  meths.reduce(n) { |t,m| t && t.send(m) }
end

chain_em(meths,0)
  #=> nil 
chain_em(meths,1)
  #=> nil 
chain_em(meths,2)
  #=> nil 
chain_em(meths,3)
  #=> nil 
chain_em(meths,4)
  #=> 4

现在假设我们还想确定哪个方法返回nil,如果有的话(chk3除外)。我们可以相应地修改方法:

def chain_em(meths,n)
  last_meth = ''
  meths.reduce(n) do |t,m|
    if t.nil?
      puts "#{last_meth} returned nil"
      return nil
    end
    last_meth = t
    t && t.send(m)
  end
end

chain_em(meths,0)
  #-> chk0 returned nil
  #=> nil 
chain_em(meths,1)
  #-> chk1 returned nil
  #=> nil 
chain_em(meths,2)
  #-> chk2 returned nil
  #=> nil 
chain_em(meths,3)
  #=> nil 
chain_em(meths,4)
  #=> 4 

答案 4 :(得分:0)

默认情况下,Ruby中没有这样的语法糖,但如果像这样的结构对你的项目有益,你可以将它添加到你自己的类中。在实施常规异常处理之前,花一些时间考虑它是否合适。

在下面的解决方案中,我向对象添加了一个标志,指示步骤是否失败,然后如果抛出异常则将其设置为true。在方法链的末尾,failed?方法检查它是否为true,如果提供则生成block。您可以根据您想要的任何条件设置标记。

请注意,Ruby中没有类范围的异常处理(并且可能有充分的理由),因此您需要为您认为相关的任何方法添加处理程序。

class Thing
  def initialize(name)
    @failed = false
    @name = name
  end

  def change
    begin
      @name.capitalize!
    rescue
      @failed = true
    end
    self
  end

  def failed?
    yield self if @failed && block_given?
    @failed
  end
end

thing = Thing.new(5)
# This will fail, because a Fixnum doesn't respond to capitalize!
thing.change.fail? { puts "Ooops." }
# Ooops.

您还可以将功能提取到module中,并将其用作任何需要此行为的类的混合:

module Failable
  @failed = false

  def failed?
    yield self if @failed && block_given?
    @failed
  end

  def fail!
    @failed = true
  end
end

class Thing
  include Failable

  def initialize(name)
    @name = name
  end

  def change
    begin
      @name.capitalize!
    rescue
      fail!
    end
    self
  end
end