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>