【问题标题】:Ruby: Raise error in Module if class method not foundRuby:如果找不到类方法,则在模块中引发错误
【发布时间】:2011-10-16 03:07:36
【问题描述】:

我想在模块中放入一些代码,如果未定义某些方法,则会引发错误。 这个模块依赖于这个方法的外部定义,因为这个方法的实现对于所有的类都是不同的。此代码将帮助开发人员及早知道他们忘记实现该方法,而不是何时尝试使用模块的功能。

module MyModule
  def self.included(klass)
    raise "MyModule: please `def my_method` on #{klass}" unless klass.respond_to?(:my_method)
  end
end 

如果未定义方法,我很容易在模块的包含定义中引发错误,但是由于大多数模块都包含在文件的顶部,因此我需要的方法很可能是在类中定义的,但不是在我之前模块包括在内。

class MyClass
  include MyModule
  def self.my_method
    # ...
  end
end

这仍然会引发错误:(

只有当方法确实没有在类定义中定义时才可能引发错误?几乎需要一个 class.onload 回调。如果没有,关于如何减轻程序员可能包含我们的模块而不定义此所需方法的可能性的任何其他想法?

【问题讨论】:

    标签: ruby module include metaprogramming


    【解决方案1】:

    听起来您想使用method_missingdefine_method

    如果您使用method_missing,请不要忘记:

    • 请致电super 处理未处理的案件。
    • 也实现respond_to?方法

    看看this question,加上thisthat

    更新:

    听起来目标是像 Java 或 c++ 那样进行静态方法检查。这在 ruby​​ 中并没有真正的意义 :-(

    因为在红宝石中:

    • 对象的每个实例都有自己的特征类。给定的对象可能在运行时混合了必要的方法。所以仅仅因为Foo在类加载时没有方法是没有意义的。
    • RoR 等框架挂钩 method_missing 并动态创建数据库查询方法所需的方法,因此该方法在需要时可能存在(或不存在)。

    关于“加载类”:真正执行了类定义。试试这个:

    class Foo 
      p "Hi"
    end
    

    您将在第一次且仅在第一次使用 Foo 时看到“Hi”。这就是devise 之类的东西如何发挥作用。

    class User < ActiveRecord::Base
      # **CALL 'devise' method**
      devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
    
      # **CALL attr_accessible method**
      attr_accessible :email, :password, :password_confirmation
    end
    

    所以也许按照私人约定让开发人员在相关类的底部添加一个check_class 方法调用?

    我理解其意图,但似乎与 ruby​​ 的设计工作方式作斗争。

    作为一个主要是 Java 的人,我很感激这种挫败感。让我猜猜:重复的代码被推到生产环境中但缺少方法的案例? :-P

    更新2:

    wrt onload 在 ruby​​ 中,除非使用冻结的类,否则会一直定义新方法。 (或者一个实例可以获得仅为该实例定义的新方法。)因此检查方法的不存在只是一种快照检查,而不是像静态语言带来的明确检查。这是 ruby​​ 自己的Halting problem

    【讨论】:

    • 我熟悉特征类,是的,我正在尝试做一些静态方法检查之类的事情。我的一位同事指出,我们可以将这种期望转移到实现 MyModule 的模型的测试中。我们可以有一个宏acts_as_my_module,我们可以将它与所有模型测试/规范混合。仍然不会大声抱怨,并且需要额外的程序员奉献精神,但最红宝石风格
    • 如果你使用 rspec,你可以这样做relishapp.com/rspec/rspec-core/v/2-6/dir/example-groups/…
    • 考虑到所有因素,您可能确实需要一个作为部署的一部分运行并在出现此类问题时停止部署的工具。听起来你已经知道了,但希望有一个替代方案:-)
    • 不要将super 用于未处理的案例。在覆盖method_missing 时在ruby 中使用super 将尝试前往祖先链中的下一个类或mixin 等,并且可能会在那里找到您的方法名称。相反,请执行raise NoMethodError。这是一个更安全的解决方案(来自一个企业 Rails 开发人员,他刚刚与使用 super 的边缘案例作斗争)。
    【解决方案2】:

    如何声明一个具有该名称的方法,这只会引发错误,以确保用户重新定义该方法?

    module MyModule
      def my_method
        raise "Please implement me"
      end
    end
    
    
    class MyClass 
      include MyModule
      def my_method
        # do something
      end
    end
    

    【讨论】:

    • 我考虑过这一点,尽管在您尝试调用该方法之前您仍然不会感到痛苦。
    • 如果你完全省略定义,它几乎会产生相同的效果。事实上,它会更好,因为它不会引发一个通用的非描述性RuntimeError 异常,而是引发一个NoMethodError 异常,它告诉你几乎所有你需要知道的信息:一种预期会存在的方法,但没有。这是惯用的 Ruby 方式,例如 EnumerableComparable 就是这样做的。
    • 我用这种方式制作了“抽象类”。自定义错误消息允许您在 raise 上方的 cmets 中说明为什么需要覆盖该方法的理由/原因。
    • 我喜欢这种美!
    【解决方案3】:

    假设您的程序在启动时需要所有文件并且不使用任何 autoload 等,那么您可以在需要所有内容之后但在程序实际启动之前使用类似以下内容:

    classes_to_check = Object.constants.find_all do |const|
      klass = Object.const_get(c)
      klass.ancestors.include?(MyModule) if klass.kind_of?(Module)
    end
    
    classes_to_check.each do |klass|
      raise "MyModule: please `def my_method` on #{klass}" \
        unless klass.respond_to?(:my_method)
    end
    

    但是,我个人总是使用 Dogbert 的解决方案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-05-25
      • 1970-01-01
      • 2017-07-24
      • 2020-10-30
      • 1970-01-01
      • 2018-12-30
      • 1970-01-01
      相关资源
      最近更新 更多