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.

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:

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

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"]

Testing CSRF protection in Rails

Ever wanted to test your CSRF protection in a Rails app? For example, in a situation when you have a custom “remember me” cookie set and you need to overwrite Rails’ handle_unverified_request to clear it so it does not open a big security hole in your app? I know I did and it took me a while to find out how to do that, so I figured it would be good to write about it.

Here’s how to do it (in Test::Unit, but it’s the same for RSpec):

setup do                                                           
  # Enable CSRF protection in this test                            
  ActionController::Base.allow_forgery_protection = true           
end                                                                
                                                                   
teardown do                                                        
  # Disable CSRF protection for all other tests                    
  ActionController::Base.allow_forgery_protection = false          
end                                                                

Adding the above will make it so that the authenticity_token is added to each generated <form> element and will be required to be sent with each non GET request.

Bug of the day

Completely bad code follows, beware.

Silent error in Ruby 1.8.7:

x = [:a, :b]
=> [:a, :b]

x.slice!(:a)
=> nil

x
=> [:a, :b]

Explicit error (resulting in a failing test) in Ruby 1.9.2:

x = [:a, :b]
=> [:a, :b]

x.slice!(:a)
TypeError: can't convert Symbol into Integer

Just yet another incompatibility, but for the better!

Run guard-jasmine-headless-webkit without X server

You write specs for your javascript, right? If not, you really should.

jasmine-headless-webkit really helps with that. guard-jasmine-headless-webkit makes it all even more enjoyable, although there’s one caveat – it’s not so easy to set it all up.

There is a great guide for that, but it lacks some important details on running guard-jasmine-headless-webkit without graphical interface (X server).

Assuming you already have Xvfb installed, execute this command to run Xvfb in the background:

Xvfb :0 -screen 0 1024x768x24 > /dev/null 2>&1 &

And then you need to setup the DISPLAY shell variable in order for guard-jasmine-headless-webkit to automatically connect to our virtual frame buffer. Here’s the excerpt from my .bash_profile (it first checks if there is Xvfb running on display :0 and only then sets the DISPLAY variable):

xdpyinfo -display :0 &>/dev/null && export DISPLAY=:0

hookup is the shit!

This is simply amazing:

$ g pull
Already up-to-date.

$ g co edge
Switched to branch 'edge'
==  AddFileTypeToSong: migrating ==============================================
-- add_column(:songs, :file_type, :string)
   -> 0.0048s
==  AddFileTypeToSong: migrated (0.1380s) =====================================

$ g rebase master
Current branch edge is up to date.

$ g co master
Switched to branch 'master'
==  AddFileTypeToSong: reverting ==============================================
-- remove_column(:songs, :file_type)
   -> 0.1674s
==  AddFileTypeToSong: reverted (0.1679s) =========================

Oh, and it will bundle automatically for you as well, so don’t hesitate and hookup your Rails project!