Routes in Rails 3 looks amazing compared to Rails 2. One of the great aspects in the revamped routes is the removed map. An example from Rails 2:
map.resources :assemblies do |assemblies|
assemblies.resources :parts
end
In Rails 3 we would write this as:
resources :assemblies do
resources :parts
end
So how can we get this syntax going for ourselves? Playing around in my new Ruby wrapper for the Instapaper API I wanted to go from:
InstaPush.connect "kjbekkelund@gmail.com" do |conn|
conn.add "http://kimjoar.net"
conn.add "http://bjorkoy.com"
end
to:
InstaPush.connect "kjbekkelund@gmail.com" do
add "http://kimjoar.net"
add "http://bjorkoy.com"
end
To solve this problem we can use some Ruby metaprogramming goodness, namely instance_eval. By changing from yielding to the block, to using instance eval on the block — to get the correct scope — we don’t longer need the conn. Here’s how my code ended up:
def self.connect(username, password = nil, &block)
conn = new username, password
if block_given?
conn.instance_eval(&block)
else
conn
end
end
Now all the methods defined in the conn instance will be available in the block. Such a simple trick, but very powerful. Yet another great day to be a Rubyist.
Edit (August 3. 2010):
Judofyr commented on this technique on Hacker News with a slight modification. Let’s change the code to:
def self.connect(username, password = nil, &block)
conn = new username, password
if block_given?
if block.arity == 1
block.call(conn)
else
conn.instance_eval(&block)
end
else
conn
end
end
Now we are able to do both
InstaPush.connect "mail@kimjoar.net" { authenticate }
and
InstaPush.connect "mail@kimjoar.net" { |conn| conn.authenticate }