I am trying to make a simplistic implementation of AOP in ruby. I was able to implement before and after advices, I got stuck with around advice.
This is the target class that is going to be advised:
class MyClass
def method
puts "running method"
end
end
This is the Aspect class to instantiate objects capable of making advices:
class Aspect
def advise(class_name, method, type, &block)
class_name.send(:alias_method, :proceed, :method)
class_name.send(:define_method, :method) do
case type
when :before
yield
proceed
when :after
proceed
yield
when :around
yield(proceed) # * proceed is the old version of the method
end
end
end
end
(*) Yield should execute the block around MyClass#proceed on the current object when method is invoked.
Creating the target and the aspect:
mc = MyClass.new
a = Aspect.new()
Invoking the method without advising it:
puts mc.method
Advising MyClass#method with around:
a.advise(MyClass, :method, :around) do |proceed|
puts "First"
proceed # this is not working *
puts "Last"
end
puts mc.method
(*) I am not being able to pass something to identify the call of proceed, that is the invocation of the old method without the advice.
The output should be:
First
running method
Last
In Ruby, a method call looks like this:
receiver.method(arguments)
Or, you can leave off the receiver if the receiver is self.
So, to call a method named proceed on some receiver, you would write
receiver.proceed
However, in your implementation, you don't keep track of what the receiver should be, so since you don't know the receiver, you simply cannot call the method.
Note that there are lots of other problems with your approach as well. For example, if you advise multiple methods, you will alias them all to the same method, overwriting each other.
I believe there are two things going wrong here.
This section of code
when :around
yield(proceed) # * proceed is the old version of the method
end
Calls the block given to advise providing the output of the proceed method as an argument.
So your output probably looks something like:
running method
First
Last
This block
a.advise(MyClass, :method, :around) do |proceed|
puts "First"
proceed # this is not working *
puts "Last"
end
Just evaluates the argument given as proceed. If a method is given it does not call it. So taking problem 1 into consideration in your case the original definition of method (aliased to proceed) returns nil (output of return) which will be passed as the value to the proceed argument in the block when yielded. the block ends up evaluating to something like
puts "First"
nil
puts "Last"
mc.method is called.
To address the second part, you may want to consider using send. Because the inner workings of your aspect may not be known to your code that calls it. It may change over time, so what ever calls Aspect.advise shouldn't make assumptions that the original method will still be accessible. Instead, it should take an argument (the new method name) and send it to the object. Making the block passed to advise:
a.advise(MyClass, :method, :around) do |aliased_method_name|
puts "First"
send(aliased_method_name)
puts "Last"
end
And adjusting the around item added to your class when advise is called to the following:
when :around
yield(:proceed) # * proceed is the old version of the method
end
If you do both of these things, your around section will calls the provided block, using the symbol for the new alias for the overridden method.
N.B.: This approach won't work for methods that require any arguments.
This is what I did. In the definition of Aspect#advise now I use a Proc, like this:
when :around
yield Proc.new { proceed }
end
And when calling the method to advise MyClass#method with :around parameter I use this:
a.advise(MyClass, :method, :around) do |original|
puts "First"
original.call
puts "Last"
end
I got:
First
running method
Last
Here's the fixed version that will work for arguments, and avoid clobbering.
class Aspect
##count = 0
def self.advise(class_name, method, type=nil, &block)
old_method = :"__aspect_#{method}_#{##count += 1}"
class_name.send(:alias_method, old_method, method)
class_name.send(:define_method, method) do |*args, &callblock|
case type
when :before
yield
send(old_method, *args, &callblock)
when :after
send(old_method, *args, &callblock)
yield
when :around, nil
yield lambda {
send(old_method, *args, &callblock)
}
end
end
end
end
class Foo
def foo(what)
puts "Hello, #{what}!"
end
end
Aspect.advise(Foo, :foo) do |y|
puts "before around"
y.yield
puts "after around"
end
Aspect.advise(Foo, :foo, :before) do
puts "before"
end
Aspect.advise(Foo, :foo, :after) do
puts "after"
end
Foo.new.foo("world")
# before
# before around
# Hello, world!
# after around
# after
Related
Inside the body of a class, I'd like to pass a block to a method called with. For the lifetime of the block, I would like a with_value method to be available.
Otherwise, everything inside the block should behave as if it were outside the block.
Here's an example:
class C
extend M
with "some value" do
do_something_complicated
do_something_complicated
do_something_complicated
end
end
We can almost get this with:
module M
def with(str, &block)
Object.new.tap do |wrapper|
wrapper.define_singleton_method :with_value do # Here's our with_value
str # method.
end
end.instance_eval &block
end
def do_something_complicated # Push a value onto an
(#foo ||= []).push with_value # array.
end
end
but there's a problem: since we're evaluating the block passed to with inside the context of a different object, do_something_complicated isn't available.
What's the right way to pull this off?
This will make with_value available only within the block. However, _with_value will be defined within or outside of the block.
module M
def _with_value
...
end
def with(str, &block)
alias with_value _with_value
block.call
undef with_value
end
...
end
I cannot tell from the question whether this is a problem. If it is a problem, you need to further describe what you are trying to do.
Basically, the idea is to use method_missing to forward method calls from the dummy class to the calling class. If you also need to access instance variables, you can copy them from the calling class to your dummy class, and then back again after the block returns.
The Ruby gem docile is a very simple implementation of such a system. I suggest you read the source code in that repository (don't worry, it's a very small codebase) for a good example of how DSL methods like the one in your example work.
Here is a way that is closer to your attempt:
module M
def with(str, &block)
dup.tap do |wrapper|
wrapper.define_singleton_method :with_value do
...
end
end.instance_eval &block
end
...
end
dup will duplicate the class from where with is called as a class method.
I am writing something as follows
module Test
def self.print(*args)
p 'something'
p args
end
end
def print(*args)
p "print something"
p args
end
def method_caller(method_name, *args)
send(method_name, *args)
end
method_caller(:print, 2) # this works fine
method_caller("print", 2, 3) # this one also
method_caller("Test.print", 2) # this doesn't work
Basically, I pass method_caller the name of a method and some arguments and then I use the send method to actually invoke the method.
I usually pass in the name of the method as a symbol, but how would I deal with Test.print? I imagine at some point I might be passing in objects and having them call their own methods.
The method I would like to invoke could be literally anywhere.
UPDATE:
Trying out each suggestion, the explicit receiver looks like a good way to do it since it's obvious what is happening, but what about christianblais' idea. I changed it to avoid the eval call as such
def method_caller(method_name, *args)
if method_name.is_a?(String)
chain = method_name.split('.')
obj, method_name = Object.const_get(chain[0...-1].join('.')), chain[-1]
obj.send(method_name, *args)
else
send(method_name, args)
end
end
Which means I can just say method_caller("Test.print", 2), but maybe there are some gotchas...
Well, why don't you give your method_caller a receiver argument?
def method_caller(receiver, method_name, *args)
receiver.send(method_name, *args)
end
method_caller(Test, :print, 2)
The other calls need to use self as the receiver, which is implicit in your current implementation. But finally all this gives you is an incomplete ad hoc reimplementation of send, so one really wonders what you are trying to achieve with that.
There's two things here.
1) Your example won't work, as you're trying to call Test.print with an argument while your method definition doesn't accept one.
EDIT: Just saw your update. It's now ok. Sorry!
2) It's evil, but you could always use eval like this :
module Test
def self.print(*args)
p 'something'
p args
end
end
def method_caller(method_name, *args)
if method_name.is_a?(String)
chain = method_name.split('.')
eval(chain[0...-1].join('.')).send(chain.last, args)
else
send(method_name, args)
end
end
method_caller(:print, 2) # this works fine
method_caller("print", 2, 3) # this one also
method_caller("Test.print", 2) # this doesn't work
Is the passing of the method to call as a string a requirement or is it just an idea you had ? A more ruby-ish solution would be to simply pass a block ...
method_caller(2) { |*args| Object.print(*args) }
Or to give the method object as argument
method_caller(Object.method(:print), 2)
Which, by the way, works for any object that responds to #call (labmdas, ...)
Sometimes you can see:
do_this do
available_method1 "arg1"
available_method2 "arg1"
end
When I use the block from do_this method then I get some methods I could use inside that block.
I wonder how this is accomplished? How does the code look like behind the scenes?
I want to be able to provide some methods through a block.
It's called a Domain-Specific Language (DSL). Here's (Last archived version) some great info on various forms of Ruby DSL blocks.
There are really two ways to go about doing this, with different syntaxes:
do_thing do |thing| # with a block parameter
thing.foo :bar
thing.baz :wibble
end
# versus
do_thing do # with block-specific methods
foo :bar
baz :wibble
end
The first is more explicit and less likely to fail, while the second is more concise.
The first can be implemented like so, by simply passing a new instance as the block parameter with yield:
class MyThing
def self.create
yield new
end
def foo(stuff)
puts "doing foo with #{stuff}"
end
end
MyThing.create do |thing|
thing.foo :bar
end
And the second, which runs the block in the context of the new object, giving it access to self, instance variables, and methods:
class MyThing
def self.create(&block)
new.instance_eval &block
end
def foo(stuff)
puts "doing foo with #{stuff}"
end
end
MyThing.create do
foo :bar
end
And if you really want to do it without calling MyThing.create, just:
def create_thing(&block)
MyThing.create &block
end
This is usually done using instance_eval to change the value of self inside the block to be some different object, which then handles those method calls.
As a quick example:
class ExampleReceiver
def available_method1 arg ; p [:available_method1, arg] ; end
def available_method2 arg ; p [:available_method2, arg] ; end
end
def do_this(&blk) ; ExampleReceiver.new.instance_eval(&blk) ; end
do_this do
available_method1 "arg1" #=> [:available_method1, "arg1"]
available_method2 "arg1" #=> [:available_method2, "arg1"]
end
Though this is a powerful language feature, and has been used before to great effect, there is still some debate on whether it's a good idea or not. If you don't know what's going on, you might be surprised that the value of #some_instance_variable changes inside and outside the block, since it's relative to the current value of self.
See Daniel Azuma's excellent article for more discussion and details.
class MyClass
def test
...
end
end
tmp = MyClass.new
tmp.test do |t|
"here"
end
Why am I getting the error
multiple values for a block parameter (0 for 1)
Here is a slightly longer example, based on your code:
class MyClass
def test
yield self
end
def my_own_puts s
puts s
end
end
tmp = MyClass.new
tmp.test do |t|
t.my_own_puts "here"
end
Running this code will output "here".
What is happening is there is a method test that can take a block of code, so you can call it with the do .. end syntax. Because it is passing an arg to yield, that arg is available to the block, so you give this to the block using the do |some_arg_name| ... end syntax.
The yield is where the block gets executed in the test method, and in this case I to yield I pass in self. Since the block now has access to self (an instance of MyClass), the block can call the my_own_puts method on it, and print out "here".
if test is defined with a yield statement, when that statement is reached and if there is a parameter on the yield statement, that parameter will be put into the block variable t. Thus if you have:
def test
.....
yield x
......
end
then x will be the value of t when yield is executed.
With your help, I was able to get the code working like this
class MyClass
def test
a = yield self
puts a
end
end
tmp = MyClass.new
tmp.test do |t|
"here"
end
Thanks, I had to tweak your code a bit but it works the way I wanted to now.
Passing a block to a function (as Bob shows in his answer) is overkill in this case. If you are reading in a string and printing it out, all you should need is something like:
class MyClass
def test(a)
puts a
end
end
tmp = MyClass.new
tmp.test("here")
Using a block might function correctly, but you are calling a lot of unnecessary code and making the true nature of your code unclear.
Proper block usage aside, let me address the particular error message you are seeing. When you say tmp.test do |t|, Ruby is expecting tmp.test to yield a single value which it will temporarily call t and pass to the block (think like the block is a function and you are passing it the argument your yield statement as a parameter). In your case, the method test method must not be yield-ing anything, thus the message "(0 for 1)" implies that it is seeing zero objects yielded when it is expecting to see one. I don't know what your code for test does, but check and make sure that test yields exactly one value.
EDIT: I slightly changed the spec, to better match what I imagined this to do.
Well, I don't really want to fake C# attributes, I want to one-up-them and support AOP as well.
Given the program:
class Object
def Object.profile
# magic code here
end
end
class Foo
# This is the fake attribute, it profiles a single method.
profile
def bar(b)
puts b
end
def barbar(b)
puts(b)
end
comment("this really should be fixed")
def snafu(b)
end
end
Foo.new.bar("test")
Foo.new.barbar("test")
puts Foo.get_comment(:snafu)
Desired output:
Foo.bar was called with param: b = "test"
test
Foo.bar call finished, duration was 1ms
test
This really should be fixed
Is there any way to achieve this?
I have a somewhat different approach:
class Object
def self.profile(method_name)
return_value = nil
time = Benchmark.measure do
return_value = yield
end
puts "#{method_name} finished in #{time.real}"
return_value
end
end
require "benchmark"
module Profiler
def method_added(name)
profile_method(name) if #method_profiled
super
end
def profile_method(method_name)
#method_profiled = nil
alias_method "unprofiled_#{method_name}", method_name
class_eval <<-ruby_eval
def #{method_name}(*args, &blk)
name = "\#{self.class}##{method_name}"
msg = "\#{name} was called with \#{args.inspect}"
msg << " and a block" if block_given?
puts msg
Object.profile(name) { unprofiled_#{method_name}(*args, &blk) }
end
ruby_eval
end
def profile
#method_profiled = true
end
end
module Comment
def method_added(name)
comment_method(name) if #method_commented
super
end
def comment_method(method_name)
comment = #method_commented
#method_commented = nil
alias_method "uncommented_#{method_name}", method_name
class_eval <<-ruby_eval
def #{method_name}(*args, &blk)
puts #{comment.inspect}
uncommented_#{method_name}(*args, &blk)
end
ruby_eval
end
def comment(text)
#method_commented = text
end
end
class Foo
extend Profiler
extend Comment
# This is the fake attribute, it profiles a single method.
profile
def bar(b)
puts b
end
def barbar(b)
puts(b)
end
comment("this really should be fixed")
def snafu(b)
end
end
A few points about this solution:
I provided the additional methods via modules which could be extended into new classes as needed. This avoids polluting the global namespace for all modules.
I avoided using alias_method, since module includes allow AOP-style extensions (in this case, for method_added) without the need for aliasing.
I chose to use class_eval rather than define_method to define the new method in order to be able to support methods that take blocks. This also necessitated the use of alias_method.
Because I chose to support blocks, I also added a bit of text to the output in case the method takes a block.
There are ways to get the actual parameter names, which would be closer to your original output, but they don't really fit in a response here. You can check out merb-action-args, where we wrote some code that required getting the actual parameter names. It works in JRuby, Ruby 1.8.x, Ruby 1.9.1 (with a gem), and Ruby 1.9 trunk (natively).
The basic technique here is to store a class instance variable when profile or comment is called, which is then applied when a method is added. As in the previous solution, the method_added hook is used to track when the new method is added, but instead of removing the hook each time, the hook checks for an instance variable. The instance variable is removed after the AOP is applied, so it only applies once. If this same technique was used multiple time, it could be further abstracted.
In general, I tried to stick as close to your "spec" as possible, which is why I included the Object.profile snippet instead of implementing it inline.
Great question. This is my quick attempt at an implementation (I did not try to optimise the code). I took the liberty of adding the profile method to the
Module class. In this way it will be available in every class and module definition. It would be even better
to extract it into a module and mix it into the class Module whenever you need it.
I also didn't know if the point was to make the profile method behave like Ruby's public/protected/private keywords,
but I implemented it like that anyway. All methods defined after calling profile are profiled, until noprofile is called.
class Module
def profile
require "benchmark"
#profiled_methods ||= []
class << self
# Save any original method_added callback.
alias_method :__unprofiling_method_added, :method_added
# Create new callback.
def method_added(method)
# Possible infinite loop if we do not check if we already replaced this method.
unless #profiled_methods.include?(method)
#profiled_methods << method
unbound_method = instance_method(method)
define_method(method) do |*args|
puts "#{self.class}##{method} was called with params #{args.join(", ")}"
bench = Benchmark.measure do
unbound_method.bind(self).call(*args)
end
puts "#{self.class}##{method} finished in %.5fs" % bench.real
end
# Call the original callback too.
__unprofiling_method_added(method)
end
end
end
end
def noprofile # What's the opposite of profile?
class << self
# Remove profiling callback and restore previous one.
alias_method :method_added, :__unprofiling_method_added
end
end
end
You can now use it as follows:
class Foo
def self.method_added(method) # This still works.
puts "Method '#{method}' has been added to '#{self}'."
end
profile
def foo(arg1, arg2, arg3 = nil)
puts "> body of foo"
sleep 1
end
def bar(arg)
puts "> body of bar"
end
noprofile
def baz(arg)
puts "> body of baz"
end
end
Call the methods as you would normally:
foo = Foo.new
foo.foo(1, 2, 3)
foo.bar(2)
foo.baz(3)
And get benchmarked output (and the result of the original method_added callback just to show that it still works):
Method 'foo' has been added to 'Foo'.
Method 'bar' has been added to 'Foo'.
Method 'baz' has been added to 'Foo'.
Foo#foo was called with params 1, 2, 3
> body of foo
Foo#foo finished in 1.00018s
Foo#bar was called with params 2
> body of bar
Foo#bar finished in 0.00016s
> body of baz
One thing to note is that it is impossible to dynamically get the name of the arguments with Ruby meta-programming.
You'd have to parse the original Ruby file, which is certainly possible but a little more complex. See the parse_tree and ruby_parser
gems for details.
A fun improvement would be to be able to define this kind of behaviour with a class method in the Module class. It would be cool to be able to do something like:
class Module
method_wrapper :profile do |*arguments|
# Do something before calling method.
yield *arguments # Call original method.
# Do something afterwards.
end
end
I'll leave this meta-meta-programming exercise for another time. :-)