Jake Scruggs

writes ruby/wears crazy shirts

(Or the downside of class caching)

This is a gotcha in Rails where your custom plugin or other dynamic change of a class only seems to work once.

First, a little background:
Recently we had a problem where we wanted an object to behave differently in two different instances of Rails. Ya see, we have models that are shared by two separately running rails instances. One is a cms (content management system) where all objects (let's say they are of class Thing) should show up. The other is the live site that should only show 'things' that have been designated 'live' (as opposed to, say, 'draft'). Now changing the search to filter out non-live 'things' was easy, but since you can call thing_instance.related_things and get back related objects of type Thing, now we have a problem. In the live site I want a call to 'related_things' to only bring back 'live' objects. But in the cms I want the same call to 'related_things' to bring back all the related objects no matter what the status.

Now I could create a new method called 'live_related_things' which wraps 'related_things' and filters but there's 3 problems with this:

  1. 'related_things' is used EVERYWHERE in the project – I'd have to change, like, a million files.
  2. Having 'related_things' and 'live_related_things' is really dumb. It's a bug waiting to happen.
  3. Hey this is ruby -- I can change the behavior of an class dynamically. Wooo!

So I created a plugin that uses alias_method_chain to change the behavior of the live site. And I felt all cool 'cause I was using THE POWER OF DYNAMIC LANGUAGES to solve a problem. And when I tried it in the app, it worked great. Once. On the second request it didn't work at all.

One of the cool things about Rails is that you can change your models and you don't have to restart your server -- really speeds up development. But how do they accomplish such magic? By tearing apart and rebuilding your models on every request. And the plugins only get loaded once. So when I started up my server the plugin loaded and everything was fine and on the second request the model was rebuilt from scratch (and the plugin was not reloaded) and it didn't work at all. Now this wouldn't happen if I was running in production mode (because its crazy inefficient to keep reloading classes on every request). So my solution was to add this line to my application.rb:

extend ThingExtension if Dependencies.mechanism == :load
'Dependencies.mechanism == :load' is true if rails is loading classes for every request and ThingExtension is a module that looks like this:

module ThingExtension
unless Thing.include?(::ThingLiveFilter)
load File.dirname(__FILE__) + '/../../local/plugins/live_filter/init.rb'
end
end

I have to call 'load' instead of 'require' because require only loads the file once and it's already been required when the project started up. 'Load' will open that file every time, but I need to check if Thing already has the ThingLiveFilter module included in it or I'll get one of those great 'stack level too deep' errors. Why? Well the when the project starts up, the plugin is called and then if 'load' calls it again alias_method_chain will get stuck in an infinite loop trying to redefine a method that's already been chained.

So there ya go, something to keep any eye out for when you're developing in Rails.

Update: if you're gonna do something like I described above AND cache your objects then you might want to read my bit on Caching Dynamically Modified Objects and the Trouble it Causes

(This is the third of three posts on Rails fixtures)

Awhile back Josh Cronemeyer approached me with an idea to migrate fixtures in Rails. The problem we were trying to solve is when your database changes you need to edit all the fixture files to reflect the change – which usually doesn't happen and then things break/get weird. We spent a few hours hashing out the basic plan (load all the fixtures into the db, migrate, then write the db out to the fixture files) and writing some code. Then he went off, tested it, and made it work with (very) occasional input from me. He's written up a nice post on the topic called How to Migrate Fixtures in Rails and has posted the code at Google Code.

Go check it out if you hate migrating fixtures by hand.

(This is the second of three posts on Rails fixtures)

I like using fixtures for setting up a development environment. Applications need data in the db to function so what most teams do is pass around a database, or two, or five. A new person joins the project and he/she gets a dump from one of the devs, imports it and goes to work. What happens when you screw up your db by:

  • Running a bad migration
  • Importing a crap load of data for a task (I remember once filling up a db with all movies playing in North America) but later you don't need it.
  • Doing something dumb. (my personal favorite)

Well, you need to get somebody else's db. Which maybe in a bad state and it definitely doesn't have the same data you're used to because they've been changing it a lot.

But if you have a bunch of fixtures checked in to source control, you can always go back to a base state. Anytime something is acting wacky and you think it might be a data problem (which happens more and more as the app gets older) wiping the db is simple and easy. Also, everyone has the same data so you can collaborate easier.


Next: How to migrate fixtures.

(This is the first of three posts on Rails fixtures)

So you have a Person object that has a many to many relationship with Roles and Movies (maybe this is site where people track their favorite movies). So to test it you need to have the following files:

  • people.yml
  • roles.yml
  • movies.yml
  • and the join tables:
  • people_roles.yml
  • movies_people.yml
If you want to test that only people of a certain role can edit another person's favorite movie list, then your test is spread out amongst 7 files (don't forget the test file and the actual class file). Maddening enough when you're writing it, but super crazy insane to debug when the test breaks. More than likely a developer will delete/comment out the the test when it breaks, or they will change it so passes but breaks functionality.

Long tests that repeat themselves look bad, from a DRY (don't repeat yourself) point of view but consider this: A fixture that is useful for 10 tests tells you nothing about any one test. Many times I've faced a broken test that relies on a bloated fixture and been pretty well stumped. There's all sorts of crap on this thing that have nothing to do with the test I'm trying to fix, so now I need to understand 10 tests to fix one. That takes time and makes people hate testing.

See my next post for why fixtures are still useful.

See also Jay Fields's Testing: Inline Setup and Josh Cronemeyer's Are Rails Test Fixtures Good or Evil?