class TrieStoreTest < Test::Unit::TestCase
  def setup
    @t = TrieStore.new
  end
 
  def test_should_insert
    @t.insert('hello', 1)
  end
 
  def test_should_search
    @t.insert('hello', :value1)
    assert_equal [ :value1 ], @t.search('hello')
  end
 
  def test_should_search_with_prefix
    @t.insert('hello', :value1)
    assert_equal [ :value1 ], @t.search('he')
  end
 
  def test_should_not_search_missing_values
    @t.insert('hello', :value)
    assert !@t.search('goodbye')
  end
 
  def test_should_return_unique_matches
    @t.insert('hello', :value1)
    @t.insert('help', :value2)
    assert_equal [ :value1 ], @t.search('hello')
  end
 
  def test_should_return_common_matches
    @t.insert('hello', :value1)
    @t.insert('help', :value2)
    assert_equal [ :value1, :value2 ], @t.search('hel')
  end
end

class TrieStore
  def initialize
    @children, @data = {}, []
  end
 
  def insert(term, value)
    return if term.empty?
 
    head = term[0, 1]
    rest = term[1..-1]
    @data |= [value]
    @children[head] ||= TrieStore.new
    @children[head].insert(rest, value)
  end
 
  def search(term)
    case term.size
      when 0 then []
      when 1 then @data
      else
        head = term[0, 1]
        rest = term[1..-1]
        if child = @children[head]
          child.search(rest)
        else
          []
        end
    end
  end
end

Props to Jeremy Voorhis for this cool and simple Hash maneuver.

class Hash
  def rewrite mapping
    inject({}) do |rewritten_hash, (original_key, value)|
      rewritten_hash[mapping.fetch(original_key, original_key)] = value
      rewritten_hash
    end
  end
end

Example usage:

h = { :human => 'squishy', :robot => 'tinny' }
h.rewrite(:human => :mammal)
# => { :mammal => 'squishy', :robot => 'tinny' }

Interestingly, this is the first time I’ve seen argument decomposition used in a truly clear and helpful way.

In a Rails Migration, specifying a column as binary generates a blob column. Blob columns, unfortunately, are allocated differently than any other MySQL column type; specifically, they aren’t supported in memory engine-backed tables.

Varbinary columns, on the other hand, are supported. In order to provide varbinary columns in a Migration, I wrote up this simple core extension.

# Specify :varbinary => true in column creation
# No support yet for altering tables to varbinary
class ActiveRecord::ConnectionAdapters::MysqlAdapter < AbstractAdapter
  def type_to_sql_with_varbinary(type, limit = nil, precision = nil, scale = nil)
    return type_to_sql_without_varbinary(type, limit, precision, scale) unless :varbinary == type.to_sym
    "varbinary(#{limit})"
  end
  alias_method_chain :type_to_sql, :varbinary
end
 
class ActiveRecord::ConnectionAdapters::TableDefinition
  def column_with_varbinary name, type, options = {}
    return column_without_varbinary name, "varbinary", options if options.delete(:varbinary)
    column_without_varbinary name, type, options
  end
  alias_method_chain :column, :varbinary
end

def foo
  return 1
ensure
  return 2
end

foo
=> 2

When trying to set up indices on a set of mysql tables, a number of annoying errors can occur that necessitate re-running a migration.

This simple hack lets you specify a :force => true on add_index or remove_index calls.

# Allows you to specify indices to add in a migration that will only be created if they do not
# already exist, or to remove indices only if they already exist
module ActiveRecord
  module ConnectionAdapters
    class MysqlAdapter
 
      def add_index_with_unless_exists table_name, column_name, options = {}
        if options.kind_of?(Hash) && options.delete(:force)
          begin
            add_index_without_unless_exists table_name, column_name, options
          rescue
            if /^Mysql::Error: Duplicate key name /.match($!.message)
              puts "Failed to create index #{table_name} #{column_name} #{options.inspect}"
            else
              raise $!
            end
          end
        else
          add_index_without_unless_exists table_name, column_name, options
        end
      end
      alias_method_chain :add_index, :unless_exists
 
      def remove_index_with_unless_doesnt_exist table_name, options = {}
        if options.kind_of?(Hash) && options.delete(:force)
          begin
            remove_index_without_unless_doesnt_exist table_name, options
          rescue
            if /^Mysql::Error: Can't DROP/.match($!.message)
              puts "Failed to drop index #{table_name} #{options.inspect}"
            else
              raise $!
            end
          end
        else
          remove_index_without_unless_doesnt_exist table_name, options
        end
      end
      alias_method_chain :remove_index, :unless_doesnt_exist
 
    end
  end
end