Saturday, March 21, 2009

Rails Association Caching Pitfalls

This week at work I was disappointed to find that calling #new on an association collection doesn't add the new instance to the cached collection:

>> patrick = Poster.first
=> #<Poster id: 1, name: "Patrick S.", created_at: "2009-03-21 03:55:17", updated_at: "2009-03-21 03:55:17">
>> patrick.posts.size
=> 0

>> new_post = patrick.posts.new(:title => "Another post")
=> #<Post id: nil, title: "Another post", text: nil, poster_id: 1, created_at: nil, updated_at: nil>

>> patrick.posts.size
=> 0

If I like the post and decide to save it to the database, my cached posts still doesn't get updated:

>> new_post.save
=> true

>> patrick.posts.size
=> 0

In order to get the collection up-to-date I have to know that this is a trouble spot, and call #reload on the association:

>> patrick.posts.reload; patrick.posts.size
=> 1

If, instead, I try to push that new instance onto the collection explicitly, it gets saved automatically, which doesn't seem very intuitive (I *much* prefer to have to explicitly save changes to the database):

>> patrick.posts << Post.new(:title => "pushing onto collection")
Post Create (0.8ms) INSERT INTO "posts" ("created_at", "title", "poster_id", "updated_at", "text") VALUES('2009-03-21 06:14:03', 'pushing onto collection', 1, '2009-03-21 06:14:03', NULL)
=> [#<Post id: 12, title: "pushing onto collection", text: nil, poster_id: 1, created_at: "2009-03-21 06:14:03", updated_at: "2009-03-21 06:14:03">]

The right way to do this seems to be the undocumented #build, which does exactly what I was wanting in the first place (create an unsaved instance and append it to the collection):

>> patrick.posts.size
=> 0

>> patrick.posts.build(:title => 'Built')
=> #<Post id: nil, title: "Built", text: nil, poster_id: 1, created_at: nil, updated_at: nil>

>> patrick.posts.size
=> 1

>> patrick.posts
=> [#<Post id: nil, title: "Built", text: nil, poster_id: 1, created_at: nil, updated_at: nil>]
>> patrick.posts[0].new_record?
=> true


(Unfortunately, there seems to be a related problem with the association's #delete/#destroy)

>> patrick.posts.size
=> 2

>> patrick.posts.last
=> #<Post id: 13, title: "Another", text: nil, poster_id: 1, created_at: "2009-03-21 06:19:55", updated_at: "2009-03-21 06:19:55">

>> patrick.posts.last.destroy
=> #<Post id: 13, title: "Another", text: nil, poster_id: 1, created_at: "2009-03-21 06:19:55", updated_at: "2009-03-21 06:19:55">

>> patrick.posts.size
=> 2
>> patrick.posts.reload; patrick.posts.size
=> 1

Monday, March 16, 2009

Watching Your Logs From The Console

I've often wished there was a way to see the rails logs right inside the console window, instead of having to switch back and forth betweeh console and a "tail -f" process. It wasn't hard to find an answer -- Jamis Buck blogged about it back in 2007 -- but it didn't quite do all I was hoping for, so I ended up going a different route, which I think gives a more complete solution.

The solution that I saw on Jamis' blog was to replace wholesale the rails logger:

ActiveRecord::Base.logger = Logger.new(STDOUT)


The problems with this are:
1) It must be done before any AR::Base logging is done, and
2) It cannot be undone.
(Both due to the logger object being cached)

For my needs, I'm interested in the logs for a particular method call, but I don't really want to see all the logs, all the time. So, I wrote the code that follows (at the end of this post), and put it in my .irbrc file. It gets around the cached logger object by altering the logger, rather than trying to replace it. This allows me to turn it on or off mid-session. For example (from a personal side project):

>> wr = WeightRecord.first
=> #<WeightRecord id: 1, date: "2007-10-28", weight: 177.0, ... >
... # More commands, which I don't care to see the logs from ...
>> wr.weight = 176
=> 176

>> show_log
=> nil
>> wr.save
SQL (0.000154) BEGIN
SQL (0.069810) SELECT `date` FROM `weight_records` WHERE (`weight_records`.date = '2007-10-28' AND `weight_records`.user_id = 1 AND `weight_records`.id <> 1)
WeightRecord Update (0.000688) UPDATE `weight_records` SET `updated_at` = '2009-03-16 01:04:08', `weight` = 176.0 WHERE `id` = 1
SQL (0.004874) COMMIT
=> true

>> hide_log
=> nil
... # More stuff I don't need to see the logs from...

(The "SELECT `date`" bit in the logs is because my model has: "validates_uniqueness_of :date, :scope => :user_id")

The code:

def show_log
unless @log_buffer_size
@log_file = Rails.logger.instance_variable_get("@log")
@log_level = Rails.logger.level
@log_buffer_size = Rails.logger.auto_flushing
end
Rails.logger.flush
Rails.logger.instance_variable_set("@log", STDOUT)
Rails.logger.level = Logger::DEBUG
Rails.logger.auto_flushing = 1
nil
end

def hide_log
if @log_buffer_size
Rails.logger.instance_variable_set("@log", @log_file)
Rails.logger.level = @log_level
Rails.logger.auto_flushing = @log_buffer_size
end
nil
end

Wednesday, March 4, 2009

When Your Data Leaves Home Without You

Those that know me best know that I much prefer to keep my data on my own servers, as opposed to using services such as PicasaWeb, Blogspot, and github. Something about giving up possession of my original content doesn't sit quite right with me -- maybe it's my own experience with the "possession is nine-tenths of the law" adage, or maybe I'm just wary of free services. There's also the problem of building up a brand that is on someone else's domain -- whatever popularity my content gets should be associated with my domain, not the provider's. Whatever the reason, I am generally willing to put up with poorer performance (etc.) in order to host my services myself.

For my two new blogs I started out looking for a rails plugin, to add into plainlystated.com. I didn't find anything that looked mature, and I didn't feel like coding it myself (since these things always end up taking a lot longer than they initially seem when you add in captchas, admin features, layouts, etc). I got the latest version of Typo, but it was running at almost 60 megs of RAM, which is a lot for my little VPS. Plus, "satellite blogs" (two separate blogs running on one Typo) aren't even supported "yet", so it was gonna be a matter of hacking Typo (maybe with a third-party plugin), or running two instances, and both options seemed like a waste of semi-precious resources.

So, as you can see, I broke down and went with a free hosted solution. I looked briefly at wordpress, blogspot, and livejournal, but they all seem "good enough" so I went with ole' reliable: Google.

In this case, there seem to be some substantial benefits to going with Blogspot:
  • I don't use my own resources,
  • I don't have to worry about security, system maintenance, etc.,
  • I get automatic security updates and feature upgrades,
  • I get plenty of free templates,
  • It "just works".
Blogspot also provides the option to host the blog from a subdomain of your own (which is how I have techspeak.plainlystated.com instead of techspeak.blogspot.com), which is pretty cool, and mitigates my concerns about building up my own brand.

Hopefully this decision will end up making me more comfortable with taking advantage of all the great free providers out there (maybe I'll be linking to a PicasaWeb album before long).

Tuesday, March 3, 2009

New Blog

Just a quick post to introduce my new blog... This, along with my other new blog forthright.plainlystated.com (non-technical) are meant to be my new home in the ether.

This blog (techspeak.plainlystated.com) will generally be about my adventures in Ruby on Rails, as I continue growing my expertise in the field. (I have been working professionally with Rails for about 2 years, including the past 1.5 years at Medical Decision Logic working on medical research management software.)