As most Ruby on Rails fans might be aware, Rails 6 was released this April at RailsConf 2019 and brought a number of eagerly awaited features and changes. Here is a quick recap of the key Rails 6 features you are likely to be using moving forward
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.
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.
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.
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
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.
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!
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.
Another interesting React 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 <a href="https://example.com" target="_blank">https://example.com</a>
, you would configure SendGrid with the following URL:
https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/sendgrid/i
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.
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.
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.
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.
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.
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.
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.
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.
#ruby-on-rails #ruby #web-development