1

Upgrading to Rails 5

 3 years ago
source link: https://flexport.engineering/upgrading-to-rails-5-98c81b56517
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Image for post
Image for post

We recently completed our upgrade from Rails 4.2 to Rails 5. 🎉. Along the way we, encountered some breaking changes not covered in the upgrade guide and wanted to share our experience in case others run into the same issues.

ActiveRecord::Relation no longer blanket delegates to array

Prior to Rails 5, all array methods would work on Relation except for a hard-coded blacklist as the there was a generic delegation to the underlying array. The blacklist existed to block mutating methods such as compact! and flatten! that do not make sense for a Relation. Rails 5 changes the delegation to be a whitelist, causing methods such as combination and to_csv to fail with NoMethodError

More subtly, a separate pull request changes the array delegation from delegate *array_methods, to: :to_a to delegate *array_methods, to: :records. As a result, calling an array method on a Relation will call that method directly on the underlying records instead of making a copy first with to_a, causing bugs if concurrently modifying a collection.

# assume author has articles with ids 1, 2, 3, and 4 
author.articles.each do |article|
author.articles.destroy!(article) if [2,3].include?(article.id)
end# in Rails 4, .each creates a copy of the records so it behaves as desired, removing articles 2 and 3
author.articles.count == 2# in Rails 5, .each directly modifies the records, causing concurrent modification and skipping article 3
author.articles.count == 3

SerializableHash errors for non-existing methods

In Rails 4 and earlier, calling a non-existing method in, for example, as_json would silently give null for the requested key:

class Author < ActiveRecord::Base
def as_json(options = {})
options[:methods] = [:does_not_exist]
super(options)
end
endAuthor.new.as_json == {does_not_exist: nil}

This unexpected behavior was updated to raise a NoMethodError

No more implicit to_i when making queries with enums

A common mistake I have seen among Ruby on Rails developers is to write a SQL query with an enum string instead of the underlying integer:

class Author < ActiveRecord::Base
enum genre {
fiction: 0,
nonfiction: 1
}
endAuthor.where(genre: 'nonfiction') # Whoops, should have done Author.where(genre: 1)

This error can be very hard to track down in Rails 4, as the generated SQL query tries to convert the genre into an integer. In ruby, to_i returns 0 if it fails to parse the string (e.g. 'a'.to_i == 0) so the above query would be SELECT * FROM "authors" WHERE "authors"."genre" = 0, the exact opposite of the intended behavior! Rails 5, as a more grown-up framework, ops for prepared statements instead, generating SELECT * FROM "authors" WHERE "authors"."genre" = $1, [["genre", "nonfiction"]]. In most SQL adapters, this prepared query will noisily error about type mismatches.

Parameter deep munging removed

Because of potential security vulnerabilities in Rails 3 and earlier, ActionController munged all empty arrays to nil. For example, a post request with params {author_ids: []} would be converted to {author_ids: nil} when accessed in a controller. Rails 4 removed the security vulnerability ( Author.where(name: []) generates SELECT * FROM authors WHERE 1=0) but left the munging. Rails 5 removes the munging, requiring changing any potential nil? checks to present? checks instead.

protect_from_forgery changed to prepend: false

One of the first things to add to any new ApplicationController is protect_from_forgery which adds protects against CSRF by verifying the authenticity token sent with every request. Before Rails 5, protect_from_forgery inserted itself at the beginning of the action callback chain so it would run before any other before_action even if declared afterward. This prepend behavior was removed in Rails 5, making protect_from_forgery behave like any other before_action. While a minor change, this will cause apps using Devise to fail if protect_from_forgery comes after authenticate_user

Conclusion

Rails 5 was definitely a worthwhile upgrade, providing a plethora of new features and improving performance:

Image for post
Image for post
Database time before and after Rails 5 upgrade

On the other hand, there were a couple of pitfalls along the way. So Happy Upgrading and hopefully this post will save some headache for others.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK