[フレーム]
Last Updated: October 12, 2018
·
11.49K
· glebm

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

4 Responses
Add your response

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')
over 1 year ago ·

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?

over 1 year ago ·

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
over 1 year ago ·

In ActiveRecord 2.4.1 there is native support for postgres enums. Maybe a little bit limited but works.

over 1 year ago ·

AltStyle によって変換されたページ (->オリジナル) /