Ruby metaprogramming question

Hi guys,

I was just looking at some articles on Ruby metaprogramming and came
across this example:

class Talker
[:hello, :good_bye].each do |arg|
method_name = (“say_” + arg.to_s).to_sym
send :define_method, method_name do
puts arg
end
end
end

Could we not write this the following way? I don’t understand why would
anyone choose the above option, it seems confusing to me.

class Talker
[:hello, :good_bye].each do |arg|
method_name = (“say_” + arg.to_s).to_sym
define_method(method_name) { puts arg }
end
end
end

Why use send in this case?

Mateusz,

you’re right it doesn’t need send. Further, you do not even need the
to_sym as define_method accepts strings.

Mateusz W. wrote in post #1007002:

Hi guys,

I was just looking at some articles on Ruby metaprogramming and came
across this example:

class Talker
[:hello, :good_bye].each do |arg|
method_name = (“say_” + arg.to_s).to_sym
send :define_method, method_name do
puts arg
end
end
end

Could we not write this the following way? I don’t understand why would
anyone choose the above option, it seems confusing to me.

class Talker
[:hello, :good_bye].each do |arg|
method_name = (“say_” + arg.to_s).to_sym
define_method(method_name) { puts arg }
end
end
end

Why use send in this case?

Mateusz W. wrote in post #1007002:

Hi guys,

I was just looking at some articles on Ruby metaprogramming and came
across this example:

class Talker
[:hello, :good_bye].each do |arg|
method_name = (“say_” + arg.to_s).to_sym
send :define_method, method_name do
puts arg
end
end
end

Could we not write this the following way? I don’t understand why would
anyone choose the above option, it seems confusing to me.

define_method is private, which means you can’t write an explicit
receiver when you call it. If you were in a scope where self did not
equal Talker, then you could write Talker.send(…), but you could
not write Talker.define_method(…). The send() method lets you violate
all kinds of Ruby privacy.

Here is an example:

class Talker
def initialize(*names)
names.each do |name|
method_name = “say_#{name}”
define_method(method_name) do
puts name
end
end
end
end

t = Talker.new(:hello, :goodbye)
t.say_hello
t.say_goodbye

–output:–
ruby.rb:5:in initialize': undefined methoddefine_method’ for
#Talker:0x10016a070 (NoMethodError)
from ruby.rb:3:in each' from ruby.rb:3:ininitialize’
from ruby.rb:12:in `new’
from ruby.rb:12

Inside initialize(), ruby implicitly does this:

    self.define_method(...)

But inside initialize(), self = t so that is equivalent to:

    t.define_method(...)

…and t is not a class or module, so t does not have a method called
define_method().

On the other hand, you can explicitly write a receiver when you
call send():

class Talker
def initialize(*names)
names.each do |name|
method_name = “say_#{name}”
self.class.send(:define_method, method_name) do
puts name
end
end
end
end

t = Talker.new(:hello, :goodbye)
t.say_hello
t.say_goodbye

–output:–
hello
goodbye

So the send() form can be used anywhere, but define_method() can
only be called in a scope where self equals the class/module in which
you want to define an instance method.

Another option is to create a scope inside initialize() where
self=Talker using class_eval(), and then call define_method()
within that scope:

class Talker
def initialize(*names)
names.each do |name|
method_name = “say_#{name}”
self.class.class_eval do #self.class=Talker
define_method(method_name) do
puts name
end
end
end
end
end

t = Talker.new(:hello, :goodbye)
t.say_hello
t.say_goodbye

–output:–
hello
goodbye

Inside the class_eval() block, self is equal to the object that called
class_eval().