我正在构建一个插件,允许开发人员通过类定义中的简单声明(遵循正常的 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/