In order to get some pivots up to speed on Rails 4 in anticipation of our clients’ needs, we’ve been upgrading several internal applications. In doing so, we ran into several gotcha described below. We’ve omitted the common stuff — deprecated find_by matchers, etc. — and focused on the non-obvious things that took us some thinking.
ActiveRecord::Base#set_table_name
This method was actually deprecated in Rails 3.2 and removed in Rails 4 in favor of #table_name=
. This caused a lot of our migrations to inexplicably fail. Unlike other deprecations, which print obvious warnings, these deprecation notices ended up buried in our logs/<environment>.log file. They were also hard to find with our tests as the method is usually called in little-tested database migrations.
To find these (and other similarly hidden deprecation warnings) in the future, we recommend running your migrations and tests under Rails 3.2 and grepping your logs for the word “deprecation.” To fix this warning, we considered squashing our migrations or going back and patching up old migrations. We decided to patch up the old migrations as this code change was simple enough that it was highly unlikely to cause issues.
File cache times
Rails 4 becomes much more opinionated about the Rails Asset Pipeline, and turns on fingerprinting for assets by default. Rather than fight the default, we copied the default asset-related settings into our application. In verifying our caching configuration, we noticed that our browser receiving 304 Not Modified responses for cached assets. Because we had fingerprinted assets, the browser should not have to make the any subsequent requests at all — to invalidate the cache, Rails will just change the URL of the asset. To fix this, we added config.static_cache_control = 'public, max-age=31536000'
to our staging and production configurations.
Scoped Query Deprecation Warnings
One of the big changes in Rails 4 is deprecating find_by_attribute methods and scopes not defined by lambdas. We got great deprecation warnings in general, but found several cases of deprecated finders not causing deprecation warnings. It turned out that the following scope definition disabled further finder deprecation warnings:
scope :scope, where(...).order(...)
If your code uses this style of scope definition, or has one as the first scope definition, it’s possible that Rails won’t warn you about deprecated scope syntax. We’d recommend a quick grep for “scope :” and looking over them manually after fixing all your deprecation warnings.
Number_to helpers
An undocumented change in Rails 4 caused our tests some grief. The helpers in ActionView::Helpers::NumberHelper
, such as number_to_percentage
, now require the passed object to implement #to_str
in or they will raise an error (#to_s
is not sufficient). Different helpers have somewhat different behavior, as shown by the table below that shows the return values/errors raised by calling number helpers with the argument Object.new
.
Method | Rails 3 | Rails 4 |
---|---|---|
number_to_currency | Error: undefined method `to_f’ for #<Object:0x…> | Error: undefined method `to_f’ for #<Object:0x…> |
number_to_human_size | #<Object:0x…> | #<Object:0x…> |
number_to_phone | Error: undefined method `starts_with?’ for “#<Object:0x…>”:String | “#<Object:0x…>” |
number_with_precision | #<Object:0x…> | #<Object:0x…> |
number_to_human | #<Object:0x…> | #<Object:0x…> |
number_to_percentage | “#<Object:0x…>%” | Error: no implicit conversion of Object into String |
number_with_delimiter | #<Object:0x…> | #<Object:0x…> |
Passing non-numbers to these helpers was never kosher. One of our view specs passed a test double into these helpers in testing another part of the view but which now failed because number_to_percentage
raised an error. We suspect that a refactor extracting code from ActionView to ActiveSupport caused this change but did not submit a pull request to the documentation or code because we were previously relying on undocumented behavior. Instead, we made our test double return numbers for a more realistic test. Nonetheless, we found it interesting that the behavior of these methods changed.
CGI::escape and SafeBuffers
For one of our app’s integrations, we made a call like this:
bounded_message = truncate(message, :length => 500) + " " + url
escaped_message = CGI::escape bounded_message
The Rails truncate helper returns an ActionSupport::SafeBuffer
object, which choked a gsub call inside CGI::escape
. Confusingly, adding a simple call to #to_s
did not solve the issue. On further investigation, SafeBuffer#to_s
actually returns an instance of SafeBuffer
, not String
, presumably to prevent accidental trusting of unsafe strings. Calling #to_str
fixed this issue. We felt confident removing SafeBuffer’s protection because we were escaping the data ourselves and recommend pausing before blindly bypassing any sort of tainted string escaping. Later, we found a blog post by Philip Hallstrom that describes the issue in more depth.
Wrap-up
Overall, upgrading to Rails 4 was pretty painless, and the deprecation warnings serve as a great guide. Having tests made the process go much more smoothly, and ensured that we caught deprecation warnings in every possible code path. For a major version upgrade, Rails 4 had very few gotchas, and the ones we found were all minor. If you’re hesisitating to upgrade, give it a try.
Thanks to Dave Goddard for going through the upgrade with me and being a keen investigator. Also thanks to Andy Pliszka, Trevor John, Matthew Wismer, and Mark Gangl, who upgraded apps of their own and contributed their own suggestions and findings.