2

我有一个定义一组条件的接口。它是与其他模型一起使用的几个此类接口之一。

这些条件将由消息队列处理程序调用以确定警报的完整性。所有的警报调用都是一样的,所以我试图通过将条件抽象到它们自己的方法中来稍微干掉入队调用(我质疑方法是否是正确的技术)。我认为通过这样做,我将能够测试这些条件中的每一个。

class Loan
  module AlertTriggers
    def self.included(base)
      base.extend           LifecycleScopeEnqueues

      # this isn't right
      Loan::AlertTriggers::LifecycleScopeEnqueues.instance_method.each do |cond|

        class << self
          def self.cond
            ::AlertHandler.enqueue_alerts(
              {:trigger => Loan.new}, 
              cond
            )
          end
        end

      end
    end
  end

  module LifecycleScopeEnqueues
    def student_awaiting_cosigner 
        lambda { |interval, send_limit, excluding|
          excluding ||= ''
          Loan.awaiting_cosigner.
            where('loans.id not in (?)', excluding.map(&:id) ).
            joins(:petitions).
            where('petitions.updated_at > ?', interval.days.ago).
            where('petitions.updated_at <= ?', send_limit.days.ago) 
        }
    end
  end

我已经考虑过替代方案,其中每种方法都像一个范围。在那条路上,我不确定如何成为, 和AlertHandler的来源interval,它在调用它时传递给块/proc。 send_limitexcluding


有人建议我(离线)范围是 lambda,因此可能是更合适的解决方案 - 根据@BorisStitnicky 推断,钳子可以用作锤子,但不应该。我也愿意接受这方面的答案。

4

2 回答 2

2

你知道,这可能不是你寻求的答案,但我会尽力而为。最好这样回答而不是放弃评论。在您的代码中,您正在做一些非常不寻常的事情。首先,您在类中定义一个模块。我以前从未做过或见过这个,以至于我打了 irb 来尝试一下。其次,您正在定义一个返回 lambda 的方法。这让我想起了我刚学习 Ruby 时所做的很多事情。Lambda 有非常具体的应用,通常应尽可能避免采用这种形式。如果你想像这样使用 lambda,至少将它们分配给一个变量,或者更好的是,一个常量:

STUDENT_AWAITING_COSIGNER = lambda { |interval, send_limit, excluding|
  # do your SQL magic
}

我在理解您的词汇时遇到了麻烦:特别是,我不确定您所说的“范围”是 Ruby 范围还是其他类型的范围。

但就个人而言,我认为你根本不应该使用 lambdas。我敢说,你的代码需要的不仅仅是干一点。我认为你不应该在一个类中设置子命名空间。你为什么不使用例如。实例变量?此外,公共类方法只是蛋糕上的一颗樱桃。首先解决没有它们的问题,然后您可以决定添加它们以使您的界面更方便。总而言之,我会简单地按照以下方式做一些事情:

class Loan
  attr_reader :alerts

  def initialize( whatever_options )
    @alerts = Array( whatever_options[ :alerts ] )
  end

  def check_alerts
    @alerts.each &:test
  end
end

# Then I would set up an alert class:
class Alert
  def test( interval, send_limit, excluding = '' )
    Loan.awaiting_cosigner.
      where('loans.id not in (?)', excluding.map(&:id) ).
      joins(:petitions).
      where('petitions.updated_at > ?', interval.days.ago).
      where('petitions.updated_at <= ?', send_limit.days.ago) 
  end
end
# I used your prescription statically, if you have different kind
# of alerts, you would have to make the class sufficiently flexible
# to handle them all.

# and then I would eg. supply alerts to a Loan upon instantiation
# (this can be also done later, if you make it so)
my_little_loan = Loan.new( alerts: Alert.new )
# and when the time comes to check whether the alerts alert or not:
my_little_loan.check_alerts

显然,这只是我谦虚地认为应该在 Ruby 中简单地解决这类问题的一个大纲。您需要付出自己的努力,使其在您的特定复杂情况下为您工作。

于 2012-09-05T22:06:02.427 回答
1

处理此问题的一种方法是使用域的其他模型/部分所期望(或显示给)的名称空间(在模块内)。

在这种情况下AlertHandler不需要传递一个块。相反,它可以知道命名空间的存在(相反,它LifecycleScopeEnqueues可能读起来更可操作Lifecycle_EnqueuingScopes)。因此,无论内部发生什么AlertHandler.enqueue_alerts

class AlertHandler
  def enqueue_alerts(options, condition)
    trigger = options[:trigger]
    handler = options[:trigger_handler].capitalize

    interval, send_limit, excluding = handler_metrics(handler, condition)

    range = "#{trigger.class.name}".constantize.send(condition, [interval, send_limit, excluding])

    # do other things
  end
end

所有这些范围的警报仍然可以通过一种反射方法“排队”(与问题中的代码混合)

class Loan
  module AlertTriggers
    def self.included(base)
      base.extend     ClassMethods
    end

    module  ClassMethods
      def enqueue_lifecycle_reminders
        Loan::AlertTriggers::LifecycleScopeEnqueues.instance_method.each do |cond|
          ::AlertHandler.enqueue_alerts(
              {:trigger => Loan.new}, 
              cond
          )
        end
      end
    end
  end
end

这种方法还允许通过以下方式测试范围/条件Loan::AlertTriggers::LifecycleScopeEnqueues

  • 每个方法
  • 鸭子打字
于 2012-08-29T13:29:44.537 回答