Postgres Enum Type in Rails 4
Rails has no support for Postgres Enum types yet, but if you need them today, here is a quick way to do it.
So you've created the enum type in the db:
CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
We should let ActiveRecord know. First alias the type to text (do this to avoid "Invalid OID" type warnings, really this will be a custom type once Rails supports these out of the box):
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::OID.alias_type 'my_enum_type', 'text'
You'll also want to patch the column parser to return the correct column type:
class ActiveRecord::ConnectionAdapters::Column
private
def simplified_type_with_my_enum_type(field_type)
if field_type == 'my_enum_type'
field_type.to_sym
else
simplified_type_without_my_enum_type(field_type)
end
end
alias_method_chain :simplified_type, :my_enum_type
end
When querying, you will also want to convert the string to your type, for example (if you want to, there is a way to make rails convert it automatically, see OID::Point from this pull request):
scope :my_enum_value_is, ->(value){
where('my_enum_value = ?::my_enum_type', value)
}
This is an extended version of an answer to this StackOverflow question, and includes stuff from the comments
Written by Gleb Mazovetskiy
Related protips
4 Responses
Worth noting that if you're going to use ENUMs you'll need a migration for them:
class CreateEnumerations < ActiveRecord::Migration
ENUM_TYPES = {
:common_sex => [
'Male', 'Female',
],
}
def self.up
ENUM_TYPES.each do |type, values|
values = "'" + values.join("', '") + "'"
sql = "CREATE TYPE #{type} AS ENUM(#{values})"
execute sql
end
end
def self.down
ENUM_TYPES.each_key do |type|
sql = "DROP TYPE #{type}"
execute sql
end
end
end
Also, a nicer core_ext/initializer for patching ActiveRecord::ConnectionAdapters::Column and adding type aliases could be put in place:
module AppName
module ActiveRecord
module ConnectionAdapters
module Column
ENUM_TYPES = [
:common_sex,
:demographic_projections_race,
:demographic_projections_education,
]
def self.included(base)
ENUM_TYPES.each do |type|
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::OID.alias_type type.to_s, 'text'
end
base.class_eval do
def simplified_type_with_custom_enum_types(type)
sym = type.to_sym
if sym.in?(ENUM_TYPES)
sym
else
simplified_type_without_custom_enum_types(type)
end
end
alias_method_chain :simplified_type, :custom_enum_types
end
end
end
end
end
end
unless ::ActiveRecord::ConnectionAdapters::Column.include?(::AppName::ActiveRecord::ConnectionAdapters::Column)
::ActiveRecord::ConnectionAdapters::Column.send(:include, ::AppName::ActiveRecord::ConnectionAdapters::Column)
end
This way everything sums up to just 2 files (or more, if you need to add more ENUMs as the project goes)
And last, but not least - typecasting the value is optional:
User.where(:sex => 'Male')
Any advices on creating migration file? I've did it this way:
create_table :messages do |t|
#...
t.column :direction, :message_direction
#...
end
And everything seems fine, but I got such comment in my schema file:
# Could not dump table "messages" because of following StandardError
# Unknown type 'message_direction' for column 'direction'
Any ideas to fix it? Did I write correct migration file?
I'm using this patch:
# PostgreSQL enums support (adapted from https://coderwall.com/p/azi3ka)
# Should be fixed in Rails >= 4.2 (https://github.com/rails/rails/pull/13244)
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapter
module OID
class Enum < Type
def type_cast(value)
value.to_s
end
end
end
def enum_types
@enum_types ||= begin
result = execute 'SELECT DISTINCT t.oid, t.typname FROM pg_type t JOIN pg_enum e ON t.oid = e.enumtypid', 'SCHEMA'
Hash[ result.map { |v| [ v['oid'], v['typname'] ] } ]
end
end
private
def initialize_type_map_with_enum_types_support
initialize_type_map_without_enum_types_support
# populate enum types
enum_types.reject { |_, name| OID.registered_type? name }.each do |oid, name|
OID::TYPE_MAP[oid.to_i] = OID::Enum.new
end
end
alias_method_chain :initialize_type_map, :enum_types_support
end
class PostgreSQLColumn
private
def simplified_type_with_enum_types(field_type)
case field_type
when *Base.connection.enum_types.values
field_type.to_sym
else
simplified_type_without_enum_types(field_type)
end
end
alias_method_chain :simplified_type, :enum_types
end
end
end
In ActiveRecord 2.4.1 there is native support for postgres enums. Maybe a little bit limited but works.