I have a module I'd like to be able to wrap every method in a class, whether it be an instance method or a class method, but am having a bit of trouble trying to get it to work in both circumstances. Below is what I have right now, it works for wrapping instance methods but calling class methods doesn't seem to work. I assume the module proxy isn't installed correctly for overriding class methods, but I'm not sure what to do to fix that.
I'm limited to ruby 2.7, btw.
module Wrapper
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
module Proxy
end
def wrap_method(name)
puts "wrapping #{name}"
Proxy.define_method(name) do |*args|
puts "in wrap_method, about to call #{name}"
super *args
end
end
def wrap_methods
# wrap any methods that were defined before this was called
self.instance_methods(false).each {|name| wrap_method(name) }
self.singleton_methods(false).each {|name| wrap_method(name) }
# wrap any methods that are defined after this is called
self.class.define_method(:singleton_method_added) do |name|
if respond_to?(:wrap_method)
wrap_method(name)
end
end
self.class.define_method(:method_added) do |name|
if respond_to?(:wrap_method)
wrap_method(name)
end
end
self.prepend(Proxy)
end
end
end
module Top
module Second
class Main
include Wrapper
wrap_methods
def self.first_class_method
puts "in self.first_class_method"
end
def self.second_class_method
puts "in self.second_class_method"
end
def some_instance_method
puts "in some_instance_method"
end
end
end
end
Top::Second::Main.first_class_method
Top::Second::Main.second_class_method
Top::Second::Main.new.some_instance_method
Output:
wrapping first_class_method
wrapping second_class_method
wrapping some_instance_method
in self.first_class_method
in self.second_class_method
in wrap_method, about to call some_instance_method
in some_instance_method
You prepend Proxy to the class itself, and this modifies the method resolution for instances of the class, but not for the class itself.
To achieve the second, you have to
self.singleton_class.prepend(Proxy)
in addition.
Related
I'd like to provide some refinements to a DSL. I'm able to get refinements working with this example:
module ArrayExtras
refine Array do
def speak
puts 'array!'
end
end
end
module MyUniverse
using ArrayExtras
class Thing
def initialize
[1].speak
end
end
end
MyUniverse::Thing.new
This prints out "array!" just fine. But once I introduce instance_eval, the method can't be found:
module MyUniverse
using ArrayExtras
class DSL
def initialize(&block)
instance_eval(&block)
end
end
end
MyUniverse::DSL.new do
[1].speak
end
I get a undefined methodspeak' for [1]:Array (NoMethodError)`
Is there a way to get refinements working within an instance_eval?
Refinements are lexically scoped. You are activating the Refinement in the wrong lexical context. You need to activate it where you are calling the refined method:
module ArrayExtras
refine Array do
def speak
puts 'array!'
end
end
end
module MyUniverse
class DSL
def initialize(&block)
instance_eval(&block)
end
end
end
using ArrayExtras
MyUniverse::DSL.new do
[1].speak
end
# array!
In some cases you can achieve it by using ArrayExtras on the binding.
module MyUniverse
using ArrayExtras
class DSL
def initialize(&block)
block.binding.eval("using ArrayExtras")
instance_eval(&block)
end
end
end
MyUniverse::DSL.new do
[1].speak
end
This will however only work if you are not using your class in an instance, it only works if the binding is a module or class context, otherwise the eval will fail because main.using is permitted only at toplevel .
If you want to refine the ruby core BaseObject, you need to modify it as below.
module ArrayExtras
refine ::Array do
def speak
puts 'array!'
end
end
end
It will be found in top level class.
Is it possible to make this work, without having to include the module at the end of the class and just include it at the top?
module VerboseJob
def self.included(job_class)
class << job_class
alias_method :original_perform, :perform
def perform(*args)
JobLogger.verbose { original_perform(*args) }
end
end
end
end
class HelloJob
include VerboseJob
def self.perform(arg1, arg2)
puts "Job invoked with #{arg1} and #{arg2}"
end
end
What I want to happen, is for HelloJob.perform to actually invoke VerboseJob.perform (which then calls the original method inside a block). Because the module here is included at the top of the class, this doesn't work, since perform isn't yet defined. Moving the include to the end does work, but is there a way that's a bit more forgiving? I like to keep all the included modules at the top of my class definitions.
I'm sort of looking for some method that is called on Module or Class when it has been fully loaded, instead of as it is interpreted by the runtime.
Here is a rather roundabout/hackish way of doing it that I came up with by deferring the definition of the wrapper method until the original method had been defined:
module A
def self.included(base)
base.class_eval do
def self.singleton_method_added(name)
##ran||=false
if name==:perform && !##ran
##ran=true
class<<self
alias_method :original_perform, :perform
def perform(*args)
puts "Hello"
original_perform(*args)
end
end
end
end
end
end
end
class B
include A
def self.perform
puts "Foobar"
end
end
B.perform
Edit:
d11wtq simplified this to the much cleaner:
module VerboseJob
module ClassMethods
def wrap_perform!
class << self
def perform_with_verbose(*args)
JobLogger.verbose { perform_without_verbose(*args) }
end
alias_method_chain :perform, :verbose \
unless instance_method(:perform) == instance_method(:perform_with_verbose)
end
end
def singleton_method_added(name)
wrap_perform! if name == :perform
end
end
def self.included(job_class)
job_class.extend ClassMethods
job_class.wrap_perform! if job_class.respond_to?(:perform)
end
end
Assuming that you want all of your classes defined before you want perform run, you may want to use Kernel#at_exit:
Converts block to a Proc object (and therefore binds it at the point
of call) and registers it for execution when the program exits. If
multiple handlers are registered, they are executed in reverse order
of registration.
def do_at_exit(str1)
at_exit { print str1 }
end
at_exit { puts "cruel world" }
do_at_exit("goodbye ")
exit
produces:
goodbye cruel world
You may also want to look at how unit testing frameworks, such as Test::Unit or MiniTest, handle delaying the running of tasks.
I'm playing with ruby's metaprogramming features, and I'm finding it a bit hairy. I'm trying to wrap a method call using a module. Currently, I'm doing this:
module Bar
module ClassMethods
def wrap(method)
class_eval do
old_method = "wrapped_#{method}".to_sym
unless respond_to? old_method
alias_method old_method, method
define_method method do |*args|
send old_method, *args
end
end
end
end
end
def self.included(base)
base.extend ClassMethods
end
end
class Foo
include Bar
def bar(arg = 'foo')
puts arg
end
wrap :bar
end
Three questions:
Is there any way to do this without renaming the method, so as to allow the use of super? Or something cleaner/shorter?
Is there a clean way to set the default values?
Is there a means to move the wrap :bar call further up?
1) Cleaner/shorter
module ClassMethods
def wrap(method)
old = "_#{method}".to_sym
alias_method old, method
define_method method do |*args|
send(old, *args)
end
end
end
class Foo
extend ClassMethods
def bar(arg = 'foo')
puts arg
end
wrap :bar
end
As far as I know there is no way to achieve this without renaming. You could try to call super inside the define_method block. But first of all, a call to super from within a define_method will only succeed if you specify arguments explicitly, otherwise you receive an error. But even if you call e.g. super(*args), self in that context would be an instance of Foo. So a call to bar would go to the super classes of Foo, not be found and ultimately result in an error.
2) Yes, like so
define_method method do |def_val='foo', *rest|
send(old, def_val, *rest)
end
However, in Ruby 1.8 it is not possible to use a block in define_method, but this has been fixed for 1.9. If you are using 1.9, you could also use this
define_method method do |def_val='foo', *rest, &block|
send(old, def_val, *rest, &block)
end
3) No, unfortunately. alias_method requires the existence of the methods that it takes as input. As Ruby methods come into existence as they are parsed, the wrap call must be placed after the definition of bar otherwise alias_method would raise an exception.
I'm writing a module in Ruby 1.9.2 that defines several methods. When any of these methods is called, I want each of them to execute a certain statement first.
module MyModule
def go_forth
a re-used statement
# code particular to this method follows ...
end
def and_multiply
a re-used statement
# then something completely different ...
end
end
But I want to avoid putting that a re-used statement code explicitly in every single method. Is there a way to do so?
(If it matters, a re-used statement will have each method, when called, print its own name. It will do so via some variant of puts __method__.)
Like this:
module M
def self.before(*names)
names.each do |name|
m = instance_method(name)
define_method(name) do |*args, &block|
yield
m.bind(self).(*args, &block)
end
end
end
end
module M
def hello
puts "yo"
end
def bye
puts "bum"
end
before(*instance_methods) { puts "start" }
end
class C
include M
end
C.new.bye #=> "start" "bum"
C.new.hello #=> "start" "yo"
This is exactly what aspector is created for.
With aspector you don't need to write the boilerplate metaprogramming code. You can even go one step further to extract the common logic into a separate aspect class and test it independently.
require 'aspector'
module MyModule
aspector do
before :go_forth, :add_multiply do
...
end
end
def go_forth
# code particular to this method follows ...
end
def and_multiply
# then something completely different ...
end
end
You can implement it with method_missing through proxy Module, like this:
module MyModule
module MyRealModule
def self.go_forth
puts "it works!"
# code particular to this method follows ...
end
def self.and_multiply
puts "it works!"
# then something completely different ...
end
end
def self.method_missing(m, *args, &block)
reused_statement
if MyModule::MyRealModule.methods.include?( m.to_s )
MyModule::MyRealModule.send(m)
else
super
end
end
def self.reused_statement
puts "reused statement"
end
end
MyModule.go_forth
#=> it works!
MyModule.stop_forth
#=> NoMethodError...
You can do this by metaprogramming technique, here's an example:
module YourModule
def included(mod)
def mod.method_added(name)
return if #added
#added = true
original_method = "original #{name}"
alias_method original_method, name
define_method(name) do |*args|
reused_statement
result = send original_method, *args
puts "The method #{name} called!"
result
end
#added = false
end
end
def reused_statement
end
end
module MyModule
include YourModule
def go_forth
end
def and_multiply
end
end
works only in ruby 1.9 and higher
UPDATE: and also can't use block, i.e. no yield in instance methods
I dunno, why I was downvoted - but a proper AOP framework is better than meta-programming hackery. And thats what OP was trying to achieve.
http://debasishg.blogspot.com/2006/06/does-ruby-need-aop.html
Another Solution could be:
module Aop
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def before_filter(method_name, options = {})
aop_methods = Array(options[:only]).compact
return if aop_methods.empty?
aop_methods.each do |m|
alias_method "#{m}_old", m
class_eval <<-RUBY,__FILE__,__LINE__ + 1
def #{m}
#{method_name}
#{m}_old
end
RUBY
end
end
end
end
module Bar
def hello
puts "Running hello world"
end
end
class Foo
include Bar
def find_hello
puts "Running find hello"
end
include Aop
before_filter :find_hello, :only => :hello
end
a = Foo.new()
a.hello()
It is possible with meta-programming.
Another alternative is Aquarium. Aquarium is a framework that implements Aspect-Oriented Programming (AOP) for Ruby. AOP allow you to implement functionality across normal object and method boundaries. Your use case, applying a pre-action on every method, is a basic task of AOP.
I have a Mix-in that reflects on the receiver class to generate some code. This means that I need to execute the class method at the end of the class definition, like in this trivially dumbed down example:
module PrintMethods
module ClassMethods
def print_methods
puts instance_methods
end
end
def self.included(receiver)
receiver.extend ClassMethods
end
end
class Tester
include PrintMethods
def method_that_needs_to_print
end
print_methods
end
I'd like to have the mixin do this for me automatically, but I can't come up with a way. My first thought was to add receiver.print_methods to self.included in the mixin, but that won't work because the method that I want it to reflect on has not been declared yet. I could call include PrintMethods at the end of the class, but that feels like bad form.
Are there any tricks to make this happen so I don't need to call print_methods at the end of the class definition?
First of all, there's no end of class definition. Remember that in Ruby you can reopen the Tester class method after you have 'initialized' it, so the interpreter can't know where the class 'ends'.
The solution I can come up with is to make the class via some helper method, like
module PrintMethods
module ClassMethods
def print_methods
puts instance_methods
end
end
def self.included(receiver)
receiver.extend ClassMethods
end
end
class Object
def create_class_and_print(&block)
klass = Class.new(&block)
klass.send :include, PrintMethods
klass.print_methods
klass
end
end
Tester = create_class_and_print do
def method_that_needs_to_print
end
end
But certainly having to define classes this way makes my eyes hurt.