Today I Learned

A Hashrocket project

174 posts about #rails

accepts_nested_attributes_for in Rails 5.2

Getting accepts_nested_attributes_for to work can be a bit tricky, and most of the walkthroughs I found are a bit dated, so here’s a quick update.

Our models will be User and Address:

class User < ApplicationRecord
  has_many :addresses
  accepts_nested_attributes_for :addresses

class Address < ApplicationRecord

Now, assuming we have a UsersController, with all the standard resource actions omitted, we try to add addresses to our permitted params:

class UsersController < ApplicationController
  def user_params
    params.require(:user).permit(:name, :email, :addresses)

Logically this seems like it would work, but if we look in the rails server logs, we will see:

Unpermitted parameter: :addresses_attributes

So we add it to our permitted params:

  def user_params
    params.require(:user).permit(:name, :email, :addresses_attributes)

and refresh. Lo and behold, we get the same error!

Unpermitted parameter: :addresses_attributes

The secret is that address_attributes is a hash, and we need to communicate that to Rails:

  def user_params
    params.require(:user).permit(:name, :email, addresses_attributes: {})

Et voilà!

Rails `travel_to` changes the value of `usec` to 0

In and of itself, not a huge deal. However, when you combine it with the way that active record quotes times:

It can lead to some incredibly subtle bugs in your tests that use queries in their assertions. Long story short, if your query relies on a time comparison, you may not return anything. :|

Rails has an Array#exclude? method

In my vendetta against the unless expression in ruby, I came across a use case where I wanted to execute code only if an item was missing from an array.

I could easily do:

unless ['a', 'b', 'c'].include?('d')
  # do a thing

But I wanted to replace the unless with an if, which led me to wonder if there was an exclude? method for arrays in ruby.

Turns out, ActiveSupport extended the Enumerable class to introduce exactly what I was looking for!

if ['a', 'b', 'c'].exclude?('d')
  # do a thing


Rails Collection#exists?

I’ve been using Rails for almost a decade, and find I still don’t know even close to all it has to offer. Today, I was browsing through an open source app, and stumbled across this code:

unless feed.posts.exists?(
  feed.posts << @post

I’d never seen or used exists? on a collection before, so I looked it up. And of course, there it is, right in the Rails Guides Association Basics - methods added by has many!

Digging in a bit, what arguments can exists? take? Here’s the reference.

Turns out there a lot of options!. Integer, string, array, hash, false, or no args.

Well that’s kinda neat! So what does the no args do?


It seems to behave similar to any?, and return true if there are any members of the collection, false if it’s empty.

TLDR: Read the Rails Guides. A decade in, you’ll probably still learn something!

Access a shadowDOM node from Capybara

Using selenium_headless you can access a node from the shadowDOM using evaluate_script. The trick is knowing where in the showdowRoot.childNodes it’s going to be. If you have a web component called my-dope-component then you could do something like this:

shadow_host = find('my-dope-component')
shadow_child = evaluate_script('arguments[0].shadowRoot.childNodes[<magic-index>]', shadow_host)

The value for <magic-index> depends on the structure of the template that you’re using, as well as the library (if any) that you’re using. You could also write a find style script that iterates the children and locates the correct one based on the tagName property or similar.

For example:

Array.from(arguments[0].shadowRoot.childNodes).map(element => {
  if (element.tagName === arguments[1].toUpperCase()) return element;
}).filter( value => value != undefined)[0]

Swap between STI classes in Rails

Rails allows you to swap between single table inheritance classes with the becomes method. This method creates a new instance of the desired class, and passes the attributes from the original record to the new instance.

So if I have two classes, User and Admin, where Admin inherits from User:

class User
  # ...

class Admin < User
  # ...

I can change between the two classes using becomes:

user = User.first # returns an instance of the User class
user.class # => User # => 1 # => Joe Hashrocket

admin = user.becomes(Admin) # returns an instance of the Admin class
admin.class # => Admin # => 1 # => Joe Hashrocket

Disable Spring in Rails with `DISABLE_SPRING`

This is fairly self explanatory. If you don’t want spring running, because maybe you forget it’s on and then you’re confused when you’re making changes to configuration and you don’t see those changes manifest, then you can disable spring with:


But be careful about where you put this. If you are using dotenv and you place it in your .env file it will not take effect. dotenv sets its environment after spring has started.

This is something that you should put in bashrc or zshrc.

export DISABLE_SPRING=true

Remove newlines from strings in Rails

Rails has a method called squish that will remove newlines from strings. It works very nicely with heredocs, where you may want readability but don’t really want the newlines.

Without squish:

  update posts
  set status = 'public'
  where status is null;
# "update posts\nset status = 'public'\nwhere status is null;\n"

With squish:

  update posts
  set status = 'public'
  where status is null;
# "update posts set status = 'public' where status is null;"

Couldn't decrypt `config/credentials.yml.enc`

Rails 5.2 introduced the credentials system for managing secure credentials in your application. You can edit the file with rails credentials:edit, which is pretty dope. But if you do like I did and you start your project on one machine and go to try and edit that file on a different machine after you pull down the repository from Github, it will generate a new master.key file for you. This will not work to decrypt the file, and you’ll get the error above. You need to make sure that you grab the file from wherever you started your application from. In my case it was as simple as using scp to grab the file from a different machine.

Constrain a route to a specific host

If you have two hosts that point to one rails app, like and and you’d like a specific route to only be availabe on orange then you can constrain the route based on the host like this:

constaints(host: '') do
  get '/thing/:id', to: 'thing#show'

And if you had additional host for banana that should also get this route:

constaints(host: ['', '']) do
  get '/thing/:id', to: 'thing#show'

But in this case only the subdomain differs so we can do this:

constaints(subdomain: ['orange', 'banana']) do
  get '/thing/:id', to: 'thing#show'

Rails CollectionProxy #length vs #count

When testing a controller, it’s not unreasonable to right a test something like this:

expect(post_to_endpoint).to change { some.objects.length }.from(0).to(1)

Because of the details of CollectionProxy, this will not work as expected. Instead, you need to write:

expect(post_to_endpoint).to change { some.objects.count }.from(0).to(1)

The length method will evaluate to the same value, while the count method will reflect changes to the underlying data since the last call.

Migrating to form_with

Whilst upgrading a Rails application from 4.1 to 5.2, my feature specs started throwing a strange error:

Unable to find field "X" that is disabled (Capybara::ElementNotFound)

I reverted my changes… the tests passed. Step 1 in debugging: read the error message. Capybara is saying the element I’m trying to find is disabled. If I inspect the html generated by form_for:

= form_for book do |f|
  = f.label :title, 'Title'
  = f.text_field :title
  = f.button 'Save Changes', type: :submit
<form action="/books/14" accept-charset="UTF-8" method="post">
  <label for="book_title">Title</label>
  <input type="text" name="book[title]" id="book_title">
  <button name="button" type="submit">Save Changes</button>

and compare it to the html generated by form_with:

= form_with model: book, local: true do |f|
  = f.label :title, 'Title'
  = f.text_field :title
  = f.button 'Save Changes', type: :submit
<form action="/books/14" accept-charset="UTF-8" method="post">
  <label for="book_title">Title</label>
  <input type="text" name="book[title]">
  <button name="button" type="submit">Save Changes</button>

Notice what’s missing. form_with does not automatically generates ids for form elements, and the id is necessary to link the label’s for with the input’s id, otherwise fill_in('Title', with: "Pride and Prejudice") doesn’t work in Capybara. Either add the ids manually, or in Rails 5.2 use this setting:

Rails.application.config.action_view.form_with_generates_ids = true

PostgreSQL index with NO lock on Rails

Rails allow us to create a PostgreSQL index that it would not lock the table for writing meanwhile it’s being calculated.

That’s usually handful if a table has millions of rows and this operation could take hours to release the INSERT/UPDATE/DELETE lock. Maybe the lock downtime is critical to the application.

There are caveats to this approach so please read the PostgreSQL documentation.

In order to do that you’ll need to add { algorithm: :concurrently } to the add_index on a Rails migration. Additionally PostgreSQL uses DB transactions to manage this new index creation so we need to disable the Rails migration transaction. Take this example:

class AddIndexOnEventsName < ActiveRecord::Migration[5.0]

  def change
    add_index(:events, :name, algorithm: :concurrently)

In this case I’m using disable_ddl_transaction! to disable the DB transaction that wraps each Rails migrations and I am using algorithm: :concurrently.

This option it will generate an index like that:

CREATE INDEX CONCURRENTLY "index_events_on_name" ON "events" ("name"));

Rails will change the `not` behavior

Yay Rails will change the behavior of the ActiveRecord#not on rails 6.1. That’s great as the current behavior sometimes leads to confusion. There will be a deprecation message on the version 6.0 so let’s watch out and change our code accordingly.

As I already wrote on this TIL this function does not act as a legit boolean algebra negation. Let’s say that we have this query:

User.where.not(active: true, admin: true).to_sql

Prior to this change this will produce this query:

SELECT users.*
  FROM users
 WHERE != 't'
   AND users.admin != 't'

The problem is that negating an AND clause is naturally a NAND operator, but Rails have implemented as a NOR. Some developers might wonder why regular active users are not returning.

On Rails 6.1 this will be fixed hence the new query will be:

SELECT users.*
  FROM users
 WHERE != 't'
    OR users.admin != 't'

Here’s the rollout plan:

  • Rails 5.2.3 acts as NOR
  • Rails 6.0.0 acts as NOR with a deprecation message
  • Rails 6.1.0 act as NAND

This is the PR in case you want to know more.

Use `reset_column_information` to Migrate Data

If you’re generating a Rails migration, chances are you might need to facilitate migrating data to a new column. You can use reset_column_information in a migration file to pick up your changes and immediately do something with those new columns.

Assuming we have 2 models, DraftPost and Post -

  class AddColumnDraftToPosts < ActiveRecord::Migration[5.2]
    def change
      add_column :posts, :draft, :boolean, default: true
      DraftPost.all.each do |draft_post|
        Post.create(content: draftPost.content)

Rails 6 new ActiveRecord method pick

Rails 6 will be released with a new convenient method on ActiveRecord pick. This method picks the first result value(s) from a ActiveRecord relation.

# => 'John'

Person.all.pick(:name, :email)
# => ['John', '']

So pick(:name) is basically equivalent to limit(1).pluck(:name).first.

Watch out for rails inconsistencies as pick(:name) returns a single string value and pick(:name, :email) returns an array of values.

Rails 6 Blocked Hosts

Rails 6 has a new feature where only whitelisted hosts are allowed to be accessed. By default only localhost is permitted.

When doing mobile development, you always need to test the app in a real device that connects to a backend. In order to automatically add the dev machine host to the list, just change your development.rb to:

# config/environments/development.rb 

config.hosts << "#{`hostname -s`.strip}.local"

Rails 6 upsert_all

Rails 6 and ActiveRecord introduces upsert_all 🎉

I used to have to jump into SQL and write this myself, but no more!

As a simplified use case, say you have a User and that user has many Tags. If an API wants to send you an array of strings (["Tag Name A", "Tag Name B"]), you no longer have to do any looping, find_or_create, or defining your own upsert in SQL nonsense.

@user.tags << Tag.upsert_all([
  { name: "Tag Name A"},
  { name: "Tag Name B"}
], unique_by: :name)

If the tags exist, they will be updated. If the tags are new, they will be created. And all this happens in 1 SQL Insert.

ActiveRecord not is not boolean algebra negation

Today I learned that rails ActiveRecord not query is not to be considered a boolean algebra negative. Let’s see by an example:

User.where(name: "Jon", role: "admin")

This will produce a simple sql query:

SELECT "admins".*
FROM "admins"
WHERE "admins"."name" = $1
  AND "admins"."role" = $2
[["name", "Jon"], ["role", "admin"]]

If we get the same where clause and negate it:

User.where.not(name: "Jon", role: "admin")

Then we get:

SELECT "admins".*
FROM "admins"
WHERE "admins"."name" != $1
  AND "admins"."role" != $2
[["name", "Jon"], ["role", "admin"]]

But I was expecting a query like:

SELECT "admins".*
FROM "admins"
WHERE ("admins"."name" != $1
   OR "admins"."role" != $2)
 [["name", "Jon"], ["role", "admin"]]

So if you want to produce a query like that you’ll have to build on your own:

User.where.not(name: "Jon").or(User.where.not(role: "admin"))

Just beaware of that when using not with multiple where clauses.

Delete Paranoid Records In Rails

The ActsAsParanoid gem provides soft delete functionality to ActiveRecord objects in Rails. You can enhance a model with its functionality like so:

class User < ActiveRecord::Base

This gem hijacks ActiveRecord’s standard destroy and destroy! functionality. If you call either of these methods, instead of the record being deleted from the database, it’s deleted_at column is updated from nil to the current timestamp. Resulting in a soft deleted record.

If you call destroy or destroy! a second time (i.e. on a record that has already been soft deleted), it will be actually deleted from the database. Alternatively, you can call destroy_fully! from the beginning to skip the soft delete.

Serialize With fast_jsonapi In A Rails App

Netflix put out a Ruby gem for super fast JSON serialization — fast_jsonapi. It is great for serializing JSON responses for Rails API endpoints.

First, add gem 'fast_jsonapi' to your Gemfile and bundle install.

Then create the app/serializers directory for housing all of your JSON serializers.

Next you can create a serializer that corresponds to the model you want to serialize:

# app/serializers/recipe_serializer.rb
class RecipeSerializer
  include FastJsonapi::ObjectSerializer

  set_id :id
  attributes :name, :source_url

Last, use it to generate a JSON response in your controller:

# app/controllers/recipes_controller.rb
class RecipesController < ApiController
  def index
    render json:

Requests to that endpoint will receive a response that looks something like this:

  data: [
      id: 1,
      attributes: { name: "Old Fashioned", source_url: "http://..." },
      id: 2,
      attributes: { name: "Sazerac", source_url: "http://..." },

Secure Passwords With Rails And Bcrypt

If you are using bcrypt (at least version 3.1.7), then you can easily add secure password functionality to an ActiveRecord model. First, ensure that the table backing the model has a password_digest column. Then add has_secure_password to your model.

class User < ActiveRecord::Base

  # other logic ...

You can now instantiate a User instance with any required fields as well as password and password_confirmation. As long as password and password_confirmation match then an encrypted password_digest will be created and stored. You can later check a given password for the user using the authenticate method.

user = User.find_by(email: user_params[:email])

if user.authenticate(user_params[:password])
  puts 'That is the correct password!'
  puts 'That password did not match!'

Rails protects production database

Rails has a mechanism to protect production databases to be dropped (and other destructive commands). In order to do that rails database tasks use a private database rake task check_protected_environments. Here’s a db task code sample from rails code databases.rake:

task drop: [:load_config, :check_protected_environments] do

Under the hood it checks the database environment from a metadata table ar_internal_metadata created by rails on the first load schema attempt

SELECT * FROM ar_internal_metadata;
     key     |  value 
 environment | staging 

Access Secrets In A Rails 5.2 App

For a long time the access chain for getting at secrets in your Rails app stayed the same. For instance, getting at the secret_key_base value looked something like this:


In the world of Rails 5.2, secrets are no longer secrets. They are now credentials. This means they are under the credentials key instead of the secrets key. Here is how you can access secret_key_base now:



Rails with_options

Rails has a convenient method with_options for reusing options on rails methods.

On the documentation we can see how to use on ActiveRecord classes but as this method is under Rails Object it actually can be used on several other places, like controllers.

Check this out:

before with_options

class PostsController < ApplicationController
  before_action :require_login, only: [:show, :edit]
  before_action :load_post_extra_info, only: [:show, :edit]


using with_options

class PostsController < ApplicationController
  with_options only: [:show, :edit] do
    before_action :require_login
    before_action :load_post_extra_info


Thanks @mattpolito for that!

Reversible integer to string migrations in Rails

I recently needed to convert an integer column in Rails to a string, and wanted to make sure that the migration would be reversible. I specified the up and down methods, but found that I couldn’t reverse the migration because the column type couldn’t be automatically cast back into an integer.

As it turns out, Rails allows us to specify how to cast the column with the using option:

def up
  change_column :users, :zip, :string

def down
  change_column :users, :zip, :integer, 
    using: "zip::integer"

This builds the sql:

TYPE integer
USING zip::integer

Good to go!

Pass a Block on Console Load

Have some Ruby code you want to run as the Rails console loads? Here’s a technique.

In this example, I’m going to use a function from the hirb gem. Add the gem to your Gemfile and bundle.

Next, pass a block to console in your application configuration.

# config/application.rb

module MyApplication
  class Application < Rails::Application
    console do
      # Action(s) I want to run on console load...

The next time you start the Rails console, Hirb.enable will run after load.

code / docs

Outer join with ActiveRecord `references` method

I want to join posts to comments regardless if comments exist or not.

You can use includes for that:


But that results in 2 queries, one to get all the posts, and one to get all comments that have the relevant post_id.

With references you can turn this into an outer join:


Now we’re getting all the information we need with just 1 query.

Check out the Active Record guides here

How to check Rails migration statuses

You can check the status of your migrations in Rails by running rails db:migrate:status

This command returns your database name, as well as a list of all your migrations, with their name and status

database: my-database-dev

Status | Migration ID | Migration Name
  up   | 201803131234 | Create users
  up   | 201803201234 | Create blogs
 down  | 201804031234 | Create posts

ActiveRecord where.not

When I need a IS NULL SQL expression with ActiveRecord it is easy to avoid a SQL string fragment.

User.where(deactivated_at: nil)

But in early versions of Rails using IS NOT NULL required a string.

User.where("deactivated_at is not null")

But modern Rails has where.not expressions which can eliminate the string.

User.where.not(deactivated_at: nil)

Add React With Webpacker To A New Rails App

Webpacker makes it easy to manage app-like JavaScript in the context of a Rails app. React is a great candidate for this kind of webpack-powered JavaScript processing pipeline.

To set up a new Rails project with Webpack and React wired up, add the --webpack=react flag:

$ rails new rails-react-app --webpack=react

As part of the generated app, you will get a app/javascript/packs directory with a hello_react.jsx file that has a really basic React component.


Specify different bundler groups with RAILS_GROUPS

Is there a gem that you want to include sometimes and not others? For instance, do you have multiple versions of staging with slightly different gemsets? You can manage that with custom groups.

group :apple do
  gem 'some_gem'

group :orange do
  gem 'some_other_gem'
RAILS_GROUPS=apple,orange rails console
> Rails.groups
[:default, "development", "apple", "orange"]

In the config/application.rb file, Rails.groups is passed to Bundler.require.


Change Name of :id Param in Rails Resource Routing

You can change the name of the parameter used by rails resource routing by specifying the param option to the resource route.

For example, if we have a show endpoint:

resources :pages, only: :show

Running rake routes will return the pages#show url as:


We can change the id param to slug by doing:

resources :pages, only: :show, param: :slug

Then running rake routes will yield our new pages#show route as:


Delayed Job Queue Adapter in RSpec with Rails 5.1

In old versions of Rails, you were able to override the ActiveJob queue in a test like this:

describe MyJob do
  it 'works' do
    ActiveJob::Base.queue_adapter = :delayed_job
    expect {
    }.to change(Delayed::Job.count).by(1)

With Rails 5.1, we have the ActiveJob::TestHelper class which you will need to employ in your tests. In order to override the queue a different strategy is needed.

describe MyJob do
  def queue_adapter_for_test
  it 'works' do
    expect {
    }.to change(Delayed::Job.count).by(1)

You will need to add the following to your rspec config or a support file:

RSpec.configure do |config|

# you will also need the code below for the test
# to clear out the jobs between test runs
class ActiveJob::QueueAdapters::DelayedJobAdapter
  class EnqueuedJobs
    def clear
      Delayed::Job.where(failed_at:nil).map &:destroy
  class PerformedJobs
    def clear
      Delayed::Job.where.not(failed_at:nil).map &:destroy
  def enqueued_jobs
  def performed_jobs

🔑 Set foreign keys to null

Sometimes, in certain circumstances, it is reasonable to have a foreign key value of null.

ActiveRecord’s .has_many method has an argument to set the foreign key column on referencing rows to null when that record is deleted.

dependent: :nullify


class Post < ApplicationRecord
  belongs_to :author
  belongs_to :category

class Category < ApplicationRecord
  has_many :posts, dependent: :nullify

In this example whenever a category is deleted, any posts referencing the categories table will have their foreign key set to null.


`disable_with` to prevent double clicks

If a link or button initiates a request that takes a long time to respond then the user (being a user) might click the link or button again. Depending on the code implemented on the server this could put the application into a bad state.

Rails has a handy feature that will disable a link or button after the user has clicked it, disable_with:

<%= link_to('something',
      data: {
        disable_with: "Please wait..."

In Rails 5.0 and up sumbit buttons have disable_with set by default. To disable disable_with use:

data: { disable_with: false }

Storing recurring schedules in #Rails + #Postgres

If you have a scheduling component to your Rails application you may need to store the day of week and time of day in the database.

One way to store the day of week is to use an integer column with a check constraint that will check that the value is between 0 and 6.

create table schedules (
  id serial primary key,
  day_of_week integer not null check(day_of_week in (0,1,2,3,4,5,6)),
  beg_time time not null,
  end_time time not null

Then when you read it back from the database and need to convert it back to day name you can use Date::DAYNAMES. e.g.:

[2] pry(main)> require 'date'
=> true
[3] pry(main)> Date::DAYNAMES
=> ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
[4] pry(main)> Date::DAYNAMES[0]
=> "Sunday"
[5] pry(main)>

If you need to store time of day as entered (in a time without timezone column - as specified above) check out the wonderful Tod gem by Jack Christensen

Convert to BigDecimal with `to_d` w/ActiveSupport

Ruby provides the BigDecimal method to convert to BigDecimal.

> require 'bigdecimal'
> BigDecimal("123.45")

But you can’t convert a float without a precision

> BigDecimal(123.12)
ArgumentError: can't omit precision for a Float.
> BigDecimal(123.12, 5).to_s

When using Rails, and specifically with ActiveSupport required, you can use the to_d method converts to BigDecimal.

> require 'active_support'
> 123.to_d
> "123".to_d

And for floats provides a default precision of Float::DIG+1 which for me is 16. DIG is described as

The number of decimal digits in a double-precision floating point.

> 123.45.to_d
> 123.45.to_d.to_s

Note, to_s in ActiveSupport outputs a more human readable number. Also Note, nil is not convertable with to_d

> require 'active_support'
> nil.to_d
NoMethodError: undefined method `to_d' for nil:NilClass
> BigDecimal(nil)
TypeError: no implicit conversion of nil into String

What you had before you saved w/`previous_changes`

When you set values into the ActiveRecord object the previous values are still available with changes, but when you save, however you save, those changes are wiped out. You can access what those values were before saving with previous_changes.

> thing = Thing.create({color: 'blue', status: 'active'})
> thing.color = 'red'
> puts thing.changes
{"color" => ['blue', 'red']}
> puts thing.previous_changes
> puts thing.changes
> puts thing.previous_changes
{"color" => ['blue', 'red']}

Access record from ActiveRecord::RecordInvalid

You can pass an array of hashes to Thing.create! in ActiveRecord. If one of those records is invalid, then an ActiveRecord::RecordInvalid error is thrown. You might need to know which record threw the error, in which case you can get the record from the error with record_invalid_error.record

bad_record = nil

  Things.create!([{value: 'bad'}, {value: 'good'}])
rescue ActiveRecord::RecordInvalid => record_invalid_error
  bad_record = record_invalid_error.record

if bad_record
  puts "got a bad record with value: #{bad_record.value}"