views:

420

answers:

3

I have a class with some methods. It's supersekret but I've reproduced what I can here.

Class RayGun
  # flashes red light
  # requires confirmation
  # makes "zowowowowowow" sound
  def stun!
    # ...
  end

  # flashes blue light
  # does not require confirmation
  # makes "trrrtrtrtrrtrtrtrtrtrtr" sound
  def freeze!
    # ...
  end

  # doesn't flash any lights
  # does not require confirmation
  # makes Windows startup sound
  def killoblast!
    # ...
  end
end

I want to be able to, at runtime, interrogate the class about one of the methods and receive a hash or struct like so:

  {:lights => 'red', :confirmation => false, :sound => 'windows'}

What's the best way of doing this? Obviously you could have separate YAML file alongside and set up a convention to relate the two, but ideally I want code and metadata in one place.

The most promising idea I can come up with is something like this:

class RayGun
  cattr_accessor :metadata
  def self.register_method(hsh)
    define_method(hsh.name, hsh.block)
    metadata[hsh[:name]] = hsh
  end

  register_method({
    :name => 'stun!', 
    :lights => 'red', 
    :confirmation => 'true', 
    :sound => 'zowowo',
    :block => Proc.new do
      # code goes here
   })

   # etc.
end

Anyone got some better ideas? Am I barking up a very wrong tree?

+2  A: 

Just a little beautyfication:

class RayGun
  cattr_accessor :metadata
  def self.register_method(name, hsh, &block)
    define_method(name, block)
    metadata[name] = hsh
  end

  register_method( 'stun!',
    :lights => 'red', 
    :confirmation => 'true', 
    :sound => 'zowowo',
    ) do
      # code goes here
  end

   # etc.
end

You do lose easy access to the original closure, but probably don't need it.

To answer the question, it doesn't look bad, you could do something a little more convetional but probably good enough:

class RayGun
  cattr_accessor :metadata

  @metadata[:stun!] = {:lights => 'red', 
                        :confirmation => 'true', 
                        :sound => 'zowowo'}
  def stun!
    # ...
  end

   # etc.
end

In the original example register_method is public, if you planned to use it that way then the second option becomes less usefull because it doesn't ensure consistency.

krusty.ar
+1  A: 

There's the YARD tool which lets you add metadata to methods. I'm pretty sure it just sticks the metadata into the rdoc files you generate, but you could easily build off of that to get runtime information.

James A. Rosen
+1  A: 

I found another strategy poking around http://github.com/wycats/thor/tree. Thor lets you write stuff like this:

Class RayGun < Thor
  desc "Flashes red light and makes zowowowowow sound"
  method_options :confirmation => :required
  def stun!
    # ...
  end
end

It manages this by using the (undocumented) hook Module#method_added. It works like this:

  1. Call to Thor#desc and Thor#method_options set instance variables @desc, @method_options.

  2. Defining method stun! calls Thor#method_added(meth)

  3. Thor#method_added registers Task.new(meth.to_s, @desc, @method_options) (roughly speaking) and unsets @desc, @method_options.

  4. It's now ready for the next method

Neat! So neat that I am going to accept my own answer :)

Dave Nolan
method_added may be undocumented on rdoc, but it is on a documentation linked by the official site: http://www.ruby-doc.org/docs/ProgrammingRuby/html/ospace.html
Daniel Ribeiro