Upgrading from Rails 3.2 to Rails 4.0 is not a trivial task. Sure, there is a guide, but when you upgrade you’ll probably be upgrading a lot of your gems, maybe your jQuery and jQuery-UI versions and then there are all the undocumented unintentional changes that can cause you grief. If you’ve got a good test suite (you have one right?) you’ll catch a lot of these, but some will leave you scratching your head. This one tripped me up for a while: we had a very simple scope stop working in Rails 4.
The scope model below works in Rails 3.2, but fails in Rails 4 when used with an association:
Here is the failure:
In the second query, post.comments.model.count the query has lost the comments scope as well as the conditional defined in our model scope and it counts all comments in the system.
The fix is quite easy, just change the name of the scope to something like not_index.
This seems to be change in associations that is most clearly illustrated by this:
I could never quite figure out where in activerecord this changed, but model was not a great choice for a scope name. This executable gist has everything in this post if you want to explore this more.
What surprises have you found with Rails 4?
I was a little surprised to discover that when you update an association through nested attributes, it won’t touch the parent record. It makes sense when you consider that Rails is optimizing by not writing records that have not changed, but if were using updated_at on the model for caching you may be surprised.
With these models:
This code will not change updated at on the post record:
As shown in the log:
The fix is very simple. Just touch the post record from the comment if you need updated_at to change on posts.
And now our post is updated:
Of course there are other ways to do this…
The code for this is on github.
Today, I ran across another form of this issue with Authlogic. In short, every request in a Rails application using Authlogic with a User model that includes a last_request_at column will cause the updated_at column to change. This can break caching that is based on the last time a user was updated.
last_request_at is one of Authlogic’s magic columns and is useful if you want to track users that might be logged in. However, by including this column, every controller request will touch the user record and change updated_at.
If you add last_request_at to your User model, you’ll start seeing this in your application logs:
UPDATE “users” SET “last_request_at” = ‘2014-03-12 20:59:48.314863’, “updated_at” = ‘2014-03-12 20:59:48.320481’
However, if you’ve added e-tags to a controller based on the updated_at timestamp, you’ll now have broken your cache control. For instance, this will no longer work:
To fix caching based on the User’s updated_at timestamp with Authlogic, you can add your own timestamp and maintain it whenever the User record changes. Here is what I came up with:
- Add a new column, user_updated_at, to the user model
- Whenever a relevant column changes on user, set user_updated_at to Time.now
- Change the cache control to be based on user_updated_at instead of updated_at
Here is what that looks like in the User model:
And in the controller:
This made my controller request caching work as expected. How would you handle this?