You are currently browsing the See John Code posts tagged: rails


Installing the “Fast Require” Patch via RVM

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.

UUIDs and Rails find_in_batches

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)

Using ENUMs with Rails

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

  • It may make your life a bit harder if you’re switching databases, but worst case you can just not run the migration in databases that don’t support it.
  • If you use ENUMs in a field, when its used to represent a new type, or mixed into new models – you need to modify the ENUM in a migration to contain the new value. You need to be particularly careful in your test environment, since schema.rb will load these ENUMs as strings and thus they won’t fail on trying to insert a value not in the ENUM.
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