Back-end
9 minute read

Rails 6 Features: What's New and Why It Matters

Working as a consultant, Avant helped build applications for clients such as Chipotle, Sebamed USA, HealthEngine, DealDey, and many more.

As most Ruby on Rails fans might be aware, Rails 6 is coming soon, and bringing a number of eagerly awaited features and changes. The aim of this article is to familiarize you with the key features that were added to Rails 6 and outline how they can help make your applications better, thus saving valuable development time.

For starters, remember that Rails 6 requires Ruby 2.5+ and upgraded databases. So, make sure you have a plan to upgrade your systems accordingly, in case you have not done so already.

So what are these new features? Here is a quick recap of the key Rails 6 features you are likely to be using moving forward:

Testing in Rails 6

As professional Ruby on Rails developers, we aim to ensure maximum coverage for our code. However, testing becomes a tedious activity when our test cases become “heavy” and we have to wait several minutes, or even hours, just to get the test cases executed.

Parallel Testing

Well, Rails 6 has an answer here. It has added a parallelize method to the ActiveSupport::TestCase which allows you to parallelize the test suite with forked processes.

So, what you need to do to parallelize the processes for your tests is add this to your test_helper.rb:

parallelize(workers: 2)

Alternatively, we can replace our previously used commands for running tests. For example, bin/rails test OR bin/rspec spec can now be replaced by PARALLEL_WORKERS=15 rails test OR PARALLEL_WORKERS=15 rspec spec.

Accordingly, you can change the commands for running the test suites on different CI platforms like Travis, Gitlab, CircleCI, and others.

There are also hooks when each process is created/destroyed, which can be used as follows:

class ActiveSupport::TestCase
  parallelize_setup do |worker|
    # setup databases
  end
 
  parallelize_teardown do |worker|
    # cleanup databases
  end
 
  parallelize(workers: :number_of_processors)
end

Note: If you’d like to learn more, you can check out Rails Guides for additional details.

Action Cable Testing

Since we were talking about efficient testing, let’s also understand how Action Cable, one of the most salient features of Rails 5, has improved. Now it is possible to test Action Cable at any level: connections, channels, and broadcasts.

Connection tests aim to check whether a connection’s identifiers get assigned properly or that any improper connection requests are rejected:

class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
  test "connects with params" do

    connect params: { user_id: 42 }
    OR
    cookies.signed[:user_id] = "42"
    connect

    assert_equal connection.user_id, "42"
  end
 
  test "rejects connection without params" do
    assert_reject_connection { connect }
  end
end

Channel tests can be written to check whether users can subscribe to channels and the channel has a stream:

class ChatChannelTest < ActionCable::Channel::TestCase
  test "subscribes and stream for room" do
    # Simulate a subscription creation by calling `subscribe`
    subscribe room: "15"
 
    # You can access the Channel object via `subscription` in tests
    assert subscription.confirmed?
    assert_has_stream "chat_15"
  end
end

Broadcasting to channels can be tested like this:

# app/jobs/chat_relay_job.rb
class ChatRelayJob < ApplicationJob
  def perform_later(room, message)
    ChatChannel.broadcast_to room, text: message
  end
end
 
# test/jobs/chat_relay_job_test.rb
require 'test_helper'
 
class ChatRelayJobTest < ActiveJob::TestCase
  include ActionCable::TestHelper
 
  test "broadcast message to room" do
    room = rooms(:all)
 
    assert_broadcast_on(ChatChannel.broadcasting_for(room), text: "Hi!") do
      ChatRelayJob.perform_now(room, "Hi!")
    end
  end
end

Note: More tips on how to test can be found here.

Bulk Insert and Upsert

At some point, we all need to insert multiple records in one go and have found many workarounds when doing so. Well, Rails 6 comes with a new method out of the box—insert_all, similar to update_all.

It won’t fire any callbacks and will execute a single SQL query. There is an additional method upsert_all which allows you to use the upsert operation which is exposed by many modern databases like Postgres. So now you can reduce your insert queries and make your code more optimized. Also, say goodbye to previously used gems like activerecord-import.

A single INSERT SQL query is prepared by these methods, and a single SQL statement is sent to the database, without instantiating the model, or invoking Active Record callbacks and validations. It is also possible to define criteria when a primary key—unique indexes or unique constraints are violated with an option to either skip or run upsert queries.

Some examples are below:

result = Article.insert_all(
  [
    { id: 1,
      title: 'Handling 1M Requests Per Second',
      author: 'John',
      slug: '1m-req-per-second' },
    #...snip...
  ],
  returning: %w[ id title ],
  unique_by: :index_articles_on_title_and_author
)


result = Article.upsert_all(
  [
    { id: 1, title: 'Handling 1M Requests Per Second', author: 'John', slug: '1m-req-per-second' },
    { id: 1, .... }, # duplicate 'id' here
    { id: 2, .... },
    { id: 3, .... }, # duplicate 'title' and 'author' here
    { id: 4, .... },
    { id: 5, .... }, # duplicate 'slug' here
    { id: 6, .... }
  ]
)

The methods insert, insert! and upsert are wrappers around insert_all, insert_all! and upsert_all, respectively.

Note: There is a very good article which discusses bulk queries with respect to different database. If you need additional information, make sure you check it out.

Switching Between Multiple Databases

One of the main features many big applications will appreciate is this one: Rails 6 has finally added support for multiple databases for your application, built in and ready to go, out of the box!

Diagram of switching between databases

Of course, the design choice is still yours, whether you want to break your application into multiple microservices with each having a separate database, or take a monolithic route, or add several read replicas for your application.

However, having the ability to do it in such an easy manner has the potential to save a lot of time on the development front.

So, this is how your new database.yml file will look:

development:
  primary:
    database: my_primary_db
    user: root
  primary_replica:
    database: my_primary_db
    user: ro_user
    replica: true
  animals:
    database: my_animals_db
    user: root
  animals_replica
    database: my_animals_db
    user: ro_user
    replica: true

Here are interesting ways of specifying how to switch to different databases:

class AnimalsModel < ApplicationRecord
  self.abstract_class = true

  connects_to database: { writing: :animals_primary, reading: :animals_replica }
end

class Dog < AnimalsModel
  # connected to both the animals_primary db for writing and the animals_replica for reading
end

Here is the official GitHub page, which is nicely documented as well. Personally, I am looking forward to having database sharding capabilities in future Rails updates as well (something like this).

Action Mailbox

Another interesting Rails 6 feature is the addition of Action Mailbox, which adds the capability to route incoming emails to the controller like mailboxes for processing in Rails.

Action Mailbox features ingresses for Mailgun, Mandrill, Postmark, and SendGrid. You can also handle inbound emails directly via built-in Exim, Postfix, and Qmail ingresses. Now, you can probably imagine the potential benefits without going into more detail. It may be directly processing mails from a help desk to automating support tickets—Rails 6 allows customers to reply directly through email, and much, much more. The floor is open for you to explore this feature and come up with an approach that is ideal for your application.

Here is a small example to understand how to use Action Mailbox:

COMMENTS_REGEX = /^comment\+(.+)@example\.com/i

# app/mailboxes/application_mailbox.rb
class ApplicationMailbox < ActionMailbox::Base
  routing COMMENTS_REGEX => :comments
end

# app/mailboxes/comments_mailbox.rb
class CommentsMailbox < ApplicationMailbox
  def process
    user = User.find_by(email: mail.from)
    post_uuid = COMMENTS_REGEX.match(mail.to)[1]
    
    post = Post.find_by(uuid: post_uuid)
    post.comments.create(user: user, content: mail.body)
  end
end

Also, the new way of configuring emails is as follows (taking the example of Sendgrid):

# config/environments/production.rb
config.action_mailbox.ingress = :sendgrid

Use rails credentials:edit to add the password to your application’s encrypted credentials under action_mailbox.ingress_password, where Action Mailbox will automatically find it:

action_mailbox:
  ingress_password: …

Configure the SendGrid Inbound Parse to forward inbound emails to /rails/action_mailbox/sendgrid/inbound_emails with the username actionmailbox and the password you previously generated. If your application lives at https://example.com, you would configure SendGrid with the following URL:

https://actionmailbox:[email protected]/rails/action_mailbox/sendgrid/i

In case you want to explore this further, Rails already has a guide on this here.

Zeitwerk

Zeitwerk is the new code loader for Ruby. Given a conventional file structure, Zeitwerk loads your project’s classes and modules on demand, meaning you don’t need to write require calls for your own files. To enable it in Rails 6, you can do the following:

config.autoloader = :zeitwerk

You can read more about Zeitwerk here.

Optimizer Hints

You are concerned that some of your queries are taking too long to execute? Well, now you have a way to define time-outs for your queries, too.

The following statement will raise an StatementTimeout exception if the query takes longer than normal to execute:

User.optimizer_hints("MAX_EXECUTION_TIME(5000)").all

It is supported by MySQL and you’ll have to explore if your database supports it.

Truncate Database

What about seeding data? The following statement will truncate all your database tables and you can then proceed to seeding your data:

rails db:truncate_all  

No more deleting your databases to seed. You will probably agree this is an elegant and quick solution.

Action Text

Perhaps another notable feature for many applications that play with WYSIWYG editors is the addition of support for Trix editor natively into Rails 6 applications. This will certainly be a good upgrade/addition for many projects.

Most WYSIWYG HTML editors are enormous in scope—each browser’s implementation has its own set of bugs and quirks, and JavaScript developers are left to resolve the inconsistencies. Trix sidesteps these inconsistencies by treating contenteditable as an I/O device: When input makes its way to the editor, Trix converts that input into an editing operation on its internal document model, then re-renders that document back into the editor. This gives Trix complete control over what happens after every keystroke.

Installation:

rails action_text:install

# app/models/message.rb
class Message < ApplicationRecord
  has_rich_text :content
end

You can explore Action Text in further detail in the official documentation, here.

Security

No serious upgrade is complete without a few security enhancements. Rails 6 doesn’t disappoint on the security front, either. The first notable security upgrade is the addition of support for Host Authorization.

Host Authorization is a new middleware that guards against DNS rebinding attacks by explicitly permitting the hosts a request can be sent to. What this means is that you can define the hosts that can access your applications.

Another security upgrade is meant to thwart attacks that attempt to copy the signed/encrypted value of a cookie and use it as the value of another cookie. It does so by stashing the cookie name in the purpose field which is then signed/encrypted along with the cookie value. Then, on the server-side read, we verify the cookie names and discard any attacked cookies. Enable action_dispatch.use_cookies_with_metadata to use this feature, which writes cookies with the new purpose and expiry metadata embedded.

Webpack as the Default Bundler

As is the de facto standard with many modern JavaScript frameworks for front-end development, Rails 6 has added Webpack as the default JavaScript bundler through webpacker gem, replacing the Rails Asset pipeline. This is a relatively straightforward addition, and we won’t go into much detail. Suffice to say that Webpack will bring some relief to overworked front-end developers.

Preventing Race Conditions

Rails 6 has a new method which is used to prevent SELECT/INSERT race conditions in our code (I am sure many readers have had the misfortune of encountering race conditions as they scale their project). Here is the GitHub thread in case you need additional info.

The underlying table must have the relevant columns defined with unique constraints. While we avoid the race condition between SELECT → INSERT from #find_or_create_by, we actually have another race condition between INSERT → SELECT, which can be triggered if a DELETE between those two statements is run by another client. But, for most applications, that’s a condition we’re significantly less likely to hit.

Credentials in Rails 6

Since the days of Rails 5.2, credentials have been named a new “Rails way” to deal with sensitive information with a promise to get rid of infamous .env files once and for all. With credentials, encrypted keys for third-party services can be checked directly into the source control.

Until now, however, Rails used the same encrypted file for all environments, which made dealing with different keys in development and production a little tricky, especially when dealing with big projects and legacy code.

In Rails 6, this is finally solved with support for per-environment credentials. Again, further details can be explored on the official GitHub thread.

Is Rails 6 a Good Update?

Yes, and in fact Rails 6 could be described as a major update, though few would call it a game-changer. Since Ruby on Rails has been around for years, few people expect revolutionary changes, but its sixth incarnation brings a lot to the table.

Some features rolled out in Rails 6 seem like minor improvements, while others have the potential to save a lot of development time, improve security, robustness, and so on. Bottom line: Rails is mature, a lot of developers remain enthusiastic about its future, and with the release of Rails 6, it just got even better.

Of course, this list of Rails 6 features is incomplete and to view the complete set of changes, you need to check out the changelog. In addition, there are a lot of deprecations which you should consider. Finally, if you insist on going over every single change and update yourself, please read the full release notes.

Understanding the basics

Is Ruby on Rails still relevant in 2019?

Rails is more relevant than any other solution in the market. Rails and Ruby both are upgrading (Matz is making Ruby 3X faster). Rails 6 brings easy techniques to scale your application natively. And it is still a trusted choice by many big players like Github, Shopify, Basecamp, and more.

Should I use Rails?

The answer depends on your use case. It is still the best framework out there for regular web, e-commerce, content management, and many other types of applications. It also reduces the MVP development time and is a great choice when you don't have a fixed idea or need a quick prototype for your application.

What does Upsert mean?

Upsert is a database operation which means that, based on the attributes provided for an operation, if a record already exists in the database, then we want to update that record or create a new record if the record does not exist. Upsert happens in a single transaction, thus preventing any race conditions in the application.

What are the advantages of parallel testing?

We have seen the projects where it would take hours just to run the test cases. By running a suite of test cases in different processes we can reduce the run time of our test cases. With Rails 6 parallel testing, we won't have to rely on external services to parallelize our test cases.