Defining methods, Javascript-style

The knowledgeable amongst you must have known that Ruby supports so called singleton methods which basically means methods can be defined on the instances of the class, rather than class itself. So, for example, you could redefine a method on a particular object and do all sorts of fun stuff. This is how it's usually done:

obj = "something"

def obj.my_new_method
  "does nothing"
end

obj.my_new_method # => "does nothing"

However, I like the javascript approach to this better. In javascript, function is treated as a variable you simply assign to the property of the object.

var obj = new String("something")
obj.myNewMethod = function() { return "does nothing" };
obj.myNewMethod(); // => "does nothing"
 

Fear not! With some meta programming you can achieve the same in Ruby. Let's see how.

module DynamicMethods
  def method_missing(method, *args)
    raise NoMethodError unless method.to_s[-1..-1] == "="
 
    (class << self; self; end).class_eval do
      define_method(method.to_s[0..-2].to_sym, args.first)
    end
  end
end

obj = "hello, friend!" 
obj.extend DynamicMethods
obj.piratize = proc { self.gsub("hello", "ahoy").gsub("friend", "matey") }
obj.piratize # => "ahoy, matey!"

As you have guessed, the trick is in our DynamicMethods module. This module creates a method on the fly, by invoking a non-existent method ending with "=" and a single proc argument. Note that the proc method is evaluated in the context of the "hello, friend!" object, not it's surroundings - which is the expected behaviour when defining methods.

So there you have it - instant pirate talk and method definitions javascript-style.

Filed under  //   javascript   ruby  

Comments (4)

Feb 06, 2010
pjb3 said...
I love it! Here's a version with a couple tweaks:

http://gist.github.com/296967

This allows you to assign values that aren't Procs and treats them like attributes. Also, this calls super, which is better in case something else along the class hierarchy has defined method_missing as well

Feb 07, 2010
Thanks for the input, I appreciate it!
Feb 11, 2010
matthewrudy said...
I did a bit of refactoring, making sure that you can swap between a proc and a value.

But I guess for true javascript equivalence, should probably just set the value,
and then rely on saying "object.name.call(23)"
rather than expecting ruby-style method invocation.

Feb 11, 2010
matthewrudy said...
of course I forgot my link
http://gist.github.com/302269

Leave a comment...

About

A web developer based in Split, Croatia. I do web apps using Ruby, Rails, Dotnet, Php (in order of preference) and lots of coffe. Add to that fondness for agile development, open source and that penguin OS.

Not being exactly the blogging type, I'll at least try to keep my posts updated with some usefull tips and stuff on what I'm doing development-wise. Heck, I might even throw some music recommendation just to prevent things from becoming too geeky.