Wednesday, March 23, 2011

What's the best strategy for get/setting metadata on Ruby methods at runtime?

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?

From stackoverflow
  • 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.

  • 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.

  • 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 :)

    Daniel Ribeiro : 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

0 comments:

Post a Comment