Monday, August 23, 2010

Enumerate all Rails ActiveRecord::Base model classes

I couldn't quickly find an easy way to enumerate all ActiveRecord::Base-derived model classes in my Rails project (for some simple analysis scripts), so I whipped up this quick and dirty solution.

I extended ActiveRecord::Base just out of convenience, so I can call ActiveRecord::Base.each_model_class{|klass| ...} easily. You could apply the same technique in another class if you don't like extending Rails core classes.

The "rescue nil" is just a quick hack to catch the cases where you have tables that don't map to classes directly.

NOTE: This does NOT enumerate STI-based model classes that don't have their own tables.

Saturday, August 7, 2010

Rails 2.3 ActiveSupport::Cache::MemoryStore Freezes Objects

Using MemoryStore for caching in Rails 2.3.X has what I would consider a show-stopper bug. When you write an object to the cache using Rails.cache.write, the original object gets frozen. When you read that object from the cache, it is also frozen. So, if you do something like read/write-through a cache for all your ActiveRecord models, you'll never be able to modify any instances of your models in your code.

This same behavior is not present in other cache storage classes such as ActiveSupport::Cache::MemCacheStore. I don't think I'd ever use MemoryStore in production...but it is great for testing. When running all of my rspecs and cucumber features, I have those environments set to use MemoryStore. The problem is, many of my tests break when they try to modify objects that were frozen by the cache.

Here's a simple example demonstrating this. Look at the WTFs on lines 10 and 14 of the output below:

The Attempted Fix
It turns out this bug was filed (and partially fixed) a long time ago. Rails ticket #2655 was filed back in May 2009 about this exact issue. Yehuda Katz and Carl Lerche from EngineYard committed a fix for this (to duplicate the object into the cache instead of freezing it) on July 1, 2009.

This is great. It fixes line 10 in the example output above. However, it is not quite enough if you are putting ActiveRecord::Base objects into the cache because the object's @attributes hash needs to be dup'd too. It turns out, the "carlhuda" pair was well aware of this and committed a fix for that too, just moments before the other one.

Another problem still remains though. Their fix still involves freezing the duplicate object in the cache and directly returning that frozen object. We need one more change to the read method to duplicate the object on the way out too. I made such a fix and updated the test_store_objects_should_be_immutable test for MemoryStore to be consistent with that of MemCacheStore

The final problem is that carlhuda's commits never got merged into the Rails 2.3 branch. A quick look at the github network graph for the rails project on July 1, 2009 tells the story.

Click Image To Enlarge

That yellow branch looks like wycats and carllerche were pairing on a bunch of bug fixes around this time. Notably the two short red arrows I drew here show that they cherry-picked two of those and merged them into the 2-3-stable branch. Then, shortly after that, they make a few more fixes that never got merged into the 2-3-stable branch. The two I circled in red are the two fixes I mentioned above.

Completing The Fix
I don't know if the Rails team is planning any more updates to the 2.3 line, but if they do release a 2.3.9, I vote for including these two commits, plus the one I applied. So, I forked the repo and created a topic branch off of 2-3-stable that cherry-picks the two carlhuda commits and includes my additional commit.

A Workaround
Since I'd rather not maintain my own patched version of Rails, for now I am just monkey-patching MemoryStore and ActiveRecord::Base like so: