Rails 6.1 new framework defaults: what they do and how to safely uncomment them

What does this do?

Consider the relationship between a post and its comments:

# /app/models/post.rb
class Post < ApplicationRecord
has_many :comments #, inverse_of :post is implied
end
# /app/models/comment.rb
class Comment < ApplicationRecord
belongs_to :post #, inverse_of :comments is implied
end
post = Post.new
comment = post.comments.build
# We can traverse from the dependent model back up to the
# independent. This works now without this flag.
post == comment.post # true
comment = Comment.new
post = comment.build_post
# post.comments is an empty
# ActiveRecord::Associations::CollectionProxy [] here
# We are unable to traverse from the independent model down to the
# dependent.
post.comments.first == comment # false

How to safely uncomment?

This flag can be safely uncommented.

# /app/models/employee.rb
class Employee < ApplicationRecord
# all employees have a boss, except for the CEO
belongs_to :employee, optional: true
end
cto = Employee.new
ceo = cto.build_employee
cto.employee == ceo # true, the ceo is the cto's boss
ceo.employee == nil # false, but the ceo shouldn't have a boss?
ceo.employee == cto # true, but why is the cto the ceo's boss?
cto.employee.employee.employee.employee == cto # true, wait what?
# /app/models/employee.rb
class Employee < ApplicationRecord
belongs_to :boss, class_name: Employee, foreign_key: 'employee_id'
end

What does this do?

Consider this user model that uses Active Storage to attach an avatar.

# /app/models/user.rb
class User < ApplicationRecord
has_one_attached :avatar
end
# /app/models/user.rb
class User < ApplicationRecord
has_one_attached :avatar do |attachable|
attachable.variant :thumbnail, resize: "100x100"
end
end
# /app/views/users/index.html.erb
<% @users.each do |user| %>
<%= image_tag @user.avatar.variant(:thumbnail) %>
<% end %>
  1. Active Storage waits until the first request for the variant.
  2. Active Storage calls the S3 API to check if the variant is already present in the bucket.
  3. If it is present, it serves it. Otherwise it downloads the original image, uses ImageMagick to generate the variant, uploads the variant to S3, then serves it.

How to safely uncomment?

This flag can be safely uncommented once you’ve run the database migrations that make variant tracking possible. These migrations are generated by the same rails app:update command that created this new framework defaults file.

What does this do?

Consider this Active Job:

# /app/jobs/my_job.rb
class MyJob < ApplicationJob
retry_on StandardError, wait: 5.seconds, attempts: 3

def perform
raise StandardError
end
end

How to safely uncomment?

This flag can be safely uncommented.

# /config/application.rb
# once this is set here, you can safely uncomment the flag
Rails.application.config.active_job.retry_jitter = false

What does this do?

Consider this Active Job:

# /app/jobs/my_job.rb
class MyJob < ApplicationJob
before_enqueue { |job| throw(:abort) unless job.arguments.first }
after_enqueue { logger.info 'I just got enqueued!' }
after_perform { logger.info 'I just got performed!' }
def perform(should_do_thing)
do_thing()
end
end

How to safely uncomment?

Grep your codebase for throw(:abort). If there are any instances of it, check to see if they’re inside before_enqueue hooks. If they are, check if those jobs also have after_enqueue or after_perform hooks.

# /app/jobs/my_job.rb
class MyJob < ApplicationJob
def perform(should_do_thing)
logger.info 'I just got enqueued!'
do_thing() if should_do_thing
logger.info 'I just got performed!'
end
end

What does this do?

This flag controls the SameSite policy for your Rails cookies. What on Earth is a SameSite policy?

  1. Alice convinces Bob to visit her website, which initiates a cross-origin request to bank.com.
  2. bank.com originally set its cookie withSameSite=none, which has no restrictions on cross-origin requests. The browser includes the cookie.
  3. Bob happens to be logged in, so the request is authenticated, and the bank happily transfers Bob’s money to Alice.
  4. Bob types bank.com into his browser’s URL bar. The browser includes the cookie for this request as well.
  5. Bob sees that all his money is gone!
  1. Alice convinces Bob to visit her website, which initiates a cross-origin request to bank.com.
  2. bank.com originally set its cookie with SameSite=strict, which tells the browser not to include its cookie on third-party requests from another site.
  3. Bob happens to be logged in, but since his cookie isn’t included, the request fails authentication. Bob’s money is safe.
  4. Bob types bank.com into his browser’s URL bar. The browser sees that it’s navigating across origins, so in accordance with SameSite=strict, it does not include the cookie for this request either. It turns out that strict is really strict!
  5. Bob is annoyed to find that he isn’t logged in! He refreshes the page. The browser sees that its top-level navigation is changing, but it’s not cross-origin this time. It includes the cookie. Bob sees that he is logged in now, but he’s confused. Why did he need to refresh the page to be logged in?
  1. Alice convinces Bob to visit her website, which initiates a cross-origin request to bank.com.
  2. bank.com originally set its cookie with SameSite=lax, which tells the browser not to include its cookie on third-party requests from another site.
  3. Bob happens to be logged in, but since his cookie isn’t included, the request fails authentication. Bob’s money is safe.
  4. Bob types bank.com into his browser’s URL bar. The browser sees that its top-level navigation is changing origins, but in accordance with SameSite=lax, it doesn’t care. It includes the cookie since the user is initiating the request, not the third-party site.
  5. Bob sees that his money is safe. Bob is happy.

How to safely uncomment?

This flag is safe to uncomment. As you saw with the three scenarios, lax is a sensible default policy. The majority of your users probably use Chrome, which has already been defaulting to lax anyway.

# /config/environments/production.rbconfig.action_dispatch.cookies_same_site_protection = :none
config.force_ssl = true

What does this do?

By default Rails includes a hidden authenticity token on forms as a CSRF countermeasure.

<input type="hidden" name="authenticity_token" value="B5xaeWjckaduqxSEy9SwC5WrQbjWjmr0lTozNKEOPFlycNsLkNpELaeTDXa/p3JBV8I8PunxMA0zHogWJ4g+AQ==">
B5xaeWjckaduqxSEy9SwC5WrQbjWjmr0lTozNKEOPFlycNsLkNpELaeTDXa/p3JBV8I8PunxMA0zHogWJ4g+AQ==
B5xaeWjckaduqxSEy9SwC5WrQbjWjmr0lTozNKEOPFlycNsLkNpELaeTDXa_p3JBV8I8PunxMA0zHogWJ4g-AQ
# This URL won't work as expected because +,/,= are reserved characters meant for delimiting parts of a URL.
www.bank.com?token=B5xaeWjckaduqxSEy9SwC5WrQbjWjmr0lTozNKEOPFlycNsLkNpELaeTDXa/p3JBV8I8PunxMA0zHogWJ4g+AQ==
# This URL will work as expected because it doesn't include reserved characters in the token.
www.bank.com?token=B5xaeWjckaduqxSEy9SwC5WrQbjWjmr0lTozNKEOPFlycNsLkNpELaeTDXa_p3JBV8I8PunxMA0zHogWJ4g-AQ

How to safely uncomment?

This flag can be safely uncommented.

What does this do?

y2k_utc = Time.utc(2000, 1) # 2000-01-01 00:00:00 UTC
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
# This is the current behavior.
ActiveSupport.utc_to_local_returns_utc_offset_times = false
no_offset = zone.utc_to_local(y2k_utc) # 1999-12-31 19:00:00 UTC
no_offset.class # Time
no_offset.zone # UTC
no_offset == y2k_utc # false
(y2k_utc - no_offset) / 1.hour # 5.0
# This is the new behavior after uncommenting this flag.
ActiveSupport.utc_to_local_returns_utc_offset_times = true
with_offset = zone.utc_to_local(y2k_utc) # 1999-12-31 19:00:00 -0500
with_offset.class # TZInfo::TimeWithOffset
with_offset.zone # EST
with_offset == y2k_utc # true
(y2k_utc - with_offset) / 1.hour # 0.0

How to safely uncomment?

Grep your codebase for utc_to_local. This flag can be safely uncommented if there are no instances of it.

What does this do?

It’s best practice to set config.force_ssl = true in production environments. This tells Rails to redirect HTTP requests to their HTTPS equivalents.

How to safely uncomment?

This flag is safe to uncomment. The 308 status was introduced back in 2015, browser support is strong, and there are fallbacks in place.

What does this do?

Multiple database support is a major feature of Rails 6.0. Here’s an example of such a setup:

  1. database.yml lists two production databases: primary and replica.
  2. There is an ApplicationRecord model set to write to primary and read from replica.
  3. There is an AnimalRecord model set to write to primary and read from replica.
  4. There is a Person model that inherits ApplicationRecord.
  5. There is a Dog model that inherits from AnimalRecord.
User.first # reads from replica
Dog.first # reads from replica
ActiveRecord::Base.connected_to(role: :writing) do
User.first # reads from primary
Dog.first # reads from primary
end
User.first # reads from replicaAnimalRecord.connected_to(role: :writing) do
Dog.first # reads from primary
end

How to safely uncomment?

This flag is safe to uncomment.

What does this do?

Rails has long provided form helpers. You used form_for when you were binding fields to a model, and form_tag when you weren’t. Frustratingly, the syntax was slightly different between them.

# /app/controllers/clients_controller.rb
class ClientsController < ApplicationController
def create
@client = Client.new(params[:client])
if @client.save
# This redirect works as expected. If you're not using
# Turbolinks, the browser does a full refresh to the
# client show page. If you are using Turbolinks, the
# browser merges the client show page into the current page,
# sans refresh.
redirect_to @client
else
# This never works as expected. The browser will receive a
# response to its XHR containing the new template including
# errors. If you're not using Turbolinks, nothing happens
# unless you implement your own JS to handle the response.
# If you are using Turbolinks... nothing happens. Turbolinks
# version 5 isn't programmed to do anything with the
# response either.
render "new"
end
end
end

How to safely uncomment?

Grep your codebase for form_with. This flag can be safely uncommented if there are no instances of it.

What does this do?

When a file is attached with Active Storage, the after_commit_create callback on ActiveStorage::Attachment is called. That callback enqueues an ActiveStorage::AnalysisJob. When performed, that job calls ActiveStorage::Blob#analyze. That method uses a plugin system to extract metadata from the file. That metadata is saved to the metadata column on the blob record.

How to safely uncomment?

This flag can be safely uncommented.

# /config/application.rb
# once this is set here, you can safely uncomment the flag
Rails.application.config.active_storage.queues.analysis = :active_storage_analysis

What does this do?

Consider this:

class User < ApplicationRecord
has_one_attached :avatar
end
sam = User.create
sam.avatar.attach(some_image_file)
sam.avatar.attach(some_other_image_file)

How to safely uncomment?

This flag can be safely uncommented.

# /config/application.rb
# once this is set here, you can uncomment the flag
Rails.application.config.active_storage.queues.purge = :active_storage_purge

What does this do?

Here’s (the relevant part of) how Action Mailbox handles incoming emails:

  1. Rails receives the raw email from an ingress.
  2. The raw email gets stored in Active Storage (e.g., S3).
  3. An InboundEmail record is created by parsing the raw email.
  4. An IncinerationJob is enqueued for 30 days in the future. When run, this job will purge the raw email from Active Storage and delete the parsed InboundEmail record. This ensures that PII isn’t permanently stored outside the domain model.
  5. The InboundEmail is routed to an ApplicationMailboxresponsible for processing it. The mailbox decides what data from the email it wants to permanantly store (e.g., creating a Message record).

How to safely uncomment?

This flag can be safely uncommented.

# /config/application.rb
# once this is set here, you can uncomment the flag
Rails.application.config.action_mailbox.queues.incineration = :action_mailbox_incineration

What does this do?

Here’s (the relevant part of) how Action Mailbox handles incoming emails:

  1. Rails receives the raw email from an ingress.
  2. An InboundEmail record is created by parsing the raw email.
  3. A RoutingJob is enqueued. When run, this job will pass the InboundEmail to the appropriate ApplicationMailbox.

How to safely uncomment?

This flag can be safely uncommented.

# /config/application.rb
# once this is set here, you can uncomment the flag
Rails.application.config.action_mailbox.queues.routing = :action_mailbox_routing

What does this do?

Consider this mailer:

# /app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
def welcome(user_id)
@user = User.find!(user_id)
mail(
to: @user.email,
subject: 'Welcome to the Site!',
from: 'notifications@example.com'
)
end
end

How to safely uncomment?

This flag can be safely uncommented.

# /config/application.rb
# once this is set here, you can safely uncomment the flag
Rails.application.config.action_mailer.deliver_later_queue_name = :mailers

What does this do?

Imagine that we want to execute some JavaScript on a page when it’s finished loading. We might do something like this:

<html>
<head>
<!-- The 'defer' attribute tells the browser to wait until it
finishes parsing all the HTML before loading/executing
the script. This is roughly equivalent to putting the
script tag at the bottom of the body. -->
<%= javascript_include_tag('some.js'), defer: true %>
</head>
<body>
</body>
</html>
<html>
<head>
<!-- This helper generates an element that looks like this:
<link rel="preload" href="/some.js" as="script">
This tells the browser 'go ahead and download this, but
don't execute it yet, I'll do something with it later'.
-->
<%= preload_link_tag('some.js') %>

<%= javascript_include_tag('some.js'), defer: true %>
</head>
<body>
</body>
</html>
# /app/controllers/some_controller.rb
class SomeController < ApplicationController
# This is the action responsible for rendering our ERB.
# Browsers allow Link elements to be serialized as HTTP headers.
# This moves the preload directive out of the response body.
# The browser now knows to preload the script without even
# looking at the HTML.
def show
response.set_header('Link','</some.js>; rel=preload; as=script')
end
end

How to safely uncomment?

This flag can be safely uncommented. Browsers that support Link headers will get a performance boost. Browsers that don’t will ignore them.

# /config/environments/development.rb
Rails.application.config.action_view.preload_links_header = false
  1. Let non-IE browsers unnecessarily download some assets they won’t use.
  2. Disable this feature altogether.
  3. Stop supporting IE 5–9. What’s wrong with you?
  4. Write a helper that disables this feature on a per-asset basis (named something like javascript_include_tag_without_preload). Here’s an example.

Now what?

Once all 16 flags are uncommented, you don’t need config/initializers/new_framework_defaults_6_1.rb anymore. Delete that file and add load_defaults 6.1 to your config/application.rb. This is functionaly equivalent to having all the flags uncommented.

Would you like to work with the latest Rails?

The Bridge Services team at Evidation Health is hiring! We always work with the latest tech—not to mention competetitive pay and great WLB.

--

--

Seeking staff engineer position

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store