Check if class method is called

I have the following module and classes:

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 

Can I check if the configure method was called from a module? This means that A must be accurate, but B must throw an error because it never caused configure .

I tried it in the self.included , but the configure method is called afterwards.

+6
source share
2 answers

Technically, @ndn is correct, it can be called after class evaluation. However, it looks like you want to verify that the configure method was called at some point in the definition of the class body (this will also enable any modules to complete the evaluation, so if the configure method calls are included in the module, all this is good).

The closest solution I found to solve this problem can be found here:

https://github.com/jasonayre/trax_core/blob/master/lib/trax/core/abstract_methods.rb

The above code is an implementation of abstract methods for ruby, which is technically not what you are asking for (you are talking about calling a method, abstract methods are about verifying that the subclass defined it), but the same trick I used there can be applied.

Basically, I use the ruby ​​trace points library to see how the end of the class definition will work out, after which it fires an event, I check if this method has been defined, and throw an error if not. So while you call configure from WITHIN of your classes, a similar solution may work for you. Something like (not verified):

 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 

Or you can try using the InheritanceHooks module from my library and skip manual checkpoint processing:

 class BaseClass include::Trax::Core::InheritanceHooks after_inherited do raise NotImplementedError unless self._configure_was_called end end 

Note that although I am using this template in production at the moment, and everything works fine on MRI, because tracing is a library created for debugging, there are some limitations when using jruby. (right now it breaks if you do not pass the jruby debug flag) - I again opened the problem, trying to get the added trace point without having to explicitly debug it.

https://github.com/jruby/jruby/issues/3096

0
source

Here is an example based on your structure. It checks when creating an instance if configure was called, and will work automatically with any class on which you added MyModule.

It checks every instance if configure was called, but it just checks the boolean, so it should not affect performance.

I was looking for a way to cancel the preliminary method for a specific class, but did not find anything.

 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>' 
0
source

All Articles