Enums in Ruby on Rails backed by PostgreSQL’s ENUM

Let me present you the way I usually create enums in Rails’ ActiveRecord models. I’ll be utilizing the capabilities of the underlying PostgreSQL database and its ENUM type.

Let’s start with an example Subscription model:

class Subscription < ApplicationRecord
  ACTIVE = 'active'.freeze
  INACTIVE = 'inactive'.freeze
  STATES = [ACTIVE, INACTIVE].freeze

  enum state: {
    active: ACTIVE,
    inactive: INACTIVE
  }

  validates :state, presence: true
end

The above will expect a string type state database field (instead of the default numeric one). Let’s create a migration for it:

class CreateSubscriptions < ActiveRecord::Migration[7.1]
  def change
    reversible do |direction|
      direction.up do
        execute <<-SQL
          CREATE TYPE subscription_state AS ENUM ('active', 'inactive');
        SQL
      end

      direction.down do
        execute <<-SQL
          DROP TYPE subscription_state;
        SQL
      end
    end

    create_table :subscriptions do |t|
      t.column :state, :subscription_state, default: 'active', null: false

      t.timestamps null: false
    end
  end
end

Notice that in the model I have intentionally left out the inclusion validation for the state field. This is because Rails will automatically raise ArgumentError if we try to assign a different value to it. As such, it is nice to automatically rescue from such situations in the ApplicationController:

class ApplicationController < ActionController::Base
  rescue_from ArgumentError, with: :bad_request
  ...

  private

  def bad_request exception
    message = exception.message

    respond_to do |format|
      format.html do
        render 'bad_request', status: :unprocessable_entity, locals: { message: }
      end

      format.json do
        render json: { status: 'ERROR', message: }, status: :unprocessable_entity
      end
    end
  end
end

Let’s add some tests. I’ll be using shoulda-matchers for some handy one-liners:

describe Subscription do
  specify ':state enum' do
    expect(described_class.new).to define_enum_for(:state)
      .with_values(active: 'active', inactive: 'inactive')
      .backed_by_column_of_type(:enum)
  end

  it { is_expected.to allow_values(:active, :inactive).for :state }

  it { is_expected.to validate_presence_of :state }

  describe ':state enum validation' do
    it 'raises ArgumentError when assigning an invalid value' do
      expect { described_class.new.state = 'canceled' }.to raise_exception ArgumentError
    end
  end
end

And an accompanying request spec for a most likely SubscriptionsController:

describe 'API subscriptions requests' do
  describe 'PATCH :update' do
    let(:subscription) { create :subscription }

    let(:params) { Hash[subscription: { state: 'invalid' }] }

    context 'when unsuccessful' do
      it 'responds with :unprocessable_entity with error details in the JSON response' do
        patch(subscriptions_path, params:)

        expect(response).to be_unprocessable

        expect(json_response).to be_a Hash
        expect(json_response['status']).to eql 'ERROR'
        expect(json_response['message']).to include 'ArgumentError'
      end
    end
  end
end

Voilà!

Ruby date is (ir)rational

Consider the following:

[DEV] main:0> Date.today
Fri, 05 Apr 2019

[DEV] main:0> (Date.today + 0.5)
Fri, 05 Apr 2019

[DEV] main:0> (Date.today + 0.5) == Date.today
false

The above was executed in a Rails application and was the reason for a quite long wtf moment. Things become more clear when I executed this:

[DEV] main:0> Date.today + 0.5 - Date.today
1/2

Turns out that the Date holds a Rational offset inside of it. This becomes more evident when you execute the above in irb:

irb(main):003:0> Date.today
=> #<date: 2019-04-05 ((2458579j,0s,0n),+0s,2299161j)>

irb(main):004:0> Date.today + 0.5
=> #<date: 2019-04-05 ((2458579j,43200s,0n),+0s,2299161j)>

LEFT OUTER JOIN in ActiveRecord

I always forget how to construct those queries in ActiveRecord, so here it goes.

Assuming we have the following structure:

class User < ActiveRecord::Base
  has_many :authentications
end

class Authentication < ActiveRecord::Base
  belongs_to :user
end

We can generate the LEFT OUTER JOIN SQL query in the following way (to see, for example, if we have dangling user references in authentications):

Authentication.joins('LEFT OUTER JOIN users ON authentications.user_id = users.id').where('users.id IS NULL').where('authentications.user_id IS NOT NULL')

Will generate the following SQL:

SELECT `authentications`.* FROM `authentications` LEFT OUTER JOIN users ON authentications.user_id = users.id WHERE (users.id IS NULL) AND (authentications.user_id IS NOT NULL)

i.e. it will select all authentications with incorrect (dangling) user_id references.

Writing NullObject in Ruby to please Rubocop gods

Lets get right to it. Here’s how one can write a NullObject in a modern, Rubocop-friendly manner:

class NullObject
  def method_missing method, *args, &block
    if respond_to? method
      nil
    else
      super
    end
  end

  def respond_to_missing? _name, _include_private = false
    true
  end
end

One of the things you should support is the fallback to super. I achieve that by the if/else block. In practice super is never reached, so this feels a little bit like a waste.

The other thing is to declare whether your object (NullObject) should respond to methods it doesn’t have. A true null object should respond to all methods, so I set respond_to_missing? to true.

Just for posterity, here’s how you could write specs for it:

describe NullObject do
  it 'returns nil for any method call' do
    null = NullObject.new

    expect(null.missing_method).to be_nil
    expect(null.some_other_missing_method(1, 2, 3)).to be_nil
  end

  it 'responds to missing methods' do
    null = NullObject.new

    expect(null.respond_to?(:missing_method)).to be true
  end
end

Additional reading about the NullObject pattern:

  • http://wiki.c2.com/?NullObject
  • http://www.virtuouscode.com/2011/05/30/null-objects-and-falsiness/

Run Rails 2.3 application using Ruby 2.1

There is a way to have your old Rails 2.3.something application running using latest Ruby from the 2.1 branch. However, it is moderately complex and requires quite a few hacks. Not everything works perfect with this setup, though. Common exceptions are performance tests and some of the generators, but I regard those as minor annoyances.

I’m using 2-3-stable branch of Rails, which means version 2.3.18 plus some additional unreleased patches on top of it and a recently released Ruby 2.1.8.

This guide assumes that you are using Bundler to manage your gems. If not, please follow this guide first.

Gemfile

gem 'rails', :github => 'rails/rails', :branch => '2-3-stable'
gem 'rake'
gem 'json'
gem 'iconv'

group :test do
  gem 'test-unit', '1.2.3'
end

And then:

$ bundle update rails rake json iconv test-unit

Rakefile

Remove the following line:

require 'rake/rdoctask'

config/boot.rb

Change the following block after rescue Gem::LoadError => load_error to look like this:

if load_error.message =~ /Could not find RubyGem rails/
  STDERR.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
  exit 1
else
  raise
end

config/deploy.rb

Update your Ruby version used for Capistrano deployments. Only if you’re using Capistrano with RVM.

set :rvm_ruby_string, '2.1.8'

config/environment.rb

Change hardcoded Rails version:

RAILS_GEM_VERSION = '2.3.18' unless defined? RAILS_GEM_VERSION

Before Rails::Initializer.run block add the following:

# Rails 2.3 and Ruby 2.0+ compatibility hack
# http://blog.lucascaton.com.br/index.php/2014/02/28/have-a-rails-2-app-you-can-run-it-on-the-newest-ruby/
if RUBY_VERSION >= '2.0.0'
  module Gem
    def self.source_index
      sources
    end

    def self.cache
      sources
    end

    SourceIndex = Specification

    class SourceList
      # If you want vendor gems, this is where to start writing code.
      def search(*args); []; end
      def each(&block); end
      include Enumerable
    end
  end
end

Additional initializers

config/initializers/active_record_callbacks_ruby2.rb:

# ActiveRecord::Callbacks compatibility fix
# http://blog.lucascaton.com.br/index.php/2014/02/28/have-a-rails-2-app-you-can-run-it-on-the-newest-ruby/

module ActiveRecord
  module Callbacks

    private

    def callback(method)
      result = run_callbacks(method) { |result, object| false == result }

      # The difference is here, the respond_to must check protected methods.
      if result != false && respond_to_without_attributes?(method, true)
 result = send(method)
      end

      notify(method)

      return result
    end
  end
end

config/initializers/backport_3937.rb:

# Encoding issues with Ruby 2+
# backport #3937 to Rails 2.3.8
# https://github.com/rails/rails/pull/3937/files

class ERB
  module Util
    def html_escape(s)
      s = s.to_s
      if s.html_safe?
        s
      else
        silence_warnings { s.gsub(/[&"'><]/n) { |special| HTML_ESCAPE[special] }.html_safe }
      end
    end
  end
end

config/initializers/i18n_ruby2.rb:

# Patch to make i18n work with Ruby 2+
# http://blog.lucascaton.com.br/index.php/2014/02/28/have-a-rails-2-app-you-can-run-it-on-the-newest-ruby/

module I18n
  module Backend
    module Base
      def load_file(filename)
        type = File.extname(filename).tr('.', '').downcase

        raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}", true)

        data = send(:"load_#{type}", filename) # TODO raise a meaningful exception if this does not yield a Hash

        data.each { |locale, d| store_translations(locale, d) }
      end
    end
  end
end

config/initializers/rails_generators.rb:

# This is a very important monkey patch to make Rails 2.3.18 to work with Ruby 2+
# If you're thinking to remove it, really, don't, unless you know what you're doing.
# http://blog.lucascaton.com.br/index.php/2014/02/28/have-a-rails-2-app-you-can-run-it-on-the-newest-ruby/

if Rails::VERSION::MAJOR == 2 && RUBY_VERSION >= '2.0.0'
  require 'rails_generator'
  require 'rails_generator/scripts/generate'

  Rails::Generator::Commands::Create.class_eval do
    def template(relative_source, relative_destination, template_options = {})
      file(relative_source, relative_destination, template_options) do |file|
        # Evaluate any assignments in a temporary, throwaway binding
        vars = template_options[:assigns] || {}
        b = template_options[:binding] || binding
        # this no longer works, eval throws "undefined local variable or method `vars'"
        # vars.each { |k, v| eval "#{k} = vars[:#{k}] || vars['#{k}']", b }
        vars.each { |k, v| b.local_variable_set(:"#{k}", v) }

        # Render the source file with the temporary binding
        ERB.new(file.read, nil, '-').result(b)
      end
    end
  end
end

config/initializers/ruby2.rb:

# This is a very important monkey patch to make Rails 2.3.18 to work with Ruby 2+
# If you're thinking to remove it, really, don't, unless you know what you're doing.
# http://blog.lucascaton.com.br/index.php/2014/02/28/have-a-rails-2-app-you-can-run-it-on-the-newest-ruby/

if Rails::VERSION::MAJOR == 2 && RUBY_VERSION >= '2.0.0'
  module ActiveRecord
    module Associations
      class AssociationProxy
        def send(method, *args)
          if proxy_respond_to?(method, true)
            super
          else
            load_target
            @target.send(method, *args)
          end
        end
      end
    end
  end
end

config/initializers/string_encodings.rb:

# Set default encoding for everything coming in and out of the app
# TODO this could/should be removed when upgrading to Rails 3+
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8

config/initializers/utf8_params.rb

# convert all params into UTF-8 (from ASCII-8BIT)
# http://jasoncodes.com/posts/ruby19-rails2-encodings

raise "Check if this is still needed on " + Rails.version unless Rails.version == '2.3.18'

class ActionController::Base
  def force_utf8_params
    traverse = lambda do |object, block|
      if object.kind_of?(Hash)
        object.each_value { |o| traverse.call(o, block) }
      elsif object.kind_of?(Array)
        object.each { |o| traverse.call(o, block) }
      else
        block.call(object)
      end
      object
    end
    force_encoding = lambda do |o|
      o.force_encoding(Encoding::UTF_8) if o.respond_to?(:force_encoding)
    end
    traverse.call(params, force_encoding)
  end

  before_filter :force_utf8_params
end

References:

Installing Ruby 1.8.7-head for chruby using ruby-build on OS X 10.11 (El Capitan)

Unfortunately ruby-install won’t let you install Ruby version 1.8.7 any more. Here’s how to install it using ruby-build so that it could still be used by chruby:

brew install ruby-build
brew install openssl libyaml libffi
brew install apple-gcc42
brew install openssl098
  # dependencies

mkdir -p ~/.rubies
  # if doesn't exist

brew link openssl098 --force
  # 1.8.7 requires OpenSSL 0.9.8 (or lower)

ruby-build 1.8.7-p375 ~/.rubies/ruby-1.8.7
  # compile 1.8.7-head (p375 is the same as HEAD)

brew unlink openssl098
  # revert symlinking

chruby 1.8.7
  # switch to 1.8.7

Chruby with Phusion Passenger

This guide will tell you how to use chruby together with Phusion Passenger.

What we want to achieve is automatic Ruby version switching in Passenger based on the Ruby used by the project.

I’m using zsh, but with small modifications it should also work with bash or any other shell type.

Requirements:

  • latest chruby
  • latest Passenger
  • .ruby-version file present in each app/project

The gist of it is that you need a chruby wrapper script, which will be executed separately for each project by Passenger. Place it in ~/bin or at any other place where you keep your local binaries:

#!/bin/zsh

# Wrapper for chruby to work with Phusion Passenger
#
# Based on:
#   https://github.com/postmodern/chruby/issues/258
#   https://github.com/phusion/passenger/issues/1205

source /usr/local/share/chruby/chruby.sh
source /usr/local/share/chruby/auto.sh

chruby_auto

# original call
exec "ruby" "$@"

After that you need to configure Passenger to make use of this script. I’m on OS X, so the file I’m editing is at /etc/apache2/other/passenger.conf:

# chruby wrapper for Passenger use
PassengerDefaultRuby /Users/<your-username>/bin/chruby-wrapper

# Passenger must read ENV variables for the chruby-wrapper script to work
PassengerLoadShellEnvvars on

# Run Passenger application instance as your current user
PassengerUserSwitching on
PassengerDefaultUser

That’s it! Good luck!

References:

Testing database transactions explicitly with RSpec

TL;DR; you cannot do it reliably with RSpec.

The long story goes like this. Lets say you have a code executing an AR rollback when something fails:

def call
  Model.transaction do
    update_reason

    unless send_notification
      raise ActiveRecord::Rollback
    end
  end
end

This update_reason is a block of code, which does some database operation, like an INSERT or UPDATE:

def update_reason
  object.update reason: reason
end

And send_notification is just some external API call.

So when you write a spec for this code, you might want to write something like this:

describe '#call' do
  it 'does not update the reason when sending the notification fails' do
    allow(object).to receive(:send_notification).and_return false
    
    expect {
      object.call
    }.not_to change(object, :reason)  
end

And, surprise, surprise, the above spec will fail! The reason will change on the object, even though the logic says it should not.

Why is that? This is because normally you have your whole example spec wrapped in a transaction and rolled back after the example has been run. Since your code opens up a new, nested transaction internally (with the #call method: Model.transaction do). This messes things up and now the rollback in the nested transaction does not really roll back anything. Adding require_new: true doesn’t help. Disabling transaction just for this one spec does not work either. Unfortunately.

Something like this works, but it’s not ideal:

expect {
  object.call
}.to raise_exception ActiveRecord::Rollback 

Additional reading:

* How to test that a certain function uses a transaction in Rails

A deep_reject method for hashes in Ruby

Recently, while adding missing functionality for the i18n-js gem I’ve stumbled into a problem. I needed to have a method to “deep reject” keys in the hash. There are some examples in the wild doing that, but they all solve this problem by adding a new method to the Hash class. I wanted a generic method, which would take the hash as an argument.

After some head scratching, tinkering and tweaking, I’ve come up with a correct solution (at least I think it’s correct). Here it is:

def self.deep_reject(hash, &block)                                                   
  hash.each_with_object({}) do |(k, v), memo|                                        
    unless block.call(k, v)                                                          
      if v.is_a?(Hash)                                                               
        memo[k] = deep_reject(v, &block)                                             
      else                                                                           
        memo[k] = v                                                                  
      end                                                                            
    end                                                                              
  end                                                                                
end 

You use it like this:

hash = {:a => {:b => 1, :c => 2}}

result = deep_reject(hash) { |k, v| k == :b }

result # => {:a => {:c => 2}}

Enabling I18n locale fallbacks in Rails

This guide is valid for i18n version 0.7.0+ and Rails 4.1+

Strangely enough enabling custom locale fallbacks is harder than it should be. Here’s what you need to enable custom locale fallbacks with i18n gem.

First, you need to set config.i18n.fallbacks = true for all environments in a Rails application (config/environments/*.rb).

Then you need to have this in your config/application.rb:

# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
config.i18n.default_locale = :en

# Enforce available locales
config.i18n.enforce_available_locales = true

# Custom I18n fallbacks
config.after_initialize do
  I18n.fallbacks = I18n::Locale::Fallbacks.new(at: :"de-DE", ch: :"de-DE", gb: :"en-US")
end

The above will enable custom fallbacks from at and ch locales to the German language and from gb locale to English. The enforce_available_locales bit is optional.

If you also use i18n-js to have your translated phrases available in javascript, here’s the exemplary fallbacks snippet you need to put inside your javascript code:

I18n.locales["at"] = ["de", "en"]
I18n.locales["ch"] = ["de", "en"]
I18n.locales["gb"] = ["en"]