我有以下模块和类:
module MyModule
def self.included base
base.extend(ClassMethods)
end
module ClassMethods
attr_reader :config
# this method MUST be called by every class which includes MyModule
def configure &block
@config = {}
block.call(@config) if block
end
end
end
class A
include MyModule
configure do |config|
# do sth with the config
end
end
class B
include MyModule
end
是否可以检查是否调用了模块中的configure
方法?这意味着A
应该没问题,但B
应该抛出错误,因为它从未调用configure
。
我在self.included
回调中尝试过,但之后会调用configure
方法。
答案 0 :(得分:0)
从技术上讲,@ nnd是对的,可以在评估完课程后调用它。但是,听起来你想要的是验证在类体定义中的某个点已经调用了configure方法(这也将允许已包含的任何模块完成评估,因此如果模块包含调用configure方法,一切都很好。
我可以在这里找到最接近解决这种情况的解决方案:
https://github.com/jasonayre/trax_core/blob/master/lib/trax/core/abstract_methods.rb
上面的代码是ruby的一个抽象方法实现,从技术上来说并不是你所要求的(你在谈论调用方法,抽象方法是关于检查子类定义它),但同样的技巧我使用那里可以应用。
基本上,我使用ruby的跟踪点库来监视要触发的类定义的结束,此时它会触发一个事件,我检查方法是否已定义,如果没有则抛出错误。因此,只要您从类中调用WITHIN,就可以使用类似的解决方案。像(未经测试)的东西:
module MustConfigure
extend ::ActiveSupport::Concern
module ClassMethods
def inherited(subklass)
super(subklass)
subklass.class_attribute :_configured_was_called
subklass._configured_was_called = false
trace = ::TracePoint.new(:end) do |tracepoint|
if tracepoint.self == subklass #modules also trace end we only care about the class end
trace.disable
raise NotImplementedError.new("Must call configure") unless subklass._configured_was_called
end
end
trace.enable
subklass
end
def configure(&block)
self._configured_was_called = true
#do your thing
end
end
end
class A
include MustConfigure
end
class B < A
configure do
#dowhatever
end
end
class C < B
#will blow up here
end
或者,您可以尝试使用我的库中的InheritanceHooks模块并跳过手动跟踪点处理:
class BaseClass
include::Trax::Core::InheritanceHooks
after_inherited do
raise NotImplementedError unless self._configure_was_called
end
end
注意,虽然我现在正在生产中使用这种模式,并且一切都在MRI上运行良好,因为tracepoint是一个为调试而构建的库,但在使用jruby时存在一些限制。 (现在它会中断,除非你传递了jruby调试标志) - 我开了一个问题,试图在不必明确启用调试的情况下添加tracepoint。
答案 1 :(得分:0)
以下是基于您的结构的示例。 它会在实例化时检查是否已调用configure,并且会自动使用您在其上添加MyModule的任何类。
它检查每个实例化是否已调用configure,但它只是检查一个布尔值,因此它不会对性能产生任何影响。
我找了一种方法来取消定义特定类的前置方法,但没有找到任何东西。
module MyModule
def self.prepended base
base.extend(ClassMethods)
end
module ClassMethods
attr_reader :config
def configured?
@configured
end
def configure &block
@configured = true
@config = {}
block.call(@config) if block
end
end
def initialize(*p)
klass = self.class
if klass.configured? then
super
else
raise "Please run #{klass}.configure before calling #{klass}.new"
end
end
end
class A
prepend MyModule
configure do |config|
config[:a] = true
puts "A has been configured with #{config}"
end
end
class B
prepend MyModule
end
A.new
puts "A has been instantiated"
puts
B.new
puts "B has been instantiated"
# =>
# A has been configured with {:a=>true}
# A has been instantiated
# check_module_class.rb:27:in `initialize': Please run B.configure before calling B.new (RuntimeError)
# from check_module_class.rb:50:in `new'
# from check_module_class.rb:50:in `<main>'