Installing the “Fast Require” Patch via RVM

January 7, 2012

I’ve looked up a bunch of times where to download the so called “fast require” patch for Ruby.  Every time that I install a new version, I have to look it up again.  It’s very annoying.

You can also install the patch (and others) via RVM:

rvm reinstall 1.9.3 --patch railsexpress

If you haven’t read about this (now somewhat old) patch yet, read the story here.

Tags

UUIDs and Rails find_in_batches

September 16, 2011

UUIDs (Universally unique identifiers) are really neat as IDs, and they allow you to have the ID of a model before it is even saved and guarantee that it won’t be fail insertion due to the ID being taken already. They’re also a full class type in PostgreSQL which is even more badass because it will handle the storage part, meaning you don’t have the drag on performance that would come along if you had just placed the UUID in a varchar (or equivelent).

When you try to use UUIDs with Rails, things fall apart with #find_in_batchesbecause its implementation abuses value comparisons to get a performance benefit when paging through results (source). We started using UUIDs on a few models, so I wrote a new version of #find_in_batches that can work with either type of ID (but does not support the :start option).

This also fixes #find_each because #find_each uses #find_in_batches under the covers.

I figured it’d be useful to someone eventually, so here’s the code:


in lib/clean_find_in_batches.rb

module CleanFindInBatches

  def self.included(base)
    base.class_eval do
      alias :old_find_in_batches :find_in_batches
      alias :find_in_batches :replacement_find_in_batches
    end
  end

  # Override due to implementation of regular find_in_batches
  # conflicting using UUIDs
  def replacement_find_in_batches(options = {}, &block)
    relation = self
    return old_find_in_batches(options, &block) if relation.primary_key.is_a?(Arel::Attributes::Integer)
    # Throw errors like the real thing
    if (finder_options = options.except(:batch_size)).present?
      raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order].present?
      raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit].present?
      raise 'You can\'t specify start, it\'s forced to be 0 because the ID is a string' if options.delete(:start)
      relation = apply_finder_options(finder_options)
    end
    # Compute the batch size
    batch_size = options.delete(:batch_size) || 1000
    offset = 0
    # Get the relation and keep going over it until there's nothing left
    relation = relation.except(:order).order(batch_order).limit(batch_size)
    while (results = relation.offset(offset).limit(batch_size).all).any?
      block.call results
      offset += batch_size
    end
    nil
  end

end

and in config/initializers/clean_find_in_batches.rb

ActiveRecord::Batches.send(:include, CleanFindInBatches)

Tags

Using ENUMs with Rails

February 18, 2011

There are a lot of times in our applications where a field in our database can only have one of a few values. The most common are:

  1. Something that acts like a state machine - often used for an object that goes through several states – like pending -> under_review -> accepted.
  2. The type column in single table inheritance
  3. The _type column(s) in polymorphic relationships

The most commontly used type for these columns is varchar, and a lot of projects I’ve seen don’t even make use of limits. It would be nice if we could store them in a type that didn’t require so much performance and storage overhead.

Most major databases have support for enumerable types (read: PostgreSQL,MySQL), and at Patch we’ve switched out a lot of our columns like the above for MySQL ENUMs. The best part from the Rails side, is that you don’t have to changeanything at all in your code to swap a varchar out for an ENUM. When we made the switch, we saw a nice boost in performance (numbers coming soon) – particularly on joins that involved those columns.

Complications

How?

This sample migration should give you a good idea on how to approach the move in MySQL. Note that MySQL blocks while running ALTER statements, so I combine multiple changes into single calls.

class MoveSomeColumnsToEnum < ActiveRecord::Migration

  # a map of what you want to change
  MOVES = { :workflows => [:status, :other_status], :comments => [:commentable_type] }

  # move everything to enums with the proper DISTINCTs
  def self.up
    MOVES.each do |table, columns|
      change_list = columns.map do |column|
        result = execute "select distinct(#{column}) from #{table};"
        values = []; result.each { |r| values << r[0] }
        values.map { |v| "'#{v}'" }.join(',')
      end
      change_table table do |table|
        columns.each_with_index do |column, idx|
          table.change column, "ENUM (#{change_list[idx]})"
        end
      end
    end
  end

  # move back to strings
  def self.down
    MOVES.each do |table, columns|
      change_table table do |table|
        columns.each do |column|
          table.change column, :string
        end
      end
    end
  end

end

Tags