Forum: Ruby define_method with a &block?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
4b38034fdb66886a91d55fcd12964cee?d=identicon&s=25 Erik Veenstra (Guest)
on 2006-02-05 02:21
(Received via mailing list)
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){}
Cb48ca5059faf7409a5ab3745a964696?d=identicon&s=25 unknown (Guest)
on 2006-02-05 04:48
(Received via mailing list)
On Sun, 5 Feb 2006, Erik Veenstra wrote:

>
> 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
47b1910084592eb77a032bc7d8d1a84e?d=identicon&s=25 Joel VanderWerf (Guest)
on 2006-02-05 06:10
(Received via mailing list)
ara.t.howard@noaa.gov wrote:
>     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.
4b38034fdb66886a91d55fcd12964cee?d=identicon&s=25 Erik Veenstra (Guest)
on 2006-02-05 13:10
(Received via mailing list)
>  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){}
Cb48ca5059faf7409a5ab3745a964696?d=identicon&s=25 unknown (Guest)
on 2006-02-05 17:32
(Received via mailing list)
On Sun, 5 Feb 2006, Erik Veenstra wrote:


> 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
4b38034fdb66886a91d55fcd12964cee?d=identicon&s=25 Erik Veenstra (Guest)
on 2006-02-05 20:19
(Received via mailing list)
> 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")
47b1910084592eb77a032bc7d8d1a84e?d=identicon&s=25 Joel VanderWerf (Guest)
on 2006-02-05 22:04
(Received via mailing list)
Erik Veenstra wrote:
>      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"
This topic is locked and can not be replied to.