views:

105

answers:

2

I have a model with UUIDs stored in BINARY(16) field in a MySQL table. I would like to be able to transparently convert hexadecimal uuid into binary for the setter method and back when I use the getter method.

what is the 'right' way to proceed?

A: 

I looked at the source code for ActiveRecord 2.3.5 (mysql_adapter.rb). Looking at the NATIVE_DATABASE_TYPES hash makes it clear that it does not support the BINARY(16) data type:

NATIVE_DATABASE_TYPES = {
  :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY".freeze,
  :string      => { :name => "varchar", :limit => 255 },
  :text        => { :name => "text" },
  :integer     => { :name => "int", :limit => 4 },
  :float       => { :name => "float" },
  :decimal     => { :name => "decimal" },
  :datetime    => { :name => "datetime" },
  :timestamp   => { :name => "datetime" },
  :time        => { :name => "time" },
  :date        => { :name => "date" },
  :binary      => { :name => "blob" },
  :boolean     => { :name => "tinyint", :limit => 1 }
}

Also note that :binary is not what you want, because that creates a BLOB column.

If you have the inclination, I would recommend extending ActiveRecord to support the BINARY(16) type.

Update: after some searching the following blog post by Matthew Higgins seems to be promising ("While arbitrary SQL statements can be executed in migrations, an alternative is to extend the MySql adapter to support new types of columns."): http://www.strictlyuntyped.com/2008/07/mysql-lovin-part-2-adding-new-column.html

If you get this to work, I would hope that you share what you come up with. Like Matthew, I would like to see ActiveRecord have a cleaner API for adding column types.

David James
+1  A: 

You override the setter and getter:

class User < ActiveRecord::Base
  def uuid=(value)
    @uuid = write_attribute(:uuid, value.scan(/../).map {|n| n.to_i(16)}.pack("C*"))
  end

  def uuid
    @uuid ||= read_attribute(:uuid).unpack("C*").map {|n| sprintf("%02x", n)}.join
  end
end

Of course, you'd want a BINARY column, because you're sending raw bytes to the DB. A migration such as this one:

class AddUuidToUsers
  def self.up
    execute "ALTER TABLE users ADD uuid BINARY(16)"
  end
end
François Beausoleil
Overriding the getter and setter on a per-model or per-field basis is not as elegant as teaching ActiveRecord how to handle BINARY(16) generally.
David James
Using add_column with :binary does not give a BINARY type. It will give a BLOB type.
David James
I also tried out the code examples above and ran into some problems. First, I think you meant pack("C*") instead of pack(C*). But more generally, if at all possible, I would let MySQL handle the conversion to and from the BINARY(16) type rather than doing it in Ruby. According to Aaron Scrugs' comment (#11) on http://code.openark.org/blog/mysql/common-data-types-errors-compilation, "The HEX() and UNHEX() functions allow you to convert between binary data and the application-friendly hexadecimal string."
David James
How's this? You'll get your real BINARY column using the migration above. And I fixed the syntax error.
François Beausoleil