« Publishing My Rails Plugins | Main | Row Version Migrations »

Alias a Static Method in Ruby

Listen to this articleListen to this article

As much as I love Ruby on Rails, one of the things that dissapoints me is the rather large number of static methods. Maybe it's my Java background but class methods just irk me: They're difficult to override, mock, stub-out, and they're inherintly thread-unfriendly. But that aside, the fact remains that they exist and, now and again, I need to mess with them. Case in point: My Foreign Key Migrations plugin.

The plugin was working fantastically—thanks to the efforts of Ted Davis for giving it a work out—until it came time to running functional tests. Unfortunately, but unsurprisingly, the schema dumper used to copy the database structure from development to test creates tables in alphabetical order. Now I can't actually think of a much better alternative but the problem is that this means the foreign key clauses were being generated against tables that possibly didn't exist at the time of execution. (For example, Order sorts before Product however the orders table has a foreign key to the products table.)

So anyway, the longer-term solution is to batch up the foreign-key declarations until the end of the script and then execute them but in the meantime, I just wanted to disable their generation altogether. In either case, I needed to interfere with the execution of ActiveRecord::Schema.define a static (boo, hiss) method. Now, for the fun bit.

In Ruby, the way to safely mix-in methods (rather than use subclassing) is to use some method chaining. For this we use alias_method. So, for example, if we wanted to override to_s to always place quotes around the value (nopt very useful but it will suffice for now) we could write a module like this:

module QuoteToS
  def self.included(base)
    base.class_eval do
      alias_method :to_s_without_quotes, :to_s unless method_defined?(:to_s_without_quotes)
      alias_method :to_s, :to_s_with_quotes
    end
  end

  def to_s_with_quotes
    "'#{to_s_without_quotes}'"
  end
end

This essentially says that when the module is included (ie mixed-in) to a class then: add a method named to_s_with_quotes; create an alias of the existing to_s named to_s_without_quotes; make an alias of the new to_s_with_quotes named to_s; and finally, whenever to_s. is executed, call the old to_s method (now named to_s_without_quotes) and surround the results with, you guess it, quotes.

To use this you would either manually include the module in a class or, more along the lines of aspects, force the inclusion with some code like this:

MyClass.send(:include, QuoteToS)

(As a side note, the use of unless method_defined?(:to_s_without_quotes) is to work-around a bug in Ruby 1.8.4 that causes an infinite recursion when using alias_method. I never detected it under Mac OS X but apparently it affects windows machines with monotonous regularity. D'oh!)

So that's all very well and good but what happens when you need to do the same thing with static methods? The answer is, use class << self and extend. In my case, overriding the behaviour of ActiveRecord::Schema.define looks something like this:

module ForeignKeyMigrations::Schema
  def self.included(base)
    base.extend(ClassMethods)
    base.class_eval do
      class << self
        alias_method :define_without_fk, :define unless method_defined?(:define_without_fk)
        alias_method :define, :define_with_fk
      end
    end
  end
  
  module ClassMethods
    def define_with_fk(info={}, &block)
      ...
      define_without_fk(info, &block)
    end
  end
end

Here, the use of base.extend causes all the methods defined within the module ClassMethods—an aribitrary name used by convention in most if not all the rails code I've ever seen—to be added as static methods on the class. Then, surrounding the alias_method calls within a class << self causes them to be executed in a static context.

Again, to have this code mixed-in to the existing ActiveRecord::Schema class looks like this:

ActiveRecord::Schema.send(:include, ForeignKeyMigrations::Schema)

Phew!

P.S. all the plugins are now available via SVN.

Update: See the comments on how to simplify the code thanks to Ryan Tomayko.

Comments

In your QuoteToS example, shouldn't the body of to_s_with_quotes should actually be:
return "'#{to_s_with_quotes}'"

Doh! I *had* to get that wrong. Of course i meant:
return "'#{to_s_without_quotes}'"

:)

Hey, thanks for the credit :-)

I look forward to the batching feature so that I can FINALLY get referential integrity in my testing database... that's annoyed me about Rails since day one! I have several unit tests written specifically to point-out that referential integrity is broken (they insert orphaned and duplicate records). I long for the day when they all pass :-)

Johan,

Fixed. I was coding examples on-the-fly again and then raced out to watch a movie...d'oh!

Simon, while I get what you set out to show in this example, I have to ask you to explain to me what you meant by "Maybe it's my Java background but class methods just irk me: They're difficult to override, mock, stub-out, and they're inherintly thread-unfriendly". Well, :) not so much what you meant - as it is clear as crystal - but could you give me a concrete example of where a class method proved to be difficult to handle as you so mention.

I tried out this following example -
class B
def self.foo
puts "Base"
end
end

class D < B
def self.foo
super
puts "Derieved"
end
end

now - calling D.foo puts out the following
Base
Derieved

which is what one would expect because classes are just objects - just lke anything else. Isn't static the wrong word to use in this context? From the java world atleast static methods are invoked using invokestatic instead of invokevirtual opcodes and the call is determined at compile time (hence the "static" moniker). In a language like Ruby, in which classes behave just like normal everyday objects, what is the real problem with class methods? I agree with the thread safety part - but understanding that the object identified by the constant representing the class is a global/shared resource and should be used as such - should take care of the problem.

I don't mean to come down on you bad, but your posts usually make complete sense, but this one line did not. Could you please clarify what you really meant?

Static isn't the correct term I agree. The correct term is class method but the only analogue in Java is a static method so I guess that's really why I used the term--so those not familiar with Ruby, yet have an understanding of Java, might follow. Maybe that was a mistake on my part. It wasn't the static nature of the method I was really interested in so much as the fact that it's defined at the class level and thus shared across all instances of the class.

As to why I dislike class methods, because they're shared across all instances which makes them inherintly thread un-friendly unless you take extra care; difficult to override not in effort but in effect--if I override it for one I override it for all; and, really an extension of the previous point, difficult to test because if I mock/stub it for one instance then I affect all instances.

In the example I used, I needed to override behaviour of Schema.define--which is a class method--to set some state that is unfortunately shared across all instances and because that state affects the behaviour, then I will inadvertently affect the behaviour of all instances in all threads. As I mentioned, probably not a real cause for concern in this particular case but that's the problem with this class (pardon the overloaded use of the term) of problem: you never know just when it might come back to bite you in the bum :)

I can honestly say I've managed to remove all traces of class methods from all my applications thus far and I believe that the code is cleaner, make's more sense, is easier to debug and maintain. I am certainly of the opinion that, except in a VERY small number of cases, there is no reason to use class methods (or properties for that matter) to share global state; if you wish to share state, create an object and pass it around. This is not to say that they aren't useful, just that they are abused...IMHO :)

Hope that clarifies things a little :)

Simon

Hmm..interesting...anything to remove syntax is a good thing! Thanks for the heads-up. I'll check it out.

You can easily mock/stub methods on one instance of the class object quite easily actually, by using the eigenclass of it. Just do MyClass.dup into a variable )which will give you just as good of a class as any, just not assigned to a constant) and then call the class <<var to hop into it's class definition at will.

And I am not Simon Harris, not at all.

Nice tip!

Sorry, the way your comments are laid out brought me into confusion as to where the commenter's name is. Too late and too hot here.

Hi Simon,
Can you clarify these comments:
" can honestly say I've managed to remove all traces of class methods from all my applications thus far and I believe that the code is cleaner, make's more sense, is easier to debug and maintain"

Most of the rail's model code (model in rails sense, active record objects) have extended the model behavior via class methods. so, I'd be curious to know how you are avoiding this?

I'm new to Ruby/Rails, and all I can imagine is code that looks like this (just a simple example):

class Visit ["visit_dt >= ? and visit_dt ["visit_dt >= ? and visit_dt < ?", now.at_beginning_of_week, now.next_week ])
end
end

class DashboardController < ApplicationController

def index
@member_visits_this_week = Visit.count_visits_this_week
end
end

Hmm, that code didn't come cleanly through..

Basically, I was curious as to how you are extending the behavior of your rails model (objects that extended ActiveRecord) and then how you use those objects in your controllers..
I'm assuming without a class method that you first have to instantiate a model class and then call the instance method?

@model = Model.new
@model.do_something

vs

Model.do_something

Is this correct?

Dan,

I was referring mainly to work done in other languages such as Java and C++ (and Ruby in general for that matter) not really Rails--I almost consider Rails another language.

I sympathise with your comments about class methods being "difficult to test because if I mock/stub it for one instance then I affect all instances". Have a look at Mocha. It gives you a simple readable way to mock or stub class methods as well as putting the class back together again after each test method.

Thanks a ton. I was beating my head against the wall trying to redefine a class instance method from a module in a Rails plugin, to no avail. I kept trying different combos of extend and include with the right module nesting, but a little base.class_eval combined with alias_method, from your first example, made it much cleaner and easier.

Post a comment