Nicer printing of Rails models

I like my models to be printed nicely, to make the class of the model as well as the id and other data available, so, when they end up in a log or console, I can now exactly what it is. I’ve been doing this since before Rails 3 and since Rails projects now have an ApplicationRecord class, it’s even easier.

On my global parent for all model classes, ApplicationRecord I add this:

def to_s(extra = nil)
  if extra
    extra = ":#{extra}"
  end
  "#<#{self.class.name}:#{id}#{extra}>"
end

That makes all records automatically print as:

<ModelName:id>

For example:

<User:123>

which makes the record findable.

But also, allows the sub-classes to add a bit of extra information without having to re-specify the whole format. For example, for the User class, it might be:

  def to_s
    super(email)
  end

so that when the user gets printed, it ends up being:

<User:123:sam@example.com>

which I found helps a lot with quickly debugging issues, in production as well as development environments.

Editing Rails 6.0 credentials on Windows

Rails 6 shipped with a very nice feature to keep encrypted credentials on the repo but separate them by environment, so you can have the credentials for development, staging and production, encrypted with different keys, that you keep safe at different levels.

For example, you might give the development key to all developers, but the production keys are kept very secret and only accessible to a small set of devops people.

You edit these credentials by running:

bundle exec rails credentials:edit --environment development

for the development credentials, or

bundle exec rails credentials:edit --environment production

for production ones. You get the idea.

When you run it, if the credentials don’t exist, it generates a key. If they exist, you need to have the keys. After decrypting, it runs your default editor and on Windows, this is the error I was getting:

No $EDITOR to open file in. Assign one like this:

EDITOR="mate --wait" bin/rails credentials:edit

For editors that fork and exit immediately, it's important to pass a wait flag,
otherwise the credentials will be saved immediately with no chance to edit.

It took me a surprisingly long time to figure out how to set the editor on Windows, so, for me and others, I’m documenting it in this post:

$env:EDITOR="notepad"

After that, running the credentials:edit command works and opens Notepad. Not the best editor by far, but for this quick changes, it works. Oh, and I’m using Powershell. I haven’t run cmd in ages.

The Highlander query with Rails’ ActiveRecord

For those cases in which there can be one and only one record on the database with certain fields and I don’t just want to get the first one and silently get the wrong one. I want to make sure there’s one and only one, so, I wrote this little extension to ActiveRecord that does exactly that:

module ActiveRecordExtension
  extend ActiveSupport::Concern

  class_methods do
    def one_and_only
      records = limit(2).all.to_a
      if records.count &gt; 1
        raise "#{self} generated more than one record when expecting only one."
      else
        records.first
      end
    end

    def one_and_only!
      one_and_only.tap do |record|
        if record.nil?
          raise "#{self} didn't generate any records."
        end
      end
    end
  end
end

ActiveRecord::Base.send(:include, ActiveRecordExtension)

The first method, one_and_only, will raise an exception if there’s more than one item but it’ll return null if there aren’t any. one_and_only! will fail if there isn’t exactly one and only one record in the database.

If you don’t know why I’m calling this The Highlander query, you should go and watch Christopher Lambert’s masterpiece.

Avoiding threads of emails when developing a Rails application

Call to Buzz, like many applications I developed before, sends emails. Lot’s of emails. To avoid accidentally emailing a customer or random person I use mail_safe. It’s one of the first gems I install on a Rails project and you should too. mail_safe re-writes the to-header so you end up receiving all the emails that you sent.

Once you are receiving all these emails, there’s another problem. They are likely to have exactly the same subject, so, mail clients are likely to group them in threads, which can be annoying. To avoid that, I added this to my ApplicationMailer class and voila! all emails have a unique subject:

if Rails.env.development?
  after_action :uniq_subjects_in_development

  def uniq_subjects_in_development
    mail.subject += " #{SecureRandom.uuid}"
  end
end

Displaying Delayed::Job’s jobs in Active Admin

I’ve been using both Active Admin and Delayed::Job for years now and both have served me very well. It’s very common for me to want to display job records in the admin tool and have some extra tools around them such as:

  • the ability to mark them all for re-run
  • the ability to run one manually (not recommended for production)
  • the ability to run them all manually (also not recommended for production)
  • an easy way to delete them all
  • a good view of the data inside the job

To achieve this, over the years, my admin/delayed_job.rb grew and I took it from project to project. Today I want to share it with you in case you are doing the same:

ActiveAdmin.register Delayed::Job, as: "DelayedJob" do
  collection_action :run_all, method: :post do
    successes, failures = Delayed::Worker.new.work_off
    if failures > 0
      flash[:alert] = "#{failures} failed delayed jobs when running them."
    end
    if successes > 0
      flash[:notice] = "#{successes} delayed jobs run successfully"
    end
    if failures == 0 && successes == 0
      flash[:notice] = "Yawn... no delayed jobs run"
    end
    redirect_to admin_delayed_jobs_url
    I18n.locale = :en # Running delayed jobs can mess up the locale.
  end

  if Rails.env.development?
    collection_action :delete_all, method: :post do
      n = Delayed::Job.delete_all
      redirect_to admin_delayed_jobs_url, notice: "#{n} jobs deleted."
    end
  end

  collection_action :mark_all_for_re_run, method: :post do
    n = Delayed::Job.update_all("run_at = created_at")
    redirect_to admin_delayed_jobs_url, notice: "Marked all jobs (#{n}) for re-running."
  end

  member_action :run, method: :post do
    delayed_job = Delayed::Job.find(params[:id])
    begin
      delayed_job.invoke_job
      delayed_job.destroy
      redirect_to admin_delayed_jobs_url, notice: "1 delayed job run, apparently successfully, but who knows!"
    rescue => e
      redirect_to admin_delayed_jobs_url, alert: "Failed to run a delayed job: #{e.to_s}"
    end
    I18n.locale = :en # Running delayed jobs can mess up the locale.
  end

  action_item do
    links = link_to("Run All Delayed Jobs", run_all_admin_delayed_jobs_url, method: :post)
    links += (" " + link_to("Mark all for re-run", mark_all_for_re_run_admin_delayed_jobs_url, method: :post)).html_safe
    links += (" " + link_to("Delete All Delayed Jobs", delete_all_admin_delayed_jobs_url, method: :post)).html_safe if Rails.env.development?
    links
  end

  index do
    selectable_column
    id_column
    column "P", :priority
    column "A", :attempts
    column("Error", :last_error, sortable: :last_error) { |post| post.last_error.present? ? post.last_error.split("\n").first : "" }
    column(:created_at, sortable: :created_at) { |job| job.created_at.iso8601.gsub("T", " ") }
    column(:run_at, sortable: :run_at) { |post| post.run_at.present? ? post.run_at.iso8601.gsub("T", " ") : nil }
    column :queue
    column("Running", sortable: :locked_at) { |dj| dj.locked_at.present? ? "#{(Time.now - dj.locked_at).round(1)}s by #{dj.locked_by}" : "" }
    actions
  end

  show title: -> (dj) { "Delayed job #{dj.id}" } do |delayed_job|
    panel "Delayed job" do
      attributes_table_for delayed_job do
        row :id
        row :priority
        row :attempts
        row :queue
        row :run_at
        row :locked_at
        row :failed_at
        row :locked_by
        row :created_at
        row :updated_at
      end
    end

    panel "Handler" do
      begin
        pre delayed_job.handler
      rescue => e
        div "Couldn't render handler: #{e.message}"
      end
    end

    panel "Last error" do
      begin
        pre delayed_job.last_error
      rescue => e
        div "Couldn't render last error: #{e.message}"
      end
    end

    active_admin_comments
  end

  form do |f|
    f.inputs("Delayed job") do
      f.input :id, input_html: {readonly: true}
      f.input :priority
      f.input :attempts
      f.input :queue
      f.input :created_at, input_html: {readonly: true}, as: :string
      f.input :updated_at, input_html: {readonly: true}, as: :string
      f.input :run_at, input_html: {readonly: true}, as: :string
      f.input :locked_at, input_html: {readonly: true}, as: :string
      f.input :failed_at, input_html: {readonly: true}, as: :string
      f.input :locked_by, input_html: {readonly: true}
    end

    f.buttons
  end

  controller do
    def update
      @delayed_job = Delayed::Job.find(params[:id])
      @delayed_job.assign_attributes(params[:delayed_job], without_protection: true)
      if @delayed_job.save
        redirect_to admin_delayed_jobs_url, notice: "Delayed job #{@delayed_job} saved successfully"
      else
        render :edit
      end
      I18n.locale = :en # Running delayed jobs can mess up the locale.
    end
  end
end

Nicer warning dialogs with Rails and Bootstrap

Ruby on Rails has a very convenient way of presenting a dialog for potentially dangerous tasks that require some confirmation before proceeding. All you have to do is add

data: {confirm: "Are you sure?"}

A problem with those, though, is that most (all?) browsers make the dialogs quite ugly:

Call to Buzz - ugly dialog

For Call to Buzz, I wanted to finally have something better. Thankfully, there’s a very simple solution, as long as you are using Bootstrap: Twitter::Bootstrap::Rails::Confirm. You just add it to your project and the dialog now looks like this:

Call to Buzz - bootstrap dialog

That looks much better, but it’d be nice if it had a nice title, better matching buttons, etc. It’s easy to do by adding some data attributes to the link and the documentation for this gem recommends creating a new method to use instead of link_to when deleting something. I wasn’t very happy with this approach so I resolved it with pure JavaScript so my links remain oblivious to the fact that I’m using this gem:

$(document).ready(function () {
  $("a[data-confirm]").data({
    "confirm-fade": true,
    "confirm-title": "Call to Buzz"
  });
  $("a[data-method=delete]").data({
    "confirm-title": "Warning",
    "confirm-cancel": "Cancel",
    "confirm-cancel-class": "btn-cancel",
    "confirm-proceed": "Delete",
    "confirm-proceed-class": "btn-danger"
  });
});

And with that, the dialog now looks like this:

Call to Buzz - much better bootstrap dialog

Update: Later on I wanted to be able to define some extra parameters on a link, such as:

data: {confirm: "Are you sure you want to disconnect?", confirm_proceed: "Disconnect"}

To achieve that I re-wrote the code that dynamically adds the extra confirm parameters to look like this (this uses jQuery 3, on earlier version you’d have to do confirm-fade instead of confirmFade):

$("a[data-confirm]").each(function (_, link) {
    var $link = $(link);
    var data = $link.data();
    data = $.extend({}, {
        "confirmFade": true,
        "confirmTitle": "Call to Buzz"
    }, data);
    if ($link.data("method") == "delete") {
        data = $.extend({}, {
            "confirmTitle": "Warning",
            "confirmCancel": "Cancel",
            "confirmCancelClass": "btn-cancel",
            "confirmProceed": "Delete",
            "confirmProceedClass": "btn-danger"
        }, data);

    }
    $link.data(data);
});

Left grouping label with Simple Form and Bootstrap 3

If you are using Simple Form and Bootstrap 3, you probably initialized your app with something similar to:

rails generate simple_form:install --bootstrap

which adds a file called simple_form_bootstrap.rb to your application with many customizations that make Simple Form output nicely formatted Bootstrap 3 forms.

For Call to Buzz, an app I’m building, I had several booleans to display, like this:

Call to Buzz booleans

My problem was that I wanted a label to explain what those booleans are, something like this:

Call to Buzz booleans with label

I achieved that by creating a wrapper that’s a mix between :horizontal_boolean and horizontal_radio_and_checkboxes which I called horizontal_boolean_with_label and looks like this:

config.wrappers :horizontal_boolean_with_label, tag: &quot;div&quot;, class: &quot;form-group&quot;, error_class: &quot;has-error&quot; do |b|
  b.use :html5
  b.optional :readonly

  b.use :label, class: &quot;col-sm-3 control-label&quot;

  b.wrapper tag: &quot;div&quot;, class: &quot;col-sm-9&quot; do |wr|
    wr.wrapper tag: &quot;div&quot;, class: &quot;checkbox&quot; do |ba|
      ba.use :label_input
    end

    wr.use :error, wrap_with: {tag: &quot;span&quot;, class: &quot;help-block&quot;}
    wr.use :hint, wrap_with: {tag: &quot;p&quot;, class: &quot;help-block&quot;}
  end
end

Storing SMTP credentials in secrets.yml in a Ruby on Rails Application

Rails 4.1 introduced the concept of secrets.yml, a file in which you store all the credentials for your app, separated by environment, so for example, development can talk to Test Stripe and production to Live Stripe. Furthermore, this file is capable of picking up environment variables which allows you to divorce credentials from code. Not properly separating credentials from code recently cost Uber the leakage of 50,000 driver names and license numbers.

At Qredo we are very strict about handling credentials, including the ones for SMTP, which in Rails projects are normally stored in config/environments/development.rbconfig/environments/production.rb, etc. Trying to read Rails.application.secrets from those files doesn’t work, because they are loaded before the secrets, so, we came up with this alternative solution.

The environment files that need to use SMTP for delivering email have this common configuration:

config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
    address: "smtp.example.com",
    port: 587,
    authentication: "plain",
    enable_starttls_auto: true
}

and then, on config/initializers/email.rb we finish configuring our SMTP credentials by reading it from secrets.yml:

if ActionMailer::Base.delivery_method == :smtp
  ActionMailer::Base.smtp_settings[:domain] = Rails.application.secrets.smtp_domain
  ActionMailer::Base.smtp_settings[:user_name] = Rails.application.secrets.smtp_user_name
  ActionMailer::Base.smtp_settings[:password] = Rails.application.secrets.smtp_password
end

In config/secrets.yml you need set those credentials:

development:
  secret_key_base: 123...
  smtp_domain: example.org
  smtp_user_name: postmaster@oderq.com
  smtp_password: <%= ENV["SMTP_PASSWORD"] %>

test:
  secret_key_base: c7f3f62597b14c7287d75c239168fd3a89d3a6cb51beb909679c2609e912aaa3ca0a0aa5df2e191648baed6fe49698527a75dd265d222697576405971f478c98

staging:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
  smtp_domain: example.net
  smtp_user_name: staging_user@example.net
  password: <%= ENV["SMTP_PASSWORD"] %>

production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
  smtp_domain: example.com
  smtp_user_name: user@example.com
  smtp_password: <%= ENV["SMTP_PASSWORD"] %>

And that’s it! Enjoy bringing SMTP credentials into Rails >4.0 secrets management.

Show a devise log in or sign up forms in another page

This is an update of an old post of similar name but for a newer version of Devise and with better design decisions. The old post was for Devise 1.0.8, this one covers 4.0.0

I was trying to have a single page with both sign in and sign up forms with Devise 4.0.0 but this applies to whenever you want to show log in or registering individually or together anywhere on your site other than the views and controllers Devise creates for you.

For my task, I created a custom controller for it with a single new action as the create actions would be in the respective already existing Devise controllers. Something like this:

class Users::SessionsOrRegistrationsController < ApplicationController
  def new
  end
end

And then I created a new.html.erb (actually, new.html.haml, but I digress) that contained both log in and sign up one after the other. Something like this:


<h2>Sign up</h2>


<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
 <%= devise_error_messages! %>


<div class="field">
 <%= f.label :email %>
 <%= f.email_field :email, autofocus: true %>
 </div>



<div class="field">
 <%= f.label :password %>
 <% if @minimum_password_length %>
 <em>(<%= @minimum_password_length %> characters minimum)</em>
 <% end %>
 <%= f.password_field :password, autocomplete: "off" %>
 </div>



<div class="field">
 <%= f.label :password_confirmation %>
 <%= f.password_field :password_confirmation, autocomplete: "off" %>
 </div>



<div class="actions">
 <%= f.submit "Sign up" %>
 </div>

<% end %>

<hr/>


<h2>Log in</h2>


<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>

<div class="field">
    <%= f.label :email %>
    <%= f.email_field :email, autofocus: true %>
  </div>



<div class="field">
    <%= f.label :password %>
    <%= f.password_field :password, autocomplete: "off" %>
  </div>


  <% if devise_mapping.rememberable? -%>

<div class="field">
      <%= f.check_box :remember_me %>
      <%= f.label :remember_me %>
    </div>

  <% end -%>


<div class="actions">
    <%= f.submit "Log in" %>
  </div>

<% end %>

I actually ended up creating two _form partials and including them. In either case, when you try to render those views, you’ll get errors about some missing methods. You need to provide those as helper methods so my controller actually looks like this:

class Users::SessionsOrRegistrationsController < ApplicationController
  def new
  end

  private

  def resource_name
    :user
  end
  helper_method :resource_name

  def resource
    @resource ||= User.new
  end
  helper_method :resource

  def devise_mapping
    @devise_mapping ||= Devise.mappings[:user]
  end
  helper_method :devise_mapping

  def resource_class
    User
  end
  helper_method :resource_class
end

And now it works.

Run bundler-audit during testing

There’s a gem called bundler-audit that checks whether any of the gems in your project have open security advisors against them. A year or so ago there was an infamous month in which Rails itself got three of those. It was terrible and I think bundler-audit is a good idea. My only problem with it is having to remember to run it: it just won’t happen. I need to run it automatically and an easy way to do that is to run it as part of my tests.

Unfortunately, bundler-audit doesn’t make it easy. It’s designed for the command line and that’s it, but these days it’s easier than a year ago and I recommend everybody to add them to their integration tests. Here’s how we do it at Watu:

require "test_helper"
require "bundler/audit/database"
require "bundler/audit/scanner"

class SecurityTest < ActionDispatch::IntegrationTest
  should "not have any vulnerable gems" do
    Bundler::Audit::Database.update!
    scanner = Bundler::Audit::Scanner.new
    scanner.scan do
      raise "There are vulnerable gems in your Gemfile. Run bundle-audit check to know more"
    end
  end
end

I don’t try to show the vulnerable gems because I found those methods to not be easily reusable and I didn’t want to copy them because they look like they might change at any moment. It’s not a big problem, if something is wrong, you should run bundle-audit check anyway.