【问题标题】:Ruby Metaprogramming Q: Calling an external class method on after_saveRuby 元编程 Q:在 after_save 上调用外部类方法
【发布时间】:2014-11-14 04:00:38
【问题描述】:

我有以下课程:

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

Post 是 ActiveRecord,Award 有 can_award 吗?类方法,它接受一个 post 对象并检查它是否符合某些条件。如果是,它会更新 post.owner.awards。

我知道我可以使用观察者模式来做到这一点(我对其进行了测试并且代码运行良好)。但是,这需要我向模型添加额外的代码。如果可能的话,我根本不想碰这个模型。我想做的是像这样运行 Award 检查(触发器将在类加载时调用):

class AwardOne < AwardBase
  trigger :post, :after_save

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

上面代码的意图是自动添加AwardOne.can_award?到 Post 的 after_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>):

【问题讨论】:

  • 请提供此错误的堆栈跟踪。
  • 您可以使用 ActiveSupport::Concerns,类似于此处接受的答案:stackoverflow.com/questions/12084234/…
  • 但您仍然必须将其包含在您的模型中!!不过我不太确定
  • 我怀疑这条线 model_class = klass.to_class 是否真的有效。 Symbol 中没有to_class 方法可以将其更改为类。你确定它是否有效?
  • @User089247 我已经给符号添加了猴子补丁到_class。它只是做一个 to_s.classify.constantize

标签: ruby-on-rails ruby metaprogramming


【解决方案1】:

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

想象一下这是一个更大的项目,而我是作为该项目的新开发人员来的。我正在调试一个帖子无法正确保存的问题。 自然,我会先过一遍模型的代码。我什至可能会浏览帖子控制器的代码。这样做不会有迹象表明保存帖子涉及第二类。我更难弄清楚问题出在哪里,因为我什至不知道 AwardOne 的代码也参与其中。 在这种情况下,实际上最好在控制器中执行此操作。这是最容易调试和理解的地方(因为模型已经有足够的职责并且通常更大)。

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

如果没有别的,那么至少可以通过在 Post 模型中声明一些东西来优雅地做到这一点。例如,通过在ActiveRecord::Base 上注册一个awardable 类方法。但最好的方法可能是在控制器中或通过服务对象进行。 不是AwardOne 的责任来处理如何保存Post

【讨论】:

    【解决方案2】:

    因为您将 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 "
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-04-16
      • 1970-01-01
      • 2012-03-18
      • 2015-06-03
      • 1970-01-01
      • 2013-09-24
      • 2021-01-30
      • 2019-09-25
      相关资源
      最近更新 更多