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.
Related
Define a class as follows. I want to call one_method dynamically. By default, wow.one_method calls the first one_method. If I want to change the behavior of the method, just call redefine.
I can implement the method as a function type property, but that is not what I want.
If I use the following code directly, it would report errors. Could you modify it slightly.
class Wow
def one_method
puts "hello Ruby"
end
def redefine(what="none")
define_method :one_method do
puts what
end
end
end
wow = Wow.new
wow.redefine("Whatever I want.")
You can achieve that via class_eval or instance_eval:
class Wow
def one_method
puts "hello Ruby"
end
def redefine(what="none")
self.class.class_eval do
define_method :one_method do
puts what
end
end
end
end
wow = Wow.new
wow.one_method #=> hello Ruby
wow.redefine("Whatever I want.")
wow.one_method #=> Whatever I want.
Reason is that define_method defines instance method on the receiver and is a class's instance method so you'll have to call it on the eigen class of the object that you want to redefine a method on.
I would recommend achieving your goal in a more canonical way, just redefine the method on the instance itself:
class Wow
def one
:one
end
end
w = Wow.new
w.one
#=> :one
def w.one
:two
end
w.one
#=> :two
Drawbacks are:
your methods lookup table caches will be dropped
the code is becoming more obscure and hard to debug
Alternatives:
I don't know your real problem, but for your particular question it is better to parameterize your one_method method just to receive an argument for puts. Also, you can pass a block, so you will receive more grained control over the behavior.
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
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.
as far as I understand 'send' method, this
some_object.some_method("im an argument")
is same as this
some_object.send :some_method, "im an argument"
So what is the point using 'send' method?
It can come in handy if you don't know in advance the name of the method, when you're doing metaprogramming for example, you can have the name of the method in a variable and pass it to the send method.
It can also be used to call private methods, although this particular usage is not considered to be a good practice by most Ruby developers.
class Test
private
def my_private_method
puts "Yay"
end
end
t = Test.new
t.my_private_method # Error
t.send :my_private_method #Ok
You can use public_send though to only be able to call public methods.
In addition to Intrepidd's use cases, it is convenient when you want to route different methods on the same receiver and/or arguments. If you have some_object, and want to do different things on it depending on what foo is, then without send, you need to write like:
case foo
when blah_blah then some_object.do_this(*some_arguments)
when whatever then some_object.do_that(*some_arguments)
...
end
but if you have send, you can write
next_method =
case foo
when blah_blah then :do_this
when whatever then :do_that
....
end
some_object.send(next_method, *some_arguments)
or
some_object.send(
case foo
when blah_blah then :do_this
when whatever then :do_that
....
end,
*some_arguments
)
or by using a hash, even this:
NextMethod = {blah_blah: :do_this, whatever: :do_that, ...}
some_object.send(NextMethod[:foo], *some_arguments)
In addition to everyone else's answers, a good use case would be for iterating through methods that contain an incrementing digit.
class Something
def attribute_0
"foo"
end
def attribute_1
"bar"
end
def attribute_2
"baz"
end
end
thing = Something.new
3.times do |x|
puts thing.send("attribute_#{x}")
end
#=> foo
# bar
# baz
This may seem trivial, but it's occasionally helped me keep my Rails code and templates DRY. It's a very specific case, but I think it's a valid one.
The summing briefly up what was already said by colleagues: send method is a syntax sugar for meta-programming. The example below demonstrates the case when native calls to methods are likely impossible:
class Validator
def name
'Mozart'
end
def location
'Salzburg'
end
end
v = Validator.new
'%name% was born in %location%'.gsub (/%(?<mthd>\w+)%/) do
# v.send :"#{Regexp.last_match[:mthd]}"
v.send Regexp.last_match[:mthd].to_sym
end
=> "Mozart was born in Salzburg"
I like this costruction
Object.get_const("Foo").send(:bar)
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. :-)