← recent

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:="id:">
>> patrick.posts.size
=> 0

>> new_post = patrick.posts.new(:title => "Another post")
=> #<post id:="id:" nil="nil">

>> patrick.posts.size
=> 0
</post></poster>

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:="id:">]
</post>

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:="id:" nil="nil">

>> patrick.posts.size
=> 1

>> patrick.posts
=> [#</post><post id:="id:" nil="nil">]
>> patrick.posts[0].new_record?
=> true
</post>

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

>> patrick.posts.size
=> 2

>> patrick.posts.last
=> #<post id:="id:">

>> patrick.posts.last.destroy
=> #</post><post id:="id:">

>> patrick.posts.size
=> 2
>> patrick.posts.reload; patrick.posts.size
=> 1
</post>