Updating Rails-API to Rails 5 in API mode

Since rails-api was merged into Rails as part of Rails 5, the rails-api project has largely been abandoned, and for good reason. People who have a rails-api app will want to update to Rails 5 in order to stay up-to-date. Although the process is not hard, it is slightly different from a traditional update from Rails 4 to Rails 5.

The first thing to do is to make a new branch for this update process.

git checkout -b rails5

Rails 5 requires Ruby 2.2.2, so if you’re using an older version of Ruby, you need to get that updated first. Once you’ve done that, come back here and we’ll move on.

I use RVM, and I decided to create a separate Gemset for the Rails 5 update just so I could easily switch back to my original Gemset if needed. This step is optional.

rvm gemset create rails5
rvm gemset use rails5
gem install bundler
bundle install

The next step is to update to the latest version of rails-api, which is 0.4.0, and the latest version of Rails 4.x, which is 4.2.7.1 at the time of this writing.

From the Guide to Upgrading Ruby on Rails:
When changing Rails versions, it’s best to move slowly, one minor version at a time, in order to make good use of the deprecation warnings. Rails version numbers are in the form Major.Minor.Patch. Major and Minor versions are allowed to make changes to the public API, so this may cause errors in your application. Patch versions only include bug fixes, and don’t change any public API.

The process should go as follows:

  1. Write tests and make sure they pass.
  2. Move to the latest patch version after your current version.
  3. Fix tests and deprecated features.
  4. Move to the latest patch version of the next minor version.

Repeat this process until you reach your target Rails version. Each time you move versions, you will need to change the Rails version number in the Gemfile (and possibly other gem versions) and run bundle update. Then run the Update task mentioned below to update configuration files, then run your tests.

Now lets change the version of Rails in our Gemfile to Rails 5.

gem 'rails', '5.0.0'

Then we’ll update the Rails gem by running this command:

bundle update rails

If this fails, it will give you a list of version number comparisons that bundler couldn’t resolve. Look for any dependencies that require Rails to be less than 5, and then update those gems one-by-one. Hopefully they have a newer version that will support Rails 5. For each gem, change your version of that gem, undo the change to the version for Rails so its back to 4.x, then bundle update for that gem. Run your test suite again (or test the part of your app that Gem was responsible for), and look for issues or deprecations. Repeat this process until you can get Rails to 5.0.0 and successfully run bundle update.

If a Gem does have a version that supports Rails 5, you’ll need to get rid of that Gem in your project and achieve the functionality in another way, or fork it and fix it so it supports Rails 5, or wait until that gem adds support.

Once you’re able to run bundle update rails and get yourself on Rails 5, the next step is to remove the rails-api gem. Delete that line from your Gemfile.

Now, let’s see what gems are currently installed.

gem list

Hmm, looks like we now have multiple versions of many of our gems. I like to keep things clean, so I’m going to delete the extra gems at this point, so we only have the latest versions that we’re using. Since I’m using RVM, I’ll run this. This step is optional.

rvm gemset empty
gem install bundler
bundle install

Alright! Now we have a nice clean set of gems.

The only problem is that we’re now running a full Rails 5 app, rather than a Rails 5 API app. There is a Rails Guide that discusses how to change a Rails 5 app into a Rails 5 API app. Based on that guide, we learn our next steps.

We’ll add this to config/application.rb at the top of the Application class:

# Tell Rails this is an API app since rails-api was merged into Rails
config.api_only = true

Then we’ll add this to config/environments/development.rb:

# When an error occurs in development mode, render debugging information as
# a regular API response, not an HTML page.
config.debug_exception_response_format = :api

Next thing to do is to create an application_record.rb file in app/models and fill it with this:

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end

Then, we'll need to update all of our models to inherit from ApplicationRecord instead of the previous ActiveRecord::Base.

# app/models/user.rb
class User < ApplicationRecord
  # model logic in here
end

If you're using ActiveJob, you'll want to create an application_job.rb file in app/jobs and fill it with this:

class ApplicationJob < ActiveJob::Base
end

Then, you'll need to update all of your jobs to inherit from ApplicationJob.

If you're using the assigns method or the assert_template method in your controller tests, you'll need to add the rails-controller-testing gem to your Gemfile, as these methods were extracted into their own gem by Rails 5.

gem 'rails-controller-testing', '~>1.0.1'

Then run bundle install.

At this point, you'll want to run your test suite, and fix any errors and failing tests.

Next, you need to update your db/schema.rb file again, because Rails 5 changed some things in this area. You don't need to have a pending migration. Just run:

rails db:migrate

Then you can run your app server and do a full regression test of every feature in your app just in case something slipped past your tests.

Once you've fixed the things that broke due to the Rails 5 update, this would probably be a good time to do a test deploy in a way that won't bring down your app for your users. A separate staging server might be a good choice. If your code deploys and seems to work there, great!

At this point, you'll want to focus on deprecation messages. There are four places you may see them - #1 when running your tests, #2 in your rails server log, #3 when running rails console, and #4 when doing a deployment.

One common deprecation error you will probably see is regarding your controller tests.

ActionController::TestCase HTTP request methods will accept only keyword
arguments in future Rails versions.

Examples:

  get :show, params: { id: 1 }, session: { user_id: 1 }
  process :update, method: :post, params: { id: 1 }

So if you had this in your tests:

get :show, id: 555

You'll want to update it to:

get :show, params: { id: 555 }

Another common deprecation message you may see is:

ActiveRecord::Base.raise_in_transactional_callbacks= is deprecated,
has no effect and will be removed without replacement.

You can fix this message by removing these lines from config/application.rb:

# Do not swallow errors in after_commit/after_rollback callbacks.
config.active_record.raise_in_transactional_callbacks = true

If you have callbacks that you need to be able to halt the transaction, here is a solution to keep them working as expected.

After you've fixed the deprecation issues, you should be done. Congrats! You've upgraded from Rails API to Rails 5!


Extra credit:

The above process should cover the majority of Rails API apps. If your app still has issues, the best place to start would be to review the official Guide to Updating from Rails 4.2 to Rails 5.0. This guide is geared toward a traditional Rails upgrade, not a Rails API to Rails 5 upgrade, but there are some additional details about things that have changed that may apply to your app.

Another cool thing you can do after you've updated to Rails 5 is to run this:

rails app:update

This command will compare your version of the rails configuration files with the Rails 5 versions of the rails configuration files. This is useful so that your app config doesn't fall behind the times, and so your files look similar to those that are generated with a shiny new Rails 5 app. This means better comments, more examples, and more config options to explore. This will allow you to more easily utilize the latest and greatest Rails features, but it is a little more work.

If you do this, you'll want to review each file that it creates. Some of your files will have conflicts, and when this happens, I generally press the d key. This will show you a diff between your version of the file and what a brand new version of the file would look like if it was generated. This allows you to see what lines would be added or changed. I generally will view the diff, and then copy the new lines into that file manually, and then press the n key to skip to the next file conflict. This allows me to manually add the new lines, rather than using the tool to overwrite the entire file and then having to add back in all my existing code manually.

One key thing that this may highlight is that your app may be using parts of the Rails framework that you don't need. If you're not already doing it, it will suggest that you explicitly require the parts of Rails that you want to use in your config/application.rb. For example:

require "rails"

# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
# require "action_view/railtie"
# require "action_cable/engine"
# require "sprockets/railtie"
require "rails/test_unit/railtie"

If you're not using any of these parts of the framework, now is a good time to audit them and comment out the ones you're not using. This can improve performance.

If you get any error messages like this after you comment them out:

Unable to load application: NoMethodError: undefined method `assets'
for #

This means that you have a line calling config.assets (or whatever the undefined method is) in one of your config files. Check your application, development, test, and production config files and delete that line, and it should resolve the error.

If you do use rails app:update, one potential gotcha is the config/initializers/new_framework_defaults. This file creates initializers that are meant to help ease the Rails 5 onboarding process by disabling some of the breaking changes with Rails 5, allowing people to ease into them one-by-one. But if you've already dealt with these issues, this file will revert that behavior. So be sure to review that file carefully.

That's all there is to it! Happy Rails-ing!

Back