Friday, June 26, 2009

Understanding Rrails source code - Day 1

Here I start a series of blog posts about my quest to understand Rails source code.
For the last few days I was reading through ruby meta-programming resources and I think that I am ready for rails internals.

First of all I debuged a very simple app to get a feel of what is going on.
I used NetBeans 6.7 RC3 and created a Rails project. It was fine apart from jRuby being very slow starter so I switched in the project properties to the ruby 1.8.7 which comes with my Ubuntu Intrepid Ibex.

It is convenient to have rails source code (rake rails:freeze:edge) within your application so you can change it without affecting anything beyond your experiments. So I did this but after some time my rails instance stoped behave and I had two options:
  • remove vendor/rails and freeze again or
  • start using some version control to make my changes reversible
I chose the second and cloned source code from the github repository into vendor/rails. I could mess around with the code as mush as I liked and was able to quickly revert it with
git reset --hard HEAD
All those debug sessions gave me understanding of some rails details but I still didn't have a clue of the overall architecture of Rails. I knew that it is based on MVC and that it uses ActiveRecord but that was about it. How those patterns are applied I had no idea.
It is a pity that I could not find a diagram visualizing it. Fortunately Yehuda Katz, one of Rails core developers, at the end of his recent post about Rails 3 architecture promised some visualisations showing what is going on under the hood.

Uff, that was a long day with rails and git. Next time on rails architectural big picture and contributing to rails.

Thursday, June 25, 2009

More on Proxy in Ruby

Last week I was impressed how easy it is to create a proxy in Ruby. After some thinking I realized that it is not as good as it looks (at least in some situations).
First of all we don't know what methods are available because reflection does not work as we expect. The quick and dirty solution would be to simulate expected behaviour by redefining SimpleProxy's /((private|public|protected)_)?methods/ and 'respond_to?' methods so it exposes the same interface as the class we delegate calls to.
But does it? What about SimpleProxy.instance_methods? Before we instantiate this class we don't know anything about methods we should be delegating. Obviously one could have created specific class for each class one wants to proxy but this way we are loosing flexibility of our "transparent proxy" object.
There is another disadvantage of using such a proxy - tools won't recognize methods as belonging to the proxy class so you have to have documentation for an object you are hiding behind it on hand.

Thursday, June 18, 2009

Proxy design pattern in Ruby

I have recently started exploring Ruby and it's meta programing features. Just look how straightforward it is to implement proxy pattern :)


class SimpleProxy

def initialize(klass, *args)
@proxee_class, @proxee_class_args = klass, *args
@proxee = nil
end

def method_missing(symbol, *args)
if @proxee.nil?
raise NoMethodError unless @proxee_class.instance_methods(true).include?(symbol.to_s)
@proxee = @proxee_class.new(*@proxee_class_args)
end

begin
puts @proxee.class
@proxee.send(symbol, *args)
rescue NoMethodError
raise NoMethodError
end
end
end

You have to remember that calls to methods already defined on on object will not be sent to the "proxee" so you may need to redefine those to make use cases like the one in the test blow work.

require 'test/unit'
require 'simple_proxy'

class SimpleProxyTest < Test::Unit::TestCase
def test_proxy
name = 'Michal'
friends = ['Eliza', 'Radek', 'Sasha']

proxy = SimpleProxy.new(SocialAnimal, [name])

assert_equal name, proxy.name
assert_nothing_raised(Exception) {
proxy.friends = friends
}

assert_equal proxy.friends, friends

assert_match(Regexp.new("#{friends[0]}"), proxy.to_s)
end
end

class SocialAnimal

attr_reader :name
attr_accessor :friends

def initialize(name)
@name = name
end

def to_s
"#{@name}'s friends are #{@friends.join(" ,")}"
end
end