define_method with a &block?

E

Erik Veenstra

In the code below, I want test2 to receive a block, like test1,
so they do the same. Is that possible?

(I use this "define_method" in meta-programming code. Using
"def" is not an option.)

Thanks.

gegroet,
Erik V. - http://www.erikveen.dds.nl/

----------------------------------------------------------------

class Test
def test1(*args, &block)
p [:test1, args, block_given?]
end

define_method:)test2) do |*args|
p [:test2, args, block_given?]
end
end

Test.new.test1(1, 2, 3){}
Test.new.test2(1, 2, 3){}

----------------------------------------------------------------
 
A

ara.t.howard

In the code below, I want test2 to receive a block, like test1,
so they do the same. Is that possible?

(I use this "define_method" in meta-programming code. Using
"def" is not an option.)

Thanks.

gegroet,
Erik V. - http://www.erikveen.dds.nl/

----------------------------------------------------------------

class Test
def test1(*args, &block)
p [:test1, args, block_given?]
end

define_method:)test2) do |*args|
p [:test2, args, block_given?]
end
end

Test.new.test1(1, 2, 3){}
Test.new.test2(1, 2, 3){}

harp:~ > cat a.rb
class Test
def test1(*args, &block)
p [:test1, args, block_given?]
end

module_eval <<-def
def test2 *args
p [:test2, args, block_given?]
end
def
end

Test.new.test1(1, 2, 3){}
Test.new.test2(1, 2, 3){}

harp:~ > ruby a.rb
[:test1, [1, 2, 3], true]
[:test2, [1, 2, 3], true]

hth.

-a
 
J

Joel VanderWerf

module_eval <<-def
def test2 *args
p [:test2, args, block_given?]
end
def

Or:

module_eval do
def test2 *args
p [:test2, args, block_given?]
end
end

if strings make you queasy.

But define_method won't define a method that takes a block until 1.9, IIRC.
 
E

Erik Veenstra

module_eval do
def test2 *args
p [:test2, args, block_given?]
end
end

The problem is that, in my situation, the name of the method
("test2") is not fixed, but stored in a string, like this:

method_name = "test2"
module_eval do
def #{method_name}(*args, &block) # Won't work!
p [method_name, args, block_given?]
end
end

One way to overcome this, without putting the whole method in
one big string, is using a temporary method, like this:

method_name = "test2"
module_eval do
def temp_method(*args, &block)
p [method_name, args, block_given?]
end
eval("alias :#{method_name} :temp_method")
undef :temp_method
end

See code below, Test#test4.

Comments? Suggestions?

Thanks.

gegroet,
Erik V. - http://www.erikveen.dds.nl/

----------------------------------------------------------------

class Test
def test1(*args, &block)
p [:test1, self, args, block_given?]
end

define_method:)test2) do |*args|
p [:test2, self, args, block_given?]
end

module_eval do
def test3(*args, &block)
p [:test3, self, args, block_given?]
end
end

method_name = "test4"
module_eval do
def temp_method(*args, &block)
p [:test4, self, args, block_given?]
end
eval("alias :#{method_name} :temp_method")
undef :temp_method
end
end

t = Test.new

t.test1(1, 2, 3){}
t.test2(1, 2, 3){}
t.test3(1, 2, 3){}
t.test4(1, 2, 3){}

----------------------------------------------------------------
 
A

ara.t.howard

One way to overcome this, without putting the whole method in one big
string, is using a temporary method, like this:

method_name = "test2"
module_eval do
def temp_method(*args, &block)
p [method_name, args, block_given?]
end
eval("alias :#{method_name} :temp_method")
undef :temp_method
end

See code below, Test#test4.

Comments? Suggestions?

i'm having a hard time imagining a case where only the name of the method
would need to be added and not the body - doesn't really seem like
meta-programming - for instance the method above generates something which
will always use 'method_name' as 'test2'.

- if this string is known when you're editing then you don't need
meta-programming at all.

- if it is not known, but the method body is, then you don't need to go
though such lengths to factor out variables and avoid strings - only to
eval a string

eval("alias :#{method_name} :temp_method")

you could have simply put your entire method def in this eval of a string!
;-)

of course maybe you could use

alias_method method_name, "temp_method"

to avoid that eval...

- lastly, if both the method body and name cannot be totally know until
runtime and you really, really want to avoid just having a simple method
that generates a string def of the method and would like to factor out the
state and behaviour of that method why not objectify it?

harp:~ > cat a.rb
require "forwardable"

class Test
extend Forwardable

def test1(*args, &block)
p [:test1, args, block_given?]
end

# abstract all state and behaviour for method here
class TestMethod
attr "name"
attr "data"
def initialize(name, data) @name, @data = name, data end
def call(*a, &b) p [@name, @data, a, !b.nil?] end
end

METHODS = {}
def Test::add_test_method name
name = name.to_s
METHODS[name] = TestMethod::new name, 42
accessor = "__#{ name }__"
define_method(accessor){ METHODS[name] }
def_delegator accessor, "call", name
end
end

Test.new.test1(1, 2, 3){}
Test::add_test_method "test2"
Test.new.test2(1, 2, 3){}



harp:~ > ruby a.rb
[:test1, [1, 2, 3], true]
["test2", 42, [1, 2, 3], true]


sure would be easier ruby had some way to declare blocks that take blocks! ;-)

regards.

-a
 
E

Erik Veenstra

i'm having a hard time imagining a case where only the name
of the method would need to be added and not the body -
doesn't really seem like meta-programming - for instance the
method above generates something which will always use
'method_name' as 'test2'.

I've got a couple of cases where I want to wrap a method in a
dynamically generated method. Something like this:

class Test
def do_something(a, b, &block)
# Do something with a, b and block.
end

monitor :do_something
end

This Module::monitor wraps the original Test#do_something in a
block of code which "monitors" (just an example...) the
invocation or the execution of the original method. Adding or
removing this monitor statement may not affect the behavior of
the original method. The arguments to the dynamically generated
method are passed to the original method. So is &block...
(damn...)

This Module::monitor might be implemented like this:

class Module
def monitor(method_name, *types)
org_method = instance_method(method_name)

define_method(method_name) do |*args|
block = nil # ??? &block

# Do a lot of checking on args.
# A lot of lines.
# You don't want to do that in a string.

org_method.bind(self).call(*args, &block)
end
end
end

I'm playing with a couple of monitor functions, like
statistics, benchmarking, role-validation and type-checking. In
each situation, the impact on the code must be reduced to a
minimum. The idiom (right term?) of (temporarily) wrapping a
method, as described above, doesn't affect the code of the
original method at all. That's nice.

See the code below for a full example, in which the arguments
of a method call are checked on class/behavior.
sure would be easier ruby had some way to declare blocks that
take blocks! ;-)

It's introduced in Ruby 1.9... :)

Thanks.

gegroet,
Erik V. - http://www.erikveen.dds.nl/

----------------------------------------------------------------

# LIBRARY

class Module
def typed(method_name, *types)
org_method = instance_method(method_name)

define_method(method_name) do |*args|
block = nil # ??? &block

if args.length > types.length
raise ArgumentError, "Wrong number of arguments (#{args.length} instead of #{types.length})."
end

args.length.times do |n|
arg = args[n]

[types[n]].flatten.each do |typ|
if typ.kind_of?(Module)
unless arg.kind_of?(typ)
raise ArgumentError, "Wrong argument type (#{arg.class} instead of #{typ}, argument #{n+1})."
end
elsif typ.kind_of?(Symbol)
unless arg.respond_to?(typ)
raise ArgumentError, "#{arg} doesn't respond to :#{typ} (argument #{n+1})."
end
else
raise ArgumentError, "Wrong type in types (#{typ}, argument #{n+1})"
end
end
end

org_method.bind(self).call(*args, &block)
end
end
end

----------------------------------------------------------------

# TEST SCRIPT

class Thing
def go(x, y, z)
# x should be Numeric
# y should be a String
# z should respond to :gsub and :to_s
:good
end

typed :go, Numeric, String, [:gsub, :to_s]
end

def test(*args)
begin
puts "#{args.inspect} : OK : #{Thing.new.go(*args).inspect}"
rescue Exception => e
puts "#{args.inspect} : NOK : #{e.message}"
end
end

test(7)
test(7, 8, 9)
test(7, 8, "9")
test(7, "8", 9)
test(7, "8", "9")

----------------------------------------------------------------
 
J

Joel VanderWerf

Erik said:
eval("alias :#{method_name} :temp_method")

This is a bit cleaner using alias_method:

class A
def temp_method; "foo"; end
method_name = "bar"
alias_method method_name, :temp_method
end

p A.new.bar # ==> "foo"
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,011
Latest member
AjaUqq1950

Latest Threads

Top