Skip to content

Instantly share code, notes, and snippets.

@ToniRib
Created July 24, 2019 23:54
Show Gist options
  • Save ToniRib/042cc7aaa30fb9ee418d7f98f1146c10 to your computer and use it in GitHub Desktop.
Save ToniRib/042cc7aaa30fb9ee418d7f98f1146c10 to your computer and use it in GitHub Desktop.
Rails 5 Upgrade

General Process

This is going to take awhile, so as you keep this branch open, make sure you merge in master every day!!!! There will be merge conflicts, and people will keep bringing in new things that cause deprecation warnings, but you just have to fix them.

Official Rails documentation: https://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html#upgrading-from-rails-4-2-to-rails-5-0

Official Rails 5.0 Changelog: https://guides.rubyonrails.org/5_0_release_notes.html

High Level Steps

I'm documenting this after the fact, so don't be surprised if I miss one or more things you need to do as part of this upgrade.

  1. Start a new Rails 5 branch
  2. Update your Gemfile to point to the newer version of rails that you want
  3. Run rake rails:update
  4. Go through all the files (there will be merge conflicts on...basically every one) and keep the parts of each that you want
  5. Try to run bundle install and fix any dependency issues you encounter by upgrading/downgrading gems until you get the magic combination of gems.
    • You will also have to upgrade your CircleCI config to stop pulling from the same cache of gems. You should be able to change the restore and save cache steps.
  6. Try starting the server. If it will start up, you've done a good job of step 4. If not, you're going to use the error messages to fix whatever it complains about.
  7. These two steps can be done together:
    • Fix all the deprecation warnings. A good way to find these is to look at what is coming out of the server logs & also test failures on CircleCI.
    • Fix all test failures. Most of these will be due to breaking changes from Rails 4 to Rails 5.
  8. Start toggling the new framework defaults in config/initializers/new_framework_defaults.rb to take the new defaults for Rails 5 (if you want them) and ensure things don't break. Run the tests and fix anything that does break.
  9. Update all migration files to use the new syntax which appends the version number after ActiveRecord::Migration
  10. Update all models to inherit from ApplicationRecord
  11. After you've smashed all failures & warnings, put it up on an actual environment and check it out. Probably get a lot of people to check it out. In fact, do a bug hug. Check out every single page and endpoint in the app for any un-tested surprises.

Main Considerations

Gemfile Dependencies

This one gets complicated fast. Various gems will not even have a new version that doesn't throw deprecation warnings and you might need to replace them. Other gems will require different versions of the same gem as a dependency, sometimes conflicting with each other, so you'll have to selectively look at the change logs for those gems and pick versions of both that are compatible. Keep going with this until you can run bundle install successfully.

If you have to upgrade major versions of a gem, check the change log because there might be breaking changes or different configuration files you need to set up. There may also be differences in what methods actually return, so be sure you are aware of these.

I also ran into an issue where a gem had not been updated in 6 years, so it was going to continue to throw deprecation warnings unless we replaced you. You may have to go digging for replacement gems if you run into this.

I ran into specific issues with the version of the redis gem as it pertains to sidekiq after the upgrade.

This is also a great time to audit your Gemfile and get rid of any gems the app does not need.

Callbacks

Returning false in a before callback no longer halts the entire callback chain. See the documentation here. You need to actually throw an error to get it to halt the chain if that is still the behavior that you want.

ActionController::Params no longer inherits from HashWithIndifferentAccess

Rails documentation: https://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html#actioncontroller-parameters-no-longer-inherits-from-hashwithindifferentaccess

This is a big one. You can no longer call methods that were part of the old inheritance chain, such as .deep_stringify_keys! which the main app was using all over the place. You also need to do something like call .permit!.to_h if you want to have them treated like a hash again. Another thing you can do is using something like transform_keys.

Less documented is a new behavior where nested empty arrays or hashes will null out the entire top level hash key in params. What does this mean?

Say you had params = { selections: { selected: [1, 2, 3], deselected: [] }, company_id: 1 } coming into your controller in Rails 4. In Rails 5, because of that empty nested array, your params will come through as { company_id: 1 }. Which totally sucks, because if you won't even have access to the selections key AT ALL.

This was actually what broke our integration with the Image Rec API, because they were passing nested things that were empty and the top level key was being removed.

Constant Autoloading

This one threw me for a loop for awhile (and we still haven't figured out why this is a problem when running specs only in RubyMine on the main app). The way that constants are autoloaded/looked up changed between Rails 4 and Rails 5. There's more info on how it is actually done in the Rails documentation but I found I had a lot of issues with nested classes under modules when we started the upgrade. Some of the classes I ended out unnesting entirely. Some of them actually uncovered issues where, because we were using modules we included in other classes, we were trying to define the same constants twice. Some of this will actually cause your operation (request, etc.) to fail. The settings for this can be tweaked on an environment by environment basis in the appropriate file.

There's some other decent info here. You may also have to start specifying inheritance scopes more specifically than you were before. Remember, if you prefix any class with :: it will default to looking for that in the root scope, instead of your current module scope. If you have multiple classes nested under the same scope, or multiple classes of the same name under different scopes, starting to be explicit about what you are referencing by giving the code the absolute path (instead of assuming it is going to look it up correctly with the relative path) can be very handy in tests.

serialize in Models

Not sure exactly what changed with this implementation, but we had a few models that do something like serialize :value. The underlying implementation changed and this led to different data than expected.

ActiveRecord::Base.connection.execute type coercion

Previously, this always returned all values as strings. In Rails 5, it starts trying to coerce them into the appropriate data type. This is helpful...unless your code relies on these values being strings.

FactoryBot

You might need to use the the 'old style' parent strategy in our app, depending on how you expected dependent objects to be built.

Precompiled Assets

This is also done slightly differently in Rails 5, especially depending on what you have config.assets.compile set to in your various environment files. You should now have an assets.rb file which you may need to add specific assets to in order to be able to use them.

Deprecations

When you go from Rails 4.2.x to Rails 5.0.x, you're going to start seeing A LOT of deprecation warnings when you start up the server/run tests. Here are some of the big buckets, but it is by no means an exhaustive list of what you might encounter.

Controller Specs

The format of these switched from something like:

get :show, id: 1 to get :show, params: { id: 1 }

The params argument is now a named argument, not positional. The deprecation warning for this is multiple lines, so your spec output will be mostly unreadable when running these until you fix them.

ActiveRecord .uniq

.uniq is deprecated in favor of .distinct

ActiveRecord .delete_all

.delete_all no longer takes arguments. You can start using .where(*args).delete_all

Model Errors

You can no longer access model errors like user.errors.get(:first_name). The new syntax uses user.errors[:first_name].

If you are trying to set the errors, you should use user.errors.set(:first_name) = 'cannot be null' instead of user.errors[:first_name] = 'cannot be null'.

request.env

Any calls to env in your controller must be changed to request.env.

String constants in ActiveRecord queries

You can't use class names as constants in ActiveRecord queries. You have to switch them to actual strings.

alias_method_chain

alias_method_chain is deprecated. It hopefully isn't used that often, but you can look for blogs on how to fix this.

render :text

You should now use render plain: '' instead of render text: ''

redirect_to :back

This doesn't specify a fallback in case Rails doesn't know what 'back' is, so the new syntax is redirect_back(fallback_location: fallback_location) where fallback_location represents the location to use if the request has no HTTP referer information.

Arel

If you use things like Arel::InsertManager, the initialize method changed to no longer take any arguments.

Composite Primary Keys

Composite primary keys, if you're using them in any model, will start to throw deprecation warnings. If you don't have it already, bring in the composite_primary_keys gem and implement it in your models.

Uglifier

If you're using uglifier to do your JavaScript compression & you're also using the jemmaloc buildpack on Heroku, you will need to update your production.rb file.

head status:

If you were using something like head status: :ok the new format is just head :ok.

before_filter

All calls to controller helpers like before_filter need to be changed to before_action.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment