The WegoWise Development Blog

Rails Gotcha: Saving STI Records

| Comments

Let's say you have a persisted instance of a model, RedCar, that uses STI and is descended from Car. You have an action that changes the type of the record to BlueCar. You use #becomes to cast the record to the new type before the save because you want to use the validations and callbacks from the new model:

car = Car.find(1)
car.type # => 'RedCar'
car = car.becomes(BlueCar)
car.type # => 'BlueCar'
car.save!

But wait! If you retrieve car from the database again you'll see that it's still an instance of RedCar. In fact, you'll see that no changes are saved to the database:

car = Car.find(1)
car.brand # => 'GM'
car = car.becomes(BlueCar)
car.brand = 'Ford'
car.save!
car = Car.find(1)
car.type # => 'RedCar'
car.brand # => 'GM'

Doing an explicit update_column on type here doesn't help:

car = Car.find(1)
car = car.becomes(BlueCar)
car.update_column(:type, 'BlueCar')
car = Car.find(1)
car.type # => 'RedCar'

Also, note: no exceptions are raised. We'll save you any more head-scratching by pointing out the SQL that is generated:

UPDATE </span><span class="n">cars</span><span class="o"> SET </span><span class="k">type</span><span class="o"> = 'BlueCar', </span><span class="n">brand</span><span class="o"> = 'Ford'
WHERE </span><span class="n">cars</span><span class="o">.</span><span class="k">type</span><span class="o"> IN ('BlueCar') AND </span><span class="n">cars</span><span class="o">.</span><span class="n">id</span><span class="o"> = 1

The problem is that Rails STI scopes the statement to WHERE `cars`.`type` IN ('BlueCar') even for UPDATES and even when you're trying to change the type itself. Woops.

So what's the solution? As far as we can tell you have to do a separate update after you've passed validation. We ended up implementing this in a before_filter along these lines:

class Car < ActiveRecord::Base
before_update :really_change_type, :if => :type_changed?
def really_change_type
Car.where(id: id).update_all(type: type)
end
end

Posts That Made Us Go Hmm

| Comments

Whenever one of the WegoWise developers reads an interesting article, they will usually post the link in our work chatroom. Here are a couple recent links that got us talking, or at least arguing, along with our thoughts about them.

Code Climate published a great list of security gotchas and tips.

Joe: The one that jumped out for me was about using \A and \Z as regex anchors instead of ^ and $ in order to validate strings that could contain newlines. Whoops. I've been using the wrong anchors for years!

Everyone on the team relies on Vim almost exclusively, so this angry and humorously truthful screed against the beloved editor was a fun read.

Joe: The author makes good points (Vimscript is terrible, you have to spend a lot of time tinkering with plugins before it's a good IDE) but he starts off by dismissing any benefits from not using the mouse. I dunno, it seems to help me stay focused by avoiding context-switching (shrug).

This is a similarly enjoyable read if your sacred cow is jQuery instead of Vim: The DOM isn’t slow, you are..

GitHub described an optimization aimed at reducing the number of objects on the heap in their RoR app.

Joe: The second half of the article is an esoteric discussion of the merits of "Judy arrays" but the first half explains why MRI's GC implementation leads to performance slowdowns (rather than just memory overhead) when there are too many uncollected objects.

GitHub had another good article about an optimization that involved replacing a low-level Ruby escaping function with a C library, resulting in a considerable performance boost.

Joe: Ruby is great, but when there's a big win to be gained from dropping down to compiled code we should take it.