MetaProgramming

Various meta-programming methods and ‘spells’ for Ruby

Resources

Install

  • sudo gem install meta_programming

Use

  • require ‘meta_programming’

Concepts/Terminology

Example Code

Beta Status

I consider this Gem in personal beta and suspect it will be in this state for a while. If you use it, expect changes that may break your code until the lexigraph has a chance to mature.

If you include metaprogramming in your projects, I suggest you limit the version dependency to 0.2.x, something like

s.add_dependency('meta_programming', '>= 0.2.0', '< 0.3.0')

Any 0.2.x version will be backward compatible with any earlier 0.2.x version. A bump to 0.3.0 cannot be guaranteed backward compatible.

I welcome comments and collaborators who would like to develop a library for simplifying common patterns and limit the pitfalls of Ruby meta-programming.

Changelog

  • 0.2.0

Removes object parameter from method blocks and lambda matchers in define_ghost_method, and adds more descriptive error handling. define_ghost_method block and matchers are now evaluated in the context of the object instance.

I’ve bumped the version to 0.2.0 because this changes the interface.

  • 0.0.8

initial implementation

Issues

1.9 vs 1.8

There are considerable differences with the way that Ruby 1.9 and 1.8 handle dynamic methods, procs and lambdas. For instance, Ruby 1.8.x cannot handle default parameter values in the block parameter list

call_some_method(*args) do |param1, param2=nil| #oops for Ruby 1.8
  ...
end

This raises support issues for this library. Should the library fully support both versions of Ruby, cripple 1.8 at the expense of 1.9, or kiss the past good-bye and only support version 1.9? Of course, the latter is clearer and easier, and may be the best path to pursue.

Description

Current Methods

  • eigenclass (or metaclass)

  • safe_alias_method_chain

  • define_chained_method

  • define_method_missing_chain

  • define_ghost_method

  • blank_slate

Usage

define_ghost_method

define_ghost_method creates a ‘ghost’ method for an undefined method that corresponds to the ‘matcher’ parameter.

define_ghost_method(matcher) do |method_name_symbol, *args|
  ...method body...
end

A block must be present and it takes (optionally) the receiving object, the method name (as a symbol with exception of a lambda matcher) and the method arguments. As of 0.2.0, the block and lambda matcher is evaluated in the context of the object.

The matcher may be any of a symbol, string, regular expression or Proc/lambda.

String, symbols and regular expression matchers

define_ghost_method('my_method) {|sym, *args| ... }
define_ghost_method(:my_method) {|sym, *args| ... }
define_ghost_method(/^my_method$/) {|sym, *args| ... }

String and symbol matchers will only process the method if its name matches the string or symbol exactly. Regular expressions provide greater flexibility and care must be taken to define scrutinizing regular expression matchers. It is a good practice to use the /^ and $/ matchers to limit the scope.

In all cases, the method name is passed in the second parameter as a symbol to the method block. This is not the case with lambda matchers.

Proc/lambda matchers

Lambda matchers provide an opportunity to greatly scrutinize the method call. The method is invoked for any lambda not evaluating to nil or false.

proc = lambda{|method_name| ...matching code... }
define_ghost_method(proc) {|proc_result, *args| ... }

Proc and lambda matchers differ from the other matcher in that the second parameter passes the result of the Proc/lambda matcher to the method block. This feature lets the matcher pre-process the name of the method passed to the body.

proc = lambda{|sym| sym =~ /^not_(.+\?)$/ && self.class.method_defined?($1.to_sym) && $1.to_sym }

The lambda matcher is evaluated in the context of the object and can call methods of the object as in the above example. The lambda matcher is also called in the respond_to? method, so it is a good idea to avoid usind respond_to? in the lambda matcher to avoid a stack overflow error. Instead call method_defined? on the class object.

The extra flexibility comes at greater responsibility. If you only want to pass the method name symbol to the method block, don’t forget to return it from the lambda.

proc = lambda{|sym| sym =~ /^not_.+\?$/ && sym }

Critique

2010.04.30

I’m not satisfied with the ghost method implementation.

2010.05.02

I like the fact that define_ghost_method blocks and lambda matchers are now evaluated in the context of the object and that the object is no longer passed into the method block and lambda matcher. The method now behaves similarly to its cousin, define_method.

Testing

Passes for Ruby

  • 1.8.7

  • 1.9.1

Dependencies

none