【问题标题】:Can you pass a "next" back to the function that called the current function?您可以将“下一个”传递回调用当前函数的函数吗?
【发布时间】:2021-09-22 22:43:12
【问题描述】:

我有一系列嵌套的 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 中的评论中所说,如果卡片已经有标签,我想记录错误并一直返回到迭代中的下一张卡片。我从当前设置中收到“Invalid next”错误。

有没有办法将next 返回到父函数或循环?

【问题讨论】:

    标签: ruby-on-rails ruby oop foreach each


    【解决方案1】:

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

    你有几个选择:

    1. 从您的谓词函数返回状态(这是您应该做的,从谓词!)并根据这些使循环短路,或者
    2. 使用 Ruby 的 catch...throw 构造(不是其引发/救援异常处理程序,而是 something like a block-scoped GOTO statement

    选项 1:返回状态。这是最合适的方法,IMO。谓词方法(以 ? 结尾的方法)通常应该返回一个布尔值并且是幂等的(也就是说,应该没有副作用,例如记录一条语句)。它们通常用于询问是/否问题。理想情况下,根据该问题决定做什么应该超出他们的范围。

    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
    

    这会导致您的助手向您的循环返回一个真或假值,这可以决定记录一条消息并进入下一次迭代。

    选项 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,它因使代码难以阅读和调试)。如果你可以简单地从你的助手返回一个状态并根据它决定是否继续循环,你应该这样做。

    【讨论】:

    • 谢谢!我最终使用了第一种方法。我应该意识到这会奏效。一般来说,我对 ruby​​ 和 oop 比较陌生。也感谢您指出最佳做法。
    【解决方案2】:

    我想展示我最终是如何实施从@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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-12-14
      • 2013-01-20
      • 2017-05-20
      • 1970-01-01
      • 2011-06-09
      • 2015-09-20
      • 2016-11-17
      相关资源
      最近更新 更多