Dynamic override class method in Ruby module - ruby

Is it possible to dynamically override a class method in Ruby?
I have a ErrorHandler module which should dynamically create a class method with a given name and do the same as the method it is overriding:
module ErrorHandler
def self.handle_error_from(method_name)
define_singleton_method(method_name) do |*arguments|
begin
super(*arguments)
rescue
return "Handler return"
end
end
end
end
The above module is prepended to another module.
module AnotherModule
prepend ErrorHandler
ErrorHandler.handle_error_from :create
def self.create(params)
# Code here
end
end
The above example triggers the ErrorHandler but it does not override it with the created method.
I've seen this be done with instance methods, but is there a limitation when it is class methods?

You have a few errors in your code.
With prepend and self in your module you will actually define the wrapper method on your module. You can try this with using your code and add a puts ErrorHandler.methods after the handle_error_from and you will see it has a create method defined. The reason is that self is ErrorHandler in this case.
You need to define the wrapper method after the original method is defined.
See here a full example.
module ErrorHandler
def handle_error_from(method_name)
define_singleton_method(method_name) do |*arguments|
begin
super(*arguments)
rescue
puts "Handler return"
end
end
end
end
class Foo
extend ErrorHandler
def self.create
raise "error"
end
handle_error_from "create"
end
Foo.create

Related

Check if a class method was 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
Is it possible to check, if the configure method from the module was called? This means A should be fine, but B should throw an error, because it never called configure.
I tried it within the self.included callback, but the configure method gets called afterwards.
Technically, #ndn is right, it could be called after the class has been evaluated. However, it sounds like what you want is to validate that the configure method has been called at some point within the class body definition (this will also allow any modules that have been included, to finish evaluating, so if a module include calls the configure method, it's all good as well).
The closest solution I've come up to address this situation can be found here:
https://github.com/jasonayre/trax_core/blob/master/lib/trax/core/abstract_methods.rb
The above code is an abstract methods implementation for ruby, which technically isn't what you're asking (you are talking about calling the method, abstract methods are about checking that a subclass defined it), but the same trick I used there could be applied.
Basically, I'm using ruby's trace point library to watch for the end of the class definition to hit, at which point it fires an event, I check whether the method was defined, and throw an error if not. So as long as you're calling configure from WITHIN your classes, a similar solution could work for you. Something like (not tested):
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 could try using the InheritanceHooks module from my library and skip the manual tracepoint handling:
class BaseClass
include::Trax::Core::InheritanceHooks
after_inherited do
raise NotImplementedError unless self._configure_was_called
end
end
Note, although I am using this pattern in production at the moment, and everything works great on MRI, because tracepoint is a library built for debugging, there are some limitations when using jruby. (right now it breaks unless you pass the jruby debug flag) -- I opened an issue awhile back trying to get tracepoint added in without having to enable debug explicitly.
https://github.com/jruby/jruby/issues/3096
Here's an example based on your structure.
It checks at instantiation if configure has been called, and will work automatically with any class on which you prepended MyModule.
It checks at every instantiation if configure has been called, but it is just checking a boolean so it shouldn't have any performance impact.
I looked for a way to undefine a prepended method for a specific class but didn't 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>'

Ruby: Calling instance method from class method

I want to call some_instance_method from some_class_method. In the following code, the class method is being called from the instance method.
class Foo
def self.some_class_method
puts self
end
def some_instance_method
self.class.some_class_method
end
end
Does what you want to do really make sense? Which instance should the instance method be called on? If it doesn't matter, you can do this:
class Foo
def self.some_class_method
new.some_instance_method
puts self
end
def some_instance_method
self.class.some_class_method
end
end
This will cause an infinite loop, of course.
To call an instance method you need to have that object already instantiated somewhere, you can't call an instance method without an object.
def self.some_class_method
puts self.first.some_instance_method
end
Something like this should work
You need to pass the instance you want to be the recipient of the call to the class method. So the new code is:
class Foo
def self.some_class_method(instance)
instance.some_other_instance_method
puts self
end
def some_instance_method
self.class.some_class_method(self)
end
end
Notice that the class method is calling 'some_other_instance_method'. If the class method were to call the same method that called it, it would enter an endless loop.

Event declarations

I got the following code from #avdi's Ruby Tapas episode for today:
module Eventful
def self.included(other)
other.extend(Macros)
end
def add_listener(listener)
(#listeners ||= []) << listener
end
def notify_listeners(event, *args)
(#listeners || []).each do |listener|
listener.public_send("on_#{event}", *args)
end
end
module Macros
def event(name)
module_eval(%Q{
def #{name}(*args)
notify_listeners(:#{name}, *args)
end
})
end
end
end
class Dradis
include Eventful
event :new_contact
end
class ConsoleListener
def on_new_contact(direction, range)
puts "DRADIS contact! #{range} kilometers, bearing #{direction}"
end
end
dradis = Dradis.new
dradis.add_listener(ConsoleListener.new)
dradis.new_contact(120, 23000)
I understand the concept of events and listeners and the observer pattern, but don't get how/why this syntax is working, and haven't seen it in any manuals. The class Dradis has this:
event :new_contact
At first, I thought that event was a method and :new_contact was an argument so that I would call event on an instance of Dradis, something like:
dradis = Dradis.new
dradis.event
but instead, new_contact is called on an instance of Dradis like:
dradis = Dradis.new
dradis.add_listener(ConsoleListener.new)
dradis.new_contact(120, 23000)
and that triggers the event method in the Macro module.
Can anyone explain why it works like this? calling :new_contact on an instance dradis to trigger the event method?
I didn't watch the episode, but look, it's right there.
module Macros
def event(name)
module_eval(%Q{
def #{name}(*args)
notify_listeners(:#{name}, *args)
end
})
end
end
event is a method which defines another method (new_contact) which calls notify_listeners from Eventful.
and that triggers the event method in the Macro module
Incorrect. That method has finished its work a long time ago and it doesn't get invoked again. It produced a new method using module_eval / def and that new method (new_contact) is what's getting called.
It's important to understand that event method runs only once, when the Dradis class is parsed and loaded. It does not get run on every instantiation of Dradis.
Several separated features of ruby is used: In the line event :new_contact the "evnet" is class method (http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_classes.html#UE).
Usually class methods are defined by:
class A
def A.my_class_method
#code
end
end
A.my_class_method #executing class_method
or
class A
def <<self #usefull when you want delare several class methods
def A.my_class_method
#code
end
end
end
A.my_class_method #executing class_method
In the code the method is included by the module Macros.
The key thing is, that (class method) event is dynamicaly creating new (instance) method (in this case new_contact)
The name of the method is passed as argument to event). And this method providing calling of the listener.
Can anyone explain why it works like this? calling :new_contact on an instance dradis to trigger the event method?
This is by the dynammically created method new_contact which is calling notify_listeners(:#{name}, *args)

Why can't I call include from a class method in ruby?

You can call include to mixin a module with a class in ruby, but it must be done at the beginning of the class definition. Why can't it be done inside a class function? Is there an alternate syntax?
EX:
module UsefulThings
def a() puts "a" end
end
class IncludeTester
include UsefulThings
def initialize
end
end
n = IncludeTester.new
n.a()
^^ This works, but if I change IncludeTester to the following, I get the error "undefined method `include'"
class IncludeTester
def initialize
include UsefulThings
end
end
It can be done in a class method.
This works:
module UsefulThings
def a
puts "a"
end
end
class IncludeTester
def self.mix_in_useful_things
include UsefulThings
end
end
x = IncludeTester.new
IncludeTester.mix_in_useful_things
x.a # => a
But "initialize" is not a class method, it's an instance method.
"new" is a class method. You can think of new as allocating a new object and then calling initialize on it, passing initialize whatever arguments were passed to new.
You can't call include directly in initialize because include is a private method of Class (inherited from Module), not of the newly created IncludeTester instance.
If you want to include a module into a class from an instance method, you have to do something like this:
class IncludeTester
def initialize
self.class.send(:include, UsefulThings)
end
end
It's necessary to use "send" here because include is private method, which means it can only be directly invoked with an implicit receiver (of self).
When you call initialize normally in a class definition, you're actually calling it with an implicit receiver of "self", referring to the class being defined.
This is what is actually happening when you do this:
class IncludeTester
include UsefulThings
end
include is a method from Module, Module is the superclass of Class and so include is a method on Class and that makes it a class method in your IncludeTester. When you do this:
class IncludeTester
def initialize
include UsefulThings
end
end
you're trying to call a class method inside an instance method and Ruby says
`initialize': undefined method `include'
because there is no instance method called include. If you want to call a class method inside an instance method (such as initialize), you'd do this:
def initialize
self.class.include UsefulThings
end
But that won't work because include is a private method; you can get around that with class_eval though:
def initialize
self.class.class_eval {
include UsefulThings
}
end
You would be doing include UsefulThings every single time you instantiated an IncludeTester, aside from not making much sense, it could cause problems if UsefulThings had an included method.
It's actually fully possible to include a module from a class method, like so:
module Stuff
def say_hello
puts "hello"
end
end
class Foo
def self.i_am_a_class_method
include Stuff
end
def i_am_an_instance_method
end
end
You cannot however do that from an instance method, because the include method is only available as a private class method, and therefore not accessible from a Foo.new instance.
You want the extend method:
class IncludeTester
def initialize
extend UsefulThings
end
end
This need not be done within the a method either:
IncludeTester.new.tap { |newTester| newTester.extend(UsefulThings) }

Unable to invoke mixin module method in spec

I have the following module:
# lib/translator/sms_command.rb
module Translator
module SmsCommand
def self.included(klass)
puts "SmsCommand initialized in #{klass}"
end
def alias_command(name)
end
end
end
And the following spec:
# spec/translator/sms_command_spec.rb
require 'spec_helper'
class ExampleCommand
include Translator::SmsCommand
alias_command :x
end
module Translator
describe SmsCommand do
describe "#alias_command" do
it "registers the class with the command registry" do
Translator::SmsCommand.command_registry.should include_key :x
end
end
end
end
Yes #alias_command does nothing at this stage but that's because I'm in the midst of developing it. However, when I run my spec I am seeing...
SmsCommand initialized in ExampleCommand
so the module is certainly being mixed in, however the spec barfs on the alias_command :x line in the ExampleCommand as if the alias_command method is never becoming available.
/spec/translator/sms_command_spec.rb:5:in `<class:ExampleCommand>': undefined method `alias_command' for ExampleCommand:Class (NoMethodError)
I could solve this problem through inheritance though I'd prefer the mixin module. What am I missing?
It's because it's not an include but an extend to access alias_command in your Class method
class ExampleCommand
extend Translator::SmsCommand
alias_command :x
end
You want to define a class method called alias_command, don't you?
In this case you need to extend the class with the module, a simple include will turn the included method into an instance method!
You can do it in a widely accepted way as follows:
module Translator
module SmsCommand
def self.included(klass)
puts "SmsCommand initialized in #{klass}"
klass.extend ClassMethods
end
module ClassMethods
def alias_command(name)
end
end
end
end
This way when you include the module, it will automatically extend the target class with the class methods!

Resources