after_method

It started innocently enough. I was writing a Rails Plugin, and needed to call a Class Method on a Model. No problemo, right? In init.rb just call the method (To register a renderer in this case).


  Site.register_template_handler(".erubis", ErubisTemplate)

The method goes on and adds some date to a Class Variable. I try it and it worked…

… For the first request only :( After that, the data is gone from the class! What gives???

Well, there is a very convenient mechanism that reloads all your models, views and controllers so you don’t have to restart your app every time you make a change. Except here. Once the class gets unloaded, BAM! There goes the Class Variable and my carefully registered renderer.

First Solution

The code responsible for unloading and reloading all the models, views, and controllers is Dispatcher.reset_application!, so why not piggy back on reset_application! ? So using alias_method_chain, after a few iteration, here’s what I came up with:


  class << Dispatcher
    def register_erubis_template
      Site.register_template_handler(".erubis", ErubisTemplate)
    end
    def reset_application_with_erubis_registration!
      returning reset_application_without_erubis_registration! do
        register_erubis_template
      end
    end
    alias_method_chain :reset_application!, :erubis_registration
  end
  Dispatcher.register_erubis_template

Since reset_application! is not called till the second request, I had to also call the registration method once, which is why I created a register_erubis_template method.

It now worked. That was nice. But, then I started implementing a second plugin, which needed the same code. Not very DRY.

So I wrote down what ideally I wanted to end up with:


after_reset_application {
  Site.register_template_handler(".erubis", ErubisTemplate)
}

So next time I have the same problem, I can just reuse the same after_reset_application call with any other code I need to run after all the classes get unloaded.

Lots of aborted attempts

Foolishly, I thought it would be quite easy to transform what I had into something reusable. A little dose of Monkey Patching here and there, a bit of Meta Programming, and there you have it. Well, I did not keep all the many iterations I tried, here are just a few that did not work for one reason or an other (I did not try to find out why, just moved on to the next attempt)


  Dispatcher.alias_method_chain :reset_application!, #{feature}
  Dispatcher.class_eval do
    def reset_application_with_#{feature}!
      returning reset_application_without_#{feature}! do
        register_#{feature}
      end
    end
  end

def after_reset_application(feature, &block)
  patch = <<-end_eval
    class << Dispatcher
      def register_#{feature}
        proc
      end
      def reset_application_with_#{feature}!
        returning reset_application_without_#{feature}! do
          register_#{feature}
        end
      end
      alias_method_chain :reset_application!, #{feature}
    end
  end_eval
  eval patch#, &block, __FILE__, __LINE__
  Dispatcher.send "register_#{feature}"
end

Use the singleton-class, Luke! Use the singleton-class!

Then I retrieved a post from Ola Bini that I had read some months back: The ruby singleton class and I started to make some progress after I understood that to define methods, I needed to do it on the singleton class, not the class itself, because even though it did not fail, it did not accomplish much. And finally arrived at:

To retrieve that singleton class:


class << Dispatcher; self end

So now, I finally arrived at this code:


require 'dispatcher'

def after_reset_application(feature, &block)
  class << Dispatcher; self end.class_eval do
    define_method("register_#{feature}", &block)
    define_method("reset_application_with_#{feature}!") {
      returning Dispatcher.send("reset_application_without_#{feature}!") do
        Dispatcher.send("register_#{feature}")
      end
    }
    alias_method_chain :reset_application!, "#{feature}"
  end
  Dispatcher.send("register_#{feature}")
end

after_reset_application("erubis_registration") {
  Site.register_template_handler(".erubis", ErubisTemplate)
}

The key reasons why this works is that it is adding the methods to the singleton class, not the class itself. It is using send to be able to build the method names, and using alias_method_chain to be able to monkey patch reset_application! as many time as needed. I had to add a parameter to after_reset_application for the same reason, so it can be called more than once. And it uses define_method because you can give it a block, plus you can define the method name from variables.

All good, except that it is still not very DRY. What if I want to use the same technique on a different class? A different method?

The final version (for now?)

This one was easy, just add a few parameters, deal safely with ?, ! and = and there you go


def after_method(klass, target, feature, &block)
  # Strip out punctuation on predicates or bang methods since
  # e.g. target?_without_feature is not a valid method name.
  aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
  class << klass; self end.class_eval do
    define_method("register_#{feature}", &block)
    define_method("#{aliased_target}_with_#{feature}#{punctuation}") {
      returning klass.send("#{aliased_target}_without_#{feature}#{punctuation}") do
        klass.send("register_#{feature}")
      end
    }
    alias_method_chain target, "#{feature}"
  end
  klass.send("register_#{feature}")
end

Then, to implement after_reset_application:


require 'dispatcher'

def after_reset_application(feature, &block)
  after_method(Dispatcher, :reset_application!, feature, &block)
end

And to use:


after_reset_application("erubis_registration") {
  Site.register_template_handler(".erubis", ErubisTemplate)
}

Phew! The block gets called once the first time, and every time the classes need to be reloaded.

Install your own ruby on a shared host

Since I upgraded to Typo 4.0, and in the process rails 1.1.6 I have had a few occurrences where nanoRAILS would hang, several bloated processes would be sitting there and not respond, and the only option at that point was to kill all ruby processes once I realized what was happening, which could be several hours. Suffice it to say, this is not a good option.

So after struggling during last rails upgrade to 1.1 on my host, the next logical step is to also use my own version of ruby so I can have better control on its environment, and even apply patches if necessary.

The following steps apply on a lot of systems. More specifically, my host is DreamHost (aff), and as best I can tell, I’m on a host with Debian Sarge.

Build your own Ruby

Download ruby from http://www.ruby-lang.org. The latest version is currently ftp://ftp.ruby-lang.org/pub/ruby/ruby-1.8.4.tar.gz

Create the makefile using

 ./configure prefix=[YOUR_OWN_RUBY_PREFIX]

Since you most likely don’t have root access, you need to override where ruby think it resides, and the way to do that is to set the prefix to somewhere into your home directory. Something like /home/USERNAME/ruby for example. From that point on, libraries, other builtin ruby files, gems will automatically install into your own ruby repository so you never have to worry about getting in trouble with an unforeseen upgrade.

Optionally, you can apply the patch used by Railsbench, with hardcoded default values because I haven’t figured how to set the environment variables for the dispatch.fcgi process (since apache in my case determines that). Download my version of rubygc.patch .

 patch gc.c rubygc.patch

Build and install ruby

 make
 make install

Additionally, so that the command line uses the same version of ruby, add this to your .bashrc or equivalent for your shel.

export PATH=[YOUR_OWN_RUBY_PREFIX]/bin:$PATH

Install your own gems

Now you are ready to install your own gems. Here’s the bare minimum you need.

First, install rubygems

 wget http://rubyforge.org/frs/download.php/11289/rubygems-0.9.0.tgz
 tar xzvf rubygems-0.9.0.tgz
 cd rubygems-0.9.0
 ruby setup.rb

Then install the minimum set of gems:

 gem install mysql
 gem install fcgi
 gem install rails --include-dependencies

Now, the only thing you need is to change the path to ruby in your dispatch file (dispatch.rb for mod_cgi, dispatch.cgi for regular cgi, and dispatch.fcgi for FastCGI/fcgid)

Typically, replace

#!/usr/bin/env ruby

by

#![YOUR_OWN_RUBY_PREFIX]/bin/ruby

Replace [YOUR_OWN_RUBY_PREFIX] by your own value you used earlier.