ruby - 如何设置 Hook 以在 Ruby 类定义的末尾运行代码?

标签 ruby plugins loading

我正在构建一个插件,允许开发人员通过类定义中的简单声明(遵循正常的 acts_as 模式)向类添加各种功能。

例如,使用插件的代码可能如下所示

class YourClass
  consumes_my_plugin option1: :value1, specific_method_to_use: :your_method
end

我的问题出现是因为我想错误检查为 :specific_method_to_use 参数提供的值是否作为方法存在,但代码通常组织和加载的方式尚不存在该方法。

我的插件中的代码暂定如下所示:

module MyPlugin
  extend ActiveSupport::Concern

  module ClassMethods
    def consumes_my_plugin(options = {})
      raise ArgumentError.new("#{options[:specific_method_to_use]} is not defined") if options[:specific_method_to_use].present? && !self.respond_to?(options[:specific_method_to_use])
    end
  end
end

这会起作用:

class YourClass
  def your_method; true; end

  consumes_my_plugin option1: :value1, specific_method_to_use: :your_method
end

但这是大多数人编写代码的方式,它不会:

class YourClass
  consumes_my_plugin option1: :value1, specific_method_to_use: :your_method

  def your_method; true; end
end

如何在 YourClass 加载时失败?然后我希望它出错,而不是在运行时出现 NoMethodError。我可以推迟执行引发 ArgumentError 的行,直到加载整个类,或者做一些其他聪明的事情来实现这一点吗?

最佳答案

使用 TracePoint 跟踪您的类何时发送 :end 事件。


通用解决方案

此模块可让您在任何类中创建一个 self.finalize 回调。

module Finalize
  def self.extended(obj)
    TracePoint.trace(:end) do |t|
      if obj == t.self
        obj.finalize
        t.disable
      end
    end
  end
end

现在您可以扩展您的类并定义 self.finalize,它将在类定义结束时立即运行:

class Foo
  puts "Top of class"

  extend Finalize

  def self.finalize
    puts "Finalizing #{self}"
  end

  puts "Bottom of class"
end

puts "Outside class"

# output:
#   Top of class
#   Bottom of class
#   Finalizing Foo
#   Outside class

OP问题的具体解决方案

以下是如何将 TracePoint 直接安装到您预先存在的模块中。

require 'active_support/all'

module MyPlugin
  extend ActiveSupport::Concern

  module ClassMethods
    def consumes_my_plugin(**options)
      m = options[:specific_method_to_use]

      TracePoint.trace(:end) do |t|
        break unless self == t.self

        raise ArgumentError.new("#{m} is not defined") unless instance_methods.include?(m)

        t.disable
      end
    end
  end
end

下面的例子证明它按规定工作:

# `def` before `consumes`: evaluates without errors
class MethodBeforePlugin
  include MyPlugin
  def your_method; end
  consumes_my_plugin option1: :value1, specific_method_to_use: :your_method
end

# `consumes` before `def`: evaluates without errors
class PluginBeforeMethod
  include MyPlugin
  consumes_my_plugin option1: :value1, specific_method_to_use: :your_method
  def your_method; end
end

# `consumes` with no `def`: throws ArgumentError at load time
class PluginWithoutMethod
  include MyPlugin
  consumes_my_plugin option1: :value1, specific_method_to_use: :your_method
end

关于ruby - 如何设置 Hook 以在 Ruby 类定义的末尾运行代码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32233860/

相关文章:

ruby - 从哈希中提取键值的简单方法

eclipse - 如何使用org.eclipse.ui.menus向Eclipse Package Explorer上下文菜单项添加子菜单项?

javascript - 如何确定 Firefox 中的所有选项卡均已加载?

javascript - 为什么Unity 2018.2 WebAssembly加载间隔只有0%、90%、100%

c++ - Assimp Faces 都有索引 (0,1,2)

ruby-on-rails - 如何在 Rails 中通过继承获得多态关系?

ruby - 将 ruby​​ 代码存储在 let() 变量中

ruby - 打印文件中出现次数最多的 n 个单词(字符串)

python - 如何从python脚本运行QGIS插件

android - 将 Cordova/Phonegap (3.2.0) 插件更新到项目中