Here's a quick case study where we got bit by ActiveRecord's use of a Transparent Proxy, namely ActiveRecord::Associations::BelongsToAssociation.
If you don't plan on reading this article, at least walk away with this:
"Don't EVER use the word 'target' for any attribute, association, or method on any of your ActiveRecord models!!!"
Why? Here's a quick example.
Consider this migration:
class CreateTables < ActiveRecord::Migration
def self.up
create_table :missiles do |t|
t.column :name, :string
t.column :silo_id, :integer
end
create_table :silos do |t|
t.column :name, :string
t.column :target, :string
end
end
def self.down
drop_table :missiles
drop_table :silos
end
end
Now consider these two models:
class Missile < ActiveRecord::Base
belongs_to :silo
end
class Silo < ActiveRecord::Base
has_many :missiles
end
We have a Missile model which belongs_to a Silo, and a Silo can point to a 'target'. Lets create our Missile and Silo objects and see what happens...
>> the_tube = Silo.create(:name => 'The Tube', :target => 'Washington, DC')
=> #<Silo:0x2855d8c>
>> boomer = Missile.create(:name => 'Boomer', :silo => the_tube)
=> #<Missile:0x27904c4>
>> silo.target
=> "Washingon, DC"
# okay, lets fire our missile at the target
# boomer launches and calls home to get its
# targeting information...
>> boomer.silo.target
=> #<Silo:0x2855d8c>
# wtf? its pointed at a silo?
>> boomer.silo.target == 'Washington, DC'
=> false
>> boomer.silo.target == the_tube
=> true
# RUN!!!!
Did you catch that? Our Missile, 'Boomer', says that its silo, 'The Tube', is NOT targeting at 'Washington, DC' like we asked it to! It's targeting itself!!!
WHY!?!
Well the reason for this has to do with the *magic* behind ActiveRecord's
BelongsToAssociation class, which defines a 'Transparent Proxy' object. I posted a little about these before:
Transparent Proxies, Part 1.
What's going on is that when we accessed the
silo association on our instance variable
boomer, what we *actually* get back is an instance of the
ActiveRecord::Associations::BelongsToProxy class. It's such a neat proxy, you probably never realized that you were dealing with it, because when you have one, you can't tell by looking at it that you don't just have an instance of the
Silo class we defined.
So where does 'target' come in to play? Well, it turns out that the BelongsToProxy isn't completely *Transparent*, it does have some methods defined, one of which happens to be
target. And in the case of Silos and Missiles, this is unfortunately a reference to the object referenced in the
belongs_to association!
Fortunately for Washington, DC, this attack has been averted. Unfortunately for this web developer, its time to redefine this model's schema. But with just a little refactoring, the next Missile will find its mark!
Labels: ActiveRecord, AssociationProxy, Associations, belongs_to, BelongsToAssociation, target, transparent proxies
Tonight at the
Chicago Area Ruby Meetup (CHIRB), we had some lightning-round presentations, and without too much preparation I decided to release and demo some code I had written for dealing with Regular Expressions.
The library in essence allows you to use the totally under-utilized embedded-comment faculty in Ruby's Regular Expression Extensions to label your captures.
For those who don't know, a comment in a Ruby Regexp looks like
(?#this), so if you want to label a capture, you can embed comments in the parentheses for that capture, like this:
/((?#number)\d+)-((?#word)\w+)/.
Of course that doesn't buy you anything on its own, so I wrote a library called 'named_captures.rb' available in my public svn repository
here.
With this library, you can now use that Regexp above like so:
my_regexp = /((?#number)\d+)-((?#word)\w+)/
my_match = my_regexp.match("i got 128-bit encryption!")
my_match[:number] # <= '128'
my_match[:word] # <= 'bit'
Feel free to explore and send me email if you use this at
brendan@usergenic.com. Comments on this post welcome as well.
Labels: named_captures.rb, regexp, regular expressions