invoking proc with instance_eval with arguments? - ruby

I know this works:
proc = Proc.new do
puts self.hi + ' world'
end
class Usa
def hi
"Hello!"
end
end
Usa.new.instance_eval &proc
However I want to pass arguments to proc, so I tried this which does not work:
proc = Proc.new do |greeting|
puts self.hi + greeting
end
class Usa
def hi
"Hello!"
end
end
Usa.new.instance_eval &proc, 'world' # does not work
Usa.new.instance_eval &proc('world') # does not work
Can anyone help me make it work?

Use instance_exec instead of instance_eval when you need to pass arguments.
proc = Proc.new do |greeting|
puts self.hi + greeting
end
class Usa
def hi
"Hello, "
end
end
Usa.new.instance_exec 'world!', &proc # => "Hello, world!"
Note: it's new to Ruby 1.8.7, so upgrade or require 'backports' if needed.

Related

Re-defined method with a yield throws no block given

Can someone explain why the last yielder throws a no block given?
class Foo
def yielder
yield "hello"
end
end
class Mod
def initialize
##foo = Foo.new
end
def self.foo
##foo
end
end
worker = Mod.new
Mod.foo.yielder do |hello|
puts hello
end
Mod.foo.class.send(:define_method,:yielder) do
yield "new hello"
end
Mod.foo.yielder do |hello|
puts hello
end
Gives:
hello
test.rb:27:in `block in ': no block given (yield) (LocalJumpError)
from test.rb:30:in `'
A short introduction:
You don't need the Mod-instance, if you define ##foo outside initialize.
You don't need the Mod class to get the problem:
class Foo
def yielder
p 2
yield "hello"
end
end
foo = Foo.new
foo.yielder do |hello|
puts hello
end
foo.class.send(:define_method,:yielder) do
p 1
yield "new hello"
end
foo.yielder do |hello|
puts hello
end
You may shorten your example again:
class Foo
end
foo = Foo.new
foo.class.send(:define_method,:yielder) do
yield "new hello"
end
foo.yielder do |hello|
puts hello
end
This is the same as:
class Foo
define_method(:yielder) do
yield "new hello"
end
end
foo = Foo.new
foo.yielder do |hello|
puts hello
end
End of Introduction.
And now, I'm not sure if I understood correct what you want (and if I understand ruby correct ;) )
define_method accepts a block and use it as method body.
If the new method should receive a block on its own, you must define it in the interface of the definition and call it:
class Foo
define_method(:yielder) do | &prc |
prc.call("new hello")
end
end
foo = Foo.new
foo.yielder do |hello|
puts hello
end
Or the same logic in your example:
class Foo
def yielder
yield "hello"
end
end
class Mod
def initialize
##foo = Foo.new
end
def self.foo
##foo
end
end
worker = Mod.new
Mod.foo.yielder do |hello|
puts hello
end
Mod.foo.class.send(:define_method,:yielder) do | &prc |
prc.call "new hello"
end
Mod.foo.yielder do |hello|
puts hello
end
To make the code more robust, I would recommend some checks with block_given?.

class_eval how to pass parameter to method

How do I pass the parameter name in the following case..the name is being is evaluated before being passed to class_eval
class Foo
end
Foo.class_eval %Q{
def hello(name)
p "hello #{name}"
end
}
Sorry about not giving the entire scenario...
I just wanted to add a instance method dynamically to a class and that method should be able to take arguments...
the above code would not compile complaining that the name is not defined as local variable when executing in irb..
Thanks
The other answers are the "right" answer, but you could also just skip interpolating inside the p call:
Foo.class_eval %Q{
def hello(name)
p "hello \#{name}"
end
}
I thought you wanted to change the actual parameter name (possibly useful for completion or when using Pry on dynamic methods), here assuming it's in a global, but could also be passed into a method doing the class_eval:
Foo.class_eval %Q{
def hello(#{$argname})
p "hello \#{$argname}"
end
}
Really simple:
Foo.class_eval do
def hello(name)
p "hello #{name}"
end
end
Try passing a block to class_eval instead of an array (from this link):
class Foo
end
Foo.class_eval {
def hello(name)
p "hello #{name}"
end
}
You then can call the instance method hello in the usual fashion:
boo = Foo.new
boo.hello("you")
which produces:
>> boo.hello("you")
"hello you"
=> nil
class Foo
end
Foo.class_eval do
define_method :hello do |name|
p "hello #{name}"
end
end
Foo.new.hello("coool") # => "hello coool"

How do I get puts to work on my class?

class X
def initialize
#name = "Bob"
end
blah blah
end
puts X.new # I want this to print X:Bob
puts [X.new, X.new] # I want this to print [X:Bob, X:Bob]
Override the to_s method of your class:
class X
def initialize
#name = "Bob"
end
def to_s
"X:#{#name}"
end
end
puts X.new # prints X:Bob
puts [X.new, X.new].to_s # prints [X:Bob, X:Bob]
You need to have initialize, not init.

Just for fun - add methods to an object via a block

Just for fun, again, but is it possible to take a block that contains method definitions and add those to an object, somehow? The following doesn't work (I never expected it to), but just so you get the idea of what I'm playing around with.
I do know that I can reopen a class with class << existing_object and add methods that way, but is there a way for code to pass that information in a block?
I guess I'm trying to borrow a little Java thinking here.
def new(cls)
obj = cls.new
class << obj
yield
end
obj
end
class Cat
def meow
puts "Meow"
end
end
cat = new(Cat) {
def purr
puts "Prrrr..."
end
}
cat.meow
# => Meow
# Not working
cat.purr
# => Prrrr...
EDIT | Here's the working version of the above, based on edgerunner's answer:
def new(cls, &block)
obj = cls.new
obj.instance_eval(&block)
obj
end
class Cat
def meow
puts "Meow"
end
end
cat = new(Cat) {
def purr
puts "Prrrr..."
end
}
cat.meow
# => Meow
cat.purr
# => Prrrr...
You can use class_eval(also aliased as module_eval) or instance_eval to evaluate a block in the context of a class/module or an object instance respectively.
class Cat
def meow
puts "Meow"
end
end
Cat.module_eval do
def purr
puts "Purr"
end
end
kitty = Cat.new
kitty.meow #=> Meow
kitty.purr #=> Purr
kitty.instance_eval do
def purr
puts "Purrrrrrrrrr!"
end
end
kitty.purr #=> Purrrrrrrrrr!
Yes
I suspect you thought of this and were looking for some other way, but just in case...
class A
def initialize
yield self
end
end
o = A.new do |o|
class << o
def purr
puts 'purr...'
end
end
end
o.purr
=> purr...
For the record, this isn't the usual way to dynamically add a method. Typically, a dynamic method starts life as a block itself, see, for example, *Module#define_method*.

Cross-cutting logging in Ruby

I'm trying to add logging to a method from the outside (Aspect-oriented-style)
class A
def test
puts "I'm Doing something..."
end
end
class A # with logging!
alias_method :test_orig, :test
def test
puts "Log Message!"
test_orig
end
end
a = A.new
a.test
The above works alright, except that if I ever needed to do alias the method again, it goes into an infinite loop. I want something more like super, where I could extend it as many times as I needed, and each extension with alias its parent.
Another alternative is to use unbound methods:
class A
original_test = instance_method(:test)
define_method(:test) do
puts "Log Message!"
original_test.bind(self).call
end
end
class A
original_test = instance_method(:test)
counter = 0
define_method(:test) do
counter += 1
puts "Counter = #{counter}"
original_test.bind(self).call
end
end
irb> A.new.test
Counter = 1
Log Message!
#=> #....
irb> A.new.test
Counter = 2
Log Message!
#=> #.....
This has the advantage that it doesn't pollute the namespace with additional method names, and is fairly easily abstracted, if you want to make a class method add_logging or what have you.
class Module
def add_logging(*method_names)
method_names.each do |method_name|
original_method = instance_method(method_name)
define_method(method_name) do |*args,&blk|
puts "logging #{method_name}"
original_method.bind(self).call(*args,&blk)
end
end
end
end
class A
add_logging :test
end
Or, if you wanted to be able to do a bunch of aspects w/o a lot of boiler plate, you could write a method that writes aspect-adding methods!
class Module
def self.define_aspect(aspect_name, &definition)
define_method(:"add_#{aspect_name}") do |*method_names|
method_names.each do |method_name|
original_method = instance_method(method_name)
define_method(method_name, &(definition[method_name, original_method]))
end
end
end
# make an add_logging method
define_aspect :logging do |method_name, original_method|
lambda do |*args, &blk|
puts "Logging #{method_name}"
original_method.bind(self).call(*args, &blk)
end
end
# make an add_counting method
global_counter = 0
define_aspect :counting do |method_name, original_method|
local_counter = 0
lambda do |*args, &blk|
global_counter += 1
local_counter += 1
puts "Counters: global##{global_counter}, local##{local_counter}"
original_method.bind(self).call(*args, &blk)
end
end
end
class A
def test
puts "I'm Doing something..."
end
def test1
puts "I'm Doing something once..."
end
def test2
puts "I'm Doing something twice..."
puts "I'm Doing something twice..."
end
def test3
puts "I'm Doing something thrice..."
puts "I'm Doing something thrice..."
puts "I'm Doing something thrice..."
end
def other_tests
puts "I'm Doing something else..."
end
add_logging :test, :test2, :test3
add_counting :other_tests, :test1, :test3
end
First choice: subclass instead of overriding:
class AWithLogging < A\
def test
puts "Log Message!"
super
end
end
Second choice: name your orig methods more carefully:
class A # with logging!
alias_method :test_without_logging, :test
def test
puts "Log Message!"
test_without_logging
end
end
Then another aspect uses a different orig name:
class A # with frobnication!
alias_method :test_without_frobnication, :test
def test
Frobnitz.frobnicate(self)
test_without_frobnication
end
end

Resources