Home > methlab

methlab

Methlab is a project mainly written in Ruby, it's free.

Method definition toolkit for ruby.

Yo dawg, I heard you liked methods.

Meth Lab is a method construction toolkit intended to ease parameter validation and a number of common "method definition patterns" seen in ruby code.

=== SYNOPSIS

A lot of times in the programming world, especially in dynamically typed languages, we see this pattern:

def foo(arg1, arg2, arg3) unless arg1.kind_of?(SomeObject) raise "Incorrect object" end

unless arg2 ... 
end

end

You get the idea. Additionally, with the use of hashes for emulating named parameters, we see a ton of this in the ruby world:

def bar(*args) args = args[0]

raise "not a hash" unless args.kind_of?(Hash)

unless args.has_key?(:some_parameter)
  raise "'some parameter' does not exist"
end

unless args[:some_parameter].kind_of?(SomeObject)
  raise "'some parameter' is not a kind of SomeObject"
end

end

Meth Lab is intended to strip your methods of this wordy, but important boilerplate, by making it ideally less wordy and slamming it right in the prototype to easily evaluate what it does. Also, it has an awesomely apropos name.

=== USAGE

So, let's do something like this:

class Awesome def_ordered(:foo, String, [Integer, :optional]) do |params| str, int = params puts "I received #{str} as a String and #{int} as an Integer!" end

def_named(:bar, :foo => String, :bar => [Integer, :required]) do |params|
  puts "I received #{params[:foo]} as a String and #{params[:bar]} as an Integer!"
end

def some_method(*args) # a hash
  params = MethLab.validate_params(:foo => String, :bar => [Integer, :required])
  raise params if params.kind_of? Exception

  puts "I received #{params[:foo]} as a String and #{params[:bar]} as an Integer!"
end

end

Which yields these opportunities:

a = Awesome.new a.foo(1, "str") # raises a.foo("str", 1) # prints the message a.foo("str") # prints the message with nil as the integer

a.bar(:foo => 1, :bar => "str") # raises a.bar(:foo => "str") # raises (:bar is required) a.bar(:bar => 1) # prints message, with nil string a.bar(:foo => "str", :bar => 1) # prints message

a.some_method(:foo => "str", :bar => 1) # prints message

See the MethLab module documentation for details.

=== FAQ

1) OMG OMG OMG OMG DUCK TYPING, you don't need this at all.

a) Then don't use it. I will offer a little opinion though: if you think duck typing can solve everything, chances are you aren't hitting a whole class of edge cases in your code. Also, punk rock is dead, get with the times and think for yourself.

2) Exceptions; how do I debug validation failures?

a) Meth Lab works hard to not raise until the last possible minute for validation failures. Generally you will see a trace like this:

(lines wrapped for sanity)

lib/methlab.rb:95:in `bar': value of argument '1' is an invalid
    type. Requires 'Integer' (ArgumentError)
        from lib/my-actual-file.rb:126

What's important to understand here is that, short of making you, the user, handle raising validation errors yourself (which would kind of defeat the point), we raise just above where your code would execute. This means that both parts of this trace are significant; the first line is the error and the method the error was raised from, but the second line is the place that the method was called.

I will not even pretend this is anything other than a "misfeature", but it is a consequence to working with a library like this.

MethLab will always raise ArgumentError, which is standard for good ruby programs.

3) Don't you solve this with blocks? Are closures an issue?

a) The short, proper answer to this is "yes". The pragmatic answer to this is "probably not". Closures are created and there is a performance impact to relying on them. I would argue that if performance is your concern, you shouldn't be using ruby at all. Meth Lab does make every effort possible to not use proc objects where possible, e.g., the check methods themselves live in the MethLab namespace and are not anonymously injected into your methods (which would be very expensive, and cause inconsistency if someone were to redefine them).

4) I don't want to use MethLab everywhere, but I'd like to use it in a specific class or module.

a) "extend MethLab" in your namespace before using the definition syntax.

5) I want to use MethLab everywhere.

a) MethLab.integrate does this. If you want it to happen at require time, set the global $METHLAB_AUTOINTEGRATE to a true value before doing so. These both pollute ::Module and ::main, so be aware of the consequences.

=== TODO

  • respond_to? checks.

  • multiplexed array/hash methods.
  • construction of methods that can take inline blocks (e.g., "def foo(&block)"). Proc can be used now, but it's not very "rubyish" syntax for the user.
  • Better handling in Modules (def self.meth(...))
  • Default values

=== THANKS

  • James Tucker (raggi) and "saywatmang" for inspiration and a bit of opinion and code review.
  • Eric Hodel (drbrain) for lots and lots of mentoring and generally being a standup dude.
  • Params::Validate and Method::Signatures from CPAN for doing it right first. Imitation is the sincerest form of flattery and good ideas should not be kept in an ivory tower, so prematurely shut it plox.
Previous:Mancala-game