Ruby Fu

Secrets of a Ruby Ninja for your edification and amusement.

Wednesday, May 30, 2007

 

Anonymous Block Pitfall

You should understand this...
a=[:x,:y,:z] # => [:x, :y, :z]
a.map{|a|} # => [nil, nil, nil]
a # => :z

...to know why this routes.rb example using with_options...
map.with_options :controller => 'users',
:action => 'show' do |map|
map.user_groups '/users/:id/groups', :section => 'groups'
map.user_history '/users/:id/history', :section => 'history'
end

...is bad.

Don't reuse the name of a local variable as a yielded local variable for use inside of a block, because the assignment will persist after the lifespan of the block. The routes example should look more like this...
map.with_options :controller => 'users',
:action => 'show' do |users|
users.user_groups '/users/:id/groups', :section => 'groups'
users.user_history '/users/:id/history', :section => 'history'
end

That is all.

Labels: , , ,


posted by Brendan Baldwin  # 1:16 PM   1 Comments   Links to this post

Wednesday, May 16, 2007

 

super is super

The super keyword is pretty sweet. It remembers the context under which it was used, even when you call an alias of the containing method. Example:
class X
def method
"got it"
end
end
class Y < X
def method
"#{super}!"
end
end
class Z < Y
alias_method :original_method, :method
def method
"you #{original_method}"
end
end

X.new.method # <= "got it"
Y.new.method # <= "got it!"
Z.new.method # <= "you got it!"

This behavior gives alias_method real flexibility when used to modify behaviors by composing method chains to pre-defined methods. I was first surprised by this behavior, but it was a pleasant surprise as it meant I didn't have to worry about 'super' suddenly referring to something else when delegating to aliased methods.

Labels: , , , ,


posted by Brendan Baldwin  # 12:19 PM   0 Comments   Links to this post

Tuesday, May 15, 2007

 

Transparent Proxies, Part 1

This is an old one, but I tightened up the code a little bit. Take a look at the following:
module Enumerable
def all
MapProxy.new self
end
end
class Enumerable::MapProxy
instance_methods.each do |m|
undef_method m unless m.to_s=~/^__(id|send)__$/
end
private
def initialize proxied
@proxied=proxied
end
def method_missing m,*a,&b
@proxied.map{|o|o.__send__ m, *a, &b} # thanks, NiKA
end
end

Basically it lets you write [1,2,3].all.to_s and get back ["1","2","3"]. It does so by creating an instance of the Enumerable::MapProxy class that dispatches any method you send it right on down to each element of the Enumerable object (an Array in the case of [1,2,3]). It's called MapProxy because it dispatches the method to each element by way of the #map method.

Its pretty convenient for some kinds of operations, but there's a downside to using Transparent Proxies-- I call them "Transparent" because as far as you can tell when you interact with it, it doesn't actually identify itself in the traditional way. If you say [1,2,3].all.class you don't get back a class, you get an Array of classes, which are the classes of the elements in the collection.

The downside therefore is that you get this object with extremely interesting behaviour, but don't really know what's going on unless you can figure out where the object came from.

If you use Rails, you already use Transparent Proxies, since the AssociationProxy class in the ActiveRecord::Associations module is a well-known example of one. In many ways the effect is seamless and great, but it can be the cause of some pretty bizarre behaviour, especially in light of the way it chooses to direct method_missing calls back down to the class the association represents.

For example, did you know that if you have a belongs_to :customer association on your Order class, that @order.belongs_to actually returns an instance of the AssociationProxy class?

If you are ever stuck with an object that you "think" might be an instance of a specific Transparent Proxy class, here is a handy way to find out:
class Class
def one_of_me? object
ObjectSpace.each_object(self) do |o|
return true if o.__id__==object.__id__
end
return false
end
end

# to demonstrate...

Enumerable::MapProxy.
one_of_me?([1,2,3].all) #<= true

ActiveRecord::Associations::AssociationProxy.
one_of_me?(@order.customer) #<= true

Labels: , , ,


posted by Brendan Baldwin  # 3:32 PM   2 Comments   Links to this post

Archives

May 2007   June 2007  

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]