您可以将 "next" 传回调用当前函数的函数吗?

Can you pass a "next" back to the function that called the current function?

我有一系列嵌套的 each 循环遍历卡片列表。这些循环调用其他子函数,测试是否满足某些条件以便继续。

def card_handler
    cards.each do |card|

      #some non-relevant code is here on my end

      already_sent?
    end
  end


  def already_sent?
    # allows for checking if different emails have been sent on the same card

    if list_action == 147
      a_s_helper(p1_label)
    elsif list_action == 146
      a_s_helper(p2_label)
    elsif list_action == 145
      a_s_helper(p3_label)
    end
  end

  def a_s_helper(label)

    if card::card_labels.include? label
    # if the card already has the label, I want to log the error and return all the way to the next card in the iteration

    puts '\n Order info: \n id: #{id} \n Email already sent'

    next 
    # doesn't work


    else
      real_id?
    end
  end

就像我在 a_s_helper 的评论中所说的那样,如果卡片已经有标签,我想记录错误并 return 一直到迭代中的下一张卡片。我从当前设置中收到“下一个无效”错误。

有没有办法 return 一个 next 回到父函数或循环?

next 仅在循环的直接上下文中有效。一旦你调用了一个方法,你就不再直接处于那个循环上下文中。您不能像这样使用 next 来短路外环。

您有两个选择:

  1. Return 来自谓词函数的状态(这是你应该做的,来自谓词!)并基于这些使循环短路,或者
  2. 使用 Ruby 的 catch...throw 构造(这不是它的 raise/rescue 异常处理程序,而是 something like a block-scoped GOTO statement

选项 1:Return查看状态。这是最合适的方法,IMO。谓词方法(那些以 ? 结尾的方法)通常应该 return 布尔值并且是幂等的(也就是说,应该没有副作用,例如记录语句)。它们通常用于提出 yes/no 问题。根据这个问题决定做什么最好不在他们的范围之内。

def card_handler
  cards.each do |card|
    #some non-relevant code is here on my end
    if already_sent?
      puts '\n Order info: \n id: #{id} \n Email already sent'
      next
    end
  end
end


def already_sent?
  case list_action
  when 145
    a_s_helper(p3_label)
  when 145
    a_s_helper(p2_label)
  when 147
    a_s_helper(p1_label)
  end
end

def a_s_helper(label)
  card::card_labels.include? label
end

这会导致您的助手 return 循环的真值或假值,它可以决定记录消息并转到下一次迭代。

选项 2:catch...throw

def card_handler
  cards.each do |card|
    # Put all your code that should nomally run inside the catch block. If
    # the message :email_sent is thrown, then Ruby will zip up the stack and
    # resume execution at the end of the block. This will skip any unexecuted
    # code in the block, essentially terminating the execution.
    catch :email_sent do
      already_sent?
    end
  end
end


def already_sent?
  # ...
end

def a_s_helper(label)
  # ... 
  throw :email_sent if card::card_labels.include? label
  # ... 
end

您可能会想使用选项 2,因为它不需要对方法构造进行仔细控制,但它非常接近被广泛认为是反模式的 exceptions as flow control(它本质上是一个稍微更花哨的 GOTO,这因使代码难以阅读和调试而臭名昭著)。如果你可以简单地 return 从你的助手那里得到一个状态并决定是否继续循环,你应该这样做。

我想展示我是如何最终为看到这个问题的人实施从@Chris-heald 那里得到的解决方案的。我让它更紧凑一点。这是我最终使用的代码:

  def card_handler
    cards.each do |card|
      real_id?
      puts "real_id? : #{real_id?}"
      next if !(real_id?)

      needs_email?
      puts "needs_email? : #{needs_email?}"
      next if !(needs_email?)

      get_email_info
    end
  end


  def needs_email?
    case list_action
    when 147
      !(card::card_labels.include? p1_label::id)
    when 146
      !(card::card_labels.include? p2_label::id)
    when 145
      !(card::card_labels.include? p3_label::id)
    else
      false
    end
  end

  def real_id?
    id != 0 ? true : false
  end

  def get_email_info
    #more stuff
  end