Ruby Metaprogramming问:在after_save上调用外部类方法

时间:2014-11-14 04:00:38

标签: ruby-on-rails ruby metaprogramming

我有以下课程:

class AwardBase
class AwardOne < AwardBase
class Post < ActiveRecord::Base

The Post是一个ActiveRecord,奖励有一个can_award? class方法,它接受一个post对象并检查它是否符合某些条件。如果是,则更新post.owner.awards。

我知道我可以使用Observer模式(我测试它并且代码工作正常)。但是,这需要我向模型添加其他代码。如果可能的话,我根本不想触摸模型。我想做的是像这样运行奖励检查(触发器将在课程加载时调用):

class AwardOne < AwardBase
  trigger :post, :after_save

  def self.can_award?(post)
    ...
  end
end

上述代码的意图是它应该自动添加AwardOne.can_award?发布的post_save方法

基本上我要做的就是让trigger来电相当于:

class Post < ActiveRecord::Base
  after_save AwardOne.can_award?(self)
  ...
end

基本上是:

class Post < ActiveRecord::Base
  after_save :check_award

  def check_award
    AwardOne.can_award?(self)
  end
end

如何在不修改Post类的情况下执行此操作?


这是我所做的(似乎不起作用):

class AwardBase

  def self.trigger (klass, active_record_event)
    model_class = klass.to_class

    this = self
    model_class.instance_eval do
      def award_callback
        this.can_award?(self)
      end
    end

    model_class.class_eval do
      self.send(active_record_event, :award_callback)
    end
  end

  def self.can_award? (model)
    raise NotImplementedError
  end
end

上述代码失败并显示错误:

NameError (undefined local variable or method `award_callback' for #<Post:0x002b57c04d52e0>):

2 个答案:

答案 0 :(得分:1)

您应该考虑为什么要这样做。我认为它比使用观察者模式更糟糕。你违反了最不惊讶的原则(也称为最小惊讶原则)。

想象一下,这是一个更大的项目,我作为这个项目的新开发人员而来。我正在调试Post无法正确保存的问题。 当然,我将首先介绍模型的代码。我甚至可能会查看posts控制器的代码。这样做没有迹象表明保存帖子涉及第二类。由于我不知道AwardOne的代码是否涉及,因此我很难弄清楚问题是什么。 在这种情况下,实际上最好在控制器中执行此操作。这是最容易调试和理解的地方(因为模型已经有足够的责任并且通常更大)。

这是元编程的常见问题。大多数情况下,最好避免它,因为最不惊讶的原则。由于您需要调试一些问题,当您回到此代码时,您将很高兴从现在起一年内不使用它。你会忘记你做过什么“聪明”的事情。如果你没有一个很好的理由,那么只要坚持既定的惯例,它们就是有原因的。

如果没有别的,那么至少可以通过在Post模型中声明一些东西来找到一种优雅的方法。例如,在awardable上注册ActiveRecord::Base类方法。但最好的方法可能是在控制器中或通过服务对象。 AwardOne负责处理Post应如何保存!{/ p>

答案 1 :(得分:0)

因为您要将award_callback添加为class方法。我打赌如果你grep类方法将会注册。

所以改变你的代码如下。它应该工作正常。

model_class.class_eval do ## Changed to class_eval
  def award_callback
    this.can_award?(self)
  end
end

如果听起来令人困惑,请举一个详细的例子。

class Test
end

Test.instance_eval do
  def class_fun
    p "from class method "
  end
end

Test.class_eval do
  def instance_fun
    p "from instance method "
  end
end


Test.methods.grep /class_fun/
# => [:class_fun]

Test.instance_methods.grep /instance_fun/
# => [:instance_fun]

Test.class_fun
# => "from class method "

Test.new.instance_fun
# => "from instance method "