Today I Learned

A Hashrocket project

Ready to join Hashrocket? Find Openings here and apply today.

192 posts about #rails

Custom URL helpers in Rails

Ever want a named route for something that’s not necessarily a resource in your Rails app?

In your any of your route files you can utilize direct.

# config/routes.rb

Rails.application.routes.draw do
  get 'foo', to: 'foo#bar'

  direct :get_to_the_goog do
    "https://google.com"
  end
end

This gives a nice named route of get_to_the_goog_url!

This can be even more useful as the return value of the block has to be valid arguments that you would pass to url_for. So you can use pretty much anything you’d normally use to build a url helper.

Let’s modify this just a bit to be more useful.

# config/routes.rb

Rails.application.routes.draw do
  get 'foo', to: 'foo#bar'

  direct :get_to_the_goog, search: nil do |options|
    "https://google.com/search?q=#{options[:search]}"
  end
end

Now we can call get_to_the_goog_url(search: "TIL").

Pretty cool… just take note of a few caveats. You get access to the _path version of the named helper but even if the helper is using a hard coded string (like our example above), it will remove the domain info (rightfully so). Also you are not able to use the direct functionality inside of a namespace or scope, however it will raise an error if you do… so that’s nice.

Break up large routing files in Rails

Breaking apart a routing file could be useful if you have large a different concepts in your app with their own larger route sets.

To do this you can utilize draw

# config/routes.rb

Rails.application.routes.draw do
  get 'foo', to: 'foo#bar'

  draw(:admin)
end

This will try to load another route file at config/routes/admin.rb. That file is just another normal routing file.

# config/routes/admin.rb

namespace :admin do
  resources :users
end

How to use ActiveRecored.pluck without Arel.sql

It is too cumbersome to remember to wrap every string in a class method, so this is a shortcut:

# app/models/application_record.rb

class ApplicationRecord < ActiveRecord::Base
  module ::ActiveRecord::Sanitization::ClassMethods
    define_method(:original_disallow_raw_sql!, instance_method(:disallow_raw_sql!))

    def disallow_raw_sql!(args, permit: connection.column_name_matcher) # :nodoc:
      original_disallow_raw_sql!(args.map { |a| a.is_a?(String) ? Arel.sql(a) : a }, permit: permit)
    end
  end
end

Then you can write sql without the class methods

This can be easier to write:

User.where(payment_due: true)
  .pluck(Arel.sql("coalesce('last_billed_date', 'start_date')"))

Can now just be written as:

User.where(payment_due: true)
  .pluck("coalesce('last_billed_date', 'start_date')")

This can also be useful for .order as well

Subtlety between Rails' #try & Ruby's #&.

Many would say that the safe navigation operator (&.) in Ruby is a drop in replacment for Rails’ #try. However I came across an interesting case where they are not the same.

thing = nil
thing&.something
> nil
thing = nil
thing.try(:something)
> nil

Seems pretty much the same right?

However what happens when the object receiving the message is not nil but does not respond to the message?

thing = Thing.new
thing&.something
> NoMethodError: undefined method 'something' for <Thing:0x0000>
thing = Thing.new
thing.try(:something)
> nil

Hopefully not something you’ll run into often, but definitely be aware of it. It makes the safe navigation operator not necessarily a drop in replacment.

Rails has helpers for uploading spec fixture files

Instead of writing your own helper method in specs:

module HelpMePlease
  def uploaded_file(path)
    Rack::Test::UploadedFile.new(Rails.root.join("spec/fixtures", path))
  end
end

Rails has a built in helper method:

require "rails_helper"

RSpec.describe HelpNeeded do
  describe "something" do
    it "sends the file" do
      post :change_avatar, params: { avatar: fixture_file_upload("spongebob.png", "image/png") }
    end
  end
end

Finding Rails ActiveModel Errors

Rails allows to filter ActiveModel erros by field name and type with the where method. Check this out:

user = User.new(email: "user@test.com")
user.valid?

user.errors.where(:email).map(&:full_message)
# => [
#      "Email has already been taken",
#      "Email is too short (minimum is 20 characters)"
#    ]

user.errors.where(:email, :taken).map(&:full_message)
# => ["Email has already been taken"]

Rails delegated_type

Rails 6.1 added delegated_type for the ones who like polymorphic relations. So in addition to set a regular polymorphic relation in rails you can also call delegated_type method to inject a bunch of useful methods and scopes for you. Check this out:

class Content < ApplicationRecord
  # belongs_to :contentable, polymorphic: true
  delegated_type :contentable, types: %w[ TilPost BlogPost ]
end

class TilPost < ApplicationRecord
  has_one :content, as: :contentable
end

class BlogPost < ApplicationRecord
  has_one :content, as: :contentable
end

And this will produce helper methods like:

content.contentable_class
# => +TilPost+ or +BlogPost+
content.contentable_name
# => "til_post" or "blog_post"

Content.til_posts
# => Content.where(contentable_type: "TilPost")
content.til_post?
# => true when contentable_type == "TilPost"
content.til_post
# => returns the til_post record, when contentable_type == "TilPost", otherwise nil
content.til_post_id
# => returns contentable_id, when contentable_type == "TilPost", otherwise nil

Rails 6 `create_or_find_by`

Rails 6 comes with a new ActiveRecord method create_or_find_by.

User.create_or_find_by(first_name: 'Vinny') do |user|
  user.status = 'pending'
end
# => #<User id: 1, first_name: "Vinny", status: pending>

This method is similar to the already existing find_or_create_by method by it tries to create the model in the database first, then if an unique constraint is raised by the DB then rails rescue the error, it rollbacks the transaction and it finally finds the model.

The idea behind to use this new method instead is to avoid race condition between a find and a create calls to the db.

There’s a few downsides to this process, but one that caught my eyes was that now we’re seeing a lot of ROLLBACK messages in the rails logs, which is the expected behavior now.

When to set `inverse_of` in Rails AR

Rails ActiveRecord does not auto infer bi-directional associations if some of the associations contains a scope or any of the following through or foreign_key options.

class Author < ApplicationRecord
  has_many :books
end

class Book < ApplicationRecord
  belongs_to :writer, class_name: 'Author', foreign_key: 'author_id'
end
irb> a = Author.first
irb> b = a.books.first
irb> a.first_name = 'David'
irb> a.id = b.writer.id
=> true
irb> a.object_id = b.writer.object_id
=> false
irb> a.first_name == b.writer.first_name
=> false

To solve this issue we should use inverse_of option, check this out:

class Author < ApplicationRecord
  has_many :books, inverse_of: 'writer'
end

class Book < ApplicationRecord
  belongs_to :writer, class_name: 'Author', foreign_key: 'author_id'
end
irb> a = Author.first
irb> b = a.books.first
irb> a.first_name = 'David'
irb> a.id = b.writer.id
=> true
irb> a.object_id = b.writer.object_id
=> true
irb> a.first_name == b.writer.first_name
=> true

Conditionally Render Rails Links with `link_to_if`

ActionView::Helpers contains some really handy helpers for conditionally rendering links. For example, the link_to_if method will render the link if the given condition is met; otherwise it renders just the text.

<%= link_to_if(current_user.present?, "Admin Panel Tools", admin_tools_path) %>

When current_user.present? is true, yields the following HTML:

<a href="/admin_tools">Admin Panel Tools</a>

But when current_user.present? is false, yields the following:

Admin Panel Tools

https://api.rubyonrails.org/v6.0.2.1/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to_if

Note, there are also methods for link_to_unless and link_to_unless_current

Easily Format Phone Numbers in Rails

Included as part of ActiveSupport, Rails has a handy helper for formatting phone numbers. Passing a number/string to the helper and some optional args and it will take care of the rest.

Here’s a few examples of how it works:

number_to_phone(8858846) # 885-8846
number_to_phone(8778858846) # 877-885-8846
number_to_phone(8778858846, area_code: true) # (877) 885-8846

To see all the options for the method, check out the Rails docs https://api.rubyonrails.org/classes/ActiveSupport/NumberHelper.html#method-i-number_to_phone

Group Related ActiveRecord Validations

Today I learned about the with_options feature of ActiveRecord. It’s used to group validations together in a block. You could use it to transform code like this:

class User < ApplicationRecord
  validates :password, length: { minimum: 10 }, if: :is_admin?
  validates :email, presence: true, if: :is_admin?
end

Into this:

class User < ApplicationRecord
  with_options if: :is_admin? do |admin|
    admin.validates :password, length: { minimum: 10 }
    admin.validates :email, presence: true
  end
end

A use case for this might be a collection of validations that should only be checked when a boolean feature flag like enabled? is true.

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
end

class Address < ApplicationRecord
end

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)
  end
end

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)
  end

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: {})
  end

Et voilà!

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

https://github.com/rails/rails/blob/4dcc5435e9569e084f6f90fcea6e7c37d7bd2b4d/activesupport/lib/active_support/testing/time_helpers.rb#L145

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

https://github.com/rails/rails/blob/4dcc5435e9569e084f6f90fcea6e7c37d7bd2b4d/activesupport/lib/active_support/testing/time_helpers.rb#L145

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
end

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
end

Hooray!

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?(@post.id)
  feed.posts << @post
end

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?

Imgur

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:

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

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
  # ...
end

class Admin < User
  # ...
end

I can change between the two classes using becomes:

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

admin = user.becomes(Admin) # returns an instance of the Admin class
admin.class # => Admin
admin.id # => 1
admin.name # => 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:

DISABLE_SPRING=true

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:

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

With squish:

<<~SQL.squish
  update posts
  set status = 'public'
  where status is null;
SQL
# "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 apple.myapp.com and orange.myapp.com 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: 'orange.myapp.com') do
  get '/thing/:id', to: 'thing#show'
end

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

constaints(host: ['orange.myapp.com', 'banana.myapp.com']) do
  get '/thing/:id', to: 'thing#show'
end

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

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

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>
</form>

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>
</form>

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]
  disable_ddl_transaction!

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

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 users.active != '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 users.active != '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
      Post.reset_column_information
      DraftPost.all.each do |draft_post|
        Post.create(content: draftPost.content)
        draft_post.delete
    end
  end

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.

Person.all.pick(:name)
# SELECT people.name FROM people LIMIT 1
# => 'John'

Person.all.pick(:name, :email)
# SELECT people.name, people.email FROM people LIMIT 1
# => ['John', 'john@mail.com']

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
  acts_as_paranoid
end

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
end

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

# app/controllers/recipes_controller.rb
class RecipesController < ApiController
  def index
    render json: RecipeSerializer.new(@current_user.recipes)
  end
end

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
  has_secure_password

  # other logic ...
end

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!'
else
  puts 'That password did not match!'
end

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
  db_namespace["drop:_unsafe"].invoke
end

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:

Rails.application.secrets.secret_key_base

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.application.credentials.secret_key_base

source

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]

  ...
end

using with_options

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

  ...
end

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
end

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

This builds the sql:

ALTER TABLE users 
ALTER COLUMN zip 
TYPE integer
USING zip::integer

Good to go!