Skip to content

Instantly share code, notes, and snippets.

@clarkdave
Last active July 18, 2019 15:37
Show Gist options
  • Save clarkdave/5936375 to your computer and use it in GitHub Desktop.
Save clarkdave/5936375 to your computer and use it in GitHub Desktop.
basic support for PostgreSQL Enums in ActiveRecord - to be placed in an initializer
##
# This provides `create_enum` and `drop_enum` methods for migrations, which creates
# a Postgres ENUM type with the provided values.
#
# It'll also dump these into the schema.rb to be loaded into another DB (e.g. rake db:test:load)
#
# In order to reference the new enums as actual types, ActiveRecord needs to know about them, so
# make sure they are all represented in the `types` array below
#
# Then you can reference them in your migrations, e.g.
#
# create_table :example do
# t.state :state
# t.location :location
# t.method :method
# end
##
types = %w(state location method)
module PgEnum
# provide the enum DDL methods
module PostgreSQLAdapter
def create_enum(name, values)
values = values.map {|v| "'#{v}'"}
execute "CREATE TYPE #{name} AS ENUM (#{values.join(', ')})"
end
def drop_enum(name)
execute "DROP TYPE #{name}"
end
end
# provide support for writing out the 'create_enum' calls in schema.rb
module SchemaDumper
extend ActiveSupport::Concern
included do
alias_method_chain :tables, :enums
end
def tables_with_enums(stream)
enums(stream)
tables_without_enums(stream)
end
private
def enums(stream)
enums = @connection.select_all("SELECT typname FROM pg_type WHERE typtype = 'e'").map{|r| r['typname']}
statements = []
enums.each do |enum|
values = @connection.select_all(
"SELECT e.enumlabel FROM pg_enum e JOIN pg_type t ON e.enumtypid = t.oid WHERE t.typname = '#{enum}'"
).map {|v| v['enumlabel'].inspect }.join(', ')
statements << " create_enum(#{enum.inspect}, [#{values}])"
end
stream.puts statements.join("\n")
stream.puts
end
end
end
class ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
include PgEnum::PostgreSQLAdapter
end
module ActiveRecord::ConnectionAdapters
include PgEnum::PostgreSQLAdapter
end
class ActiveRecord::SchemaDumper
include PgEnum::SchemaDumper
end
# make activerecord aware of our enum types
types.each do |type|
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[type.to_sym] = { name: type }
ActiveRecord::ConnectionAdapters::Column.class_eval do
define_method("simplified_type_with_#{type}") do |field_type|
if field_type == type
field_type.to_sym
else
send("simplified_type_without_#{type}", field_type)
end
end
alias_method_chain :simplified_type, type
end
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::TableDefinition.class_eval do
define_method(type) do |*args|
options = args.extract_options!
column(args[0], type, options)
end
end
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::OID.alias_type type, 'text'
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.tap do |clazz|
clazz::OID.register_type(type, clazz::OID::Identity.new)
end
end
@mothra
Copy link

mothra commented Jul 19, 2013

Is there a way to make this work in Rails 3.2?

@kernel-io
Copy link

works great (rails 4.1.4), had used this before, then lost it, finally found it again!

@aks
Copy link

aks commented May 18, 2015

I'm getting this error, after placing the above code into RAILS_APP/config/initializers/postgis.rb, under rails 4.2.1:

NameError: undefined method `simplified_type' for class `ActiveRecord::ConnectionAdapters::Column'

any pointers?

@eliduke
Copy link

eliduke commented Aug 10, 2015

I get the same error as @aks:

rake aborted!
NameError: undefined method `simplified_type' for class `ActiveRecord::ConnectionAdapters::Column'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.3/lib/active_support/core_ext/module/aliasing.rb:32:in `alias_method'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.3/lib/active_support/core_ext/module/aliasing.rb:32:in `alias_method_chain'
/Users/eliduke/web/vhosts/sandbox.api.evanta365.com/config/initializers/schema_enums.rb:96:in `block (2 levels) in <top (required)>'
/Users/eliduke/web/vhosts/sandbox.api.evanta365.com/config/initializers/schema_enums.rb:86:in `class_eval'
/Users/eliduke/web/vhosts/sandbox.api.evanta365.com/config/initializers/schema_enums.rb:86:in `block in <top (required)>'
/Users/eliduke/web/vhosts/sandbox.api.evanta365.com/config/initializers/schema_enums.rb:83:in `each'
/Users/eliduke/web/vhosts/sandbox.api.evanta365.com/config/initializers/schema_enums.rb:83:in `<top (required)>'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.3/lib/active_support/dependencies.rb:268:in `load'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.3/lib/active_support/dependencies.rb:268:in `block in load'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.3/lib/active_support/dependencies.rb:240:in `load_dependency'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.3/lib/active_support/dependencies.rb:268:in `load'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/railties-4.2.3/lib/rails/engine.rb:652:in `block in load_config_initializer'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.3/lib/active_support/notifications.rb:166:in `instrument'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/railties-4.2.3/lib/rails/engine.rb:651:in `load_config_initializer'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/railties-4.2.3/lib/rails/engine.rb:616:in `block (2 levels) in <class:Engine>'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/railties-4.2.3/lib/rails/engine.rb:615:in `each'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/railties-4.2.3/lib/rails/engine.rb:615:in `block in <class:Engine>'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/railties-4.2.3/lib/rails/initializable.rb:30:in `instance_exec'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/railties-4.2.3/lib/rails/initializable.rb:30:in `run'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/railties-4.2.3/lib/rails/initializable.rb:55:in `block in run_initializers'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/railties-4.2.3/lib/rails/initializable.rb:44:in `each'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/railties-4.2.3/lib/rails/initializable.rb:44:in `tsort_each_child'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/railties-4.2.3/lib/rails/initializable.rb:54:in `run_initializers'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/railties-4.2.3/lib/rails/application.rb:352:in `initialize!'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/railties-4.2.3/lib/rails/railtie.rb:194:in `public_send'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/railties-4.2.3/lib/rails/railtie.rb:194:in `method_missing'
/Users/eliduke/web/vhosts/sandbox.api.evanta365.com/config/environment.rb:5:in `<top (required)>'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.3/lib/active_support/dependencies.rb:274:in `require'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.3/lib/active_support/dependencies.rb:274:in `block in require'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.3/lib/active_support/dependencies.rb:240:in `load_dependency'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.3/lib/active_support/dependencies.rb:274:in `require'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/railties-4.2.3/lib/rails/application.rb:328:in `require_environment!'
/Users/eliduke/.rvm/gems/ruby-2.2.2/gems/railties-4.2.3/lib/rails/application.rb:457:in `block in run_tasks_blocks'

I'm running Rails 4.2.3 and Ruby 2.2.2.

@eliduke
Copy link

eliduke commented Aug 10, 2015

Looks like the simplified_type method was deprecated back in Rails 3.0.9:

http://apidock.com/rails/v2.3.8/ActiveRecord/ConnectionAdapters/Column/simplified_type

Unfortunately, I don't know enough about how to fix the initializer to still function correctly.

Any thoughts, @clarkdave?

@haikalhussein
Copy link

@khamusa
Copy link

khamusa commented May 11, 2018

I had the same issue regarding the simplified_type method absense. i've perfectioned the implementation for rails 4.2 and improved support for schema.rb when dumping tables with pg enum columns. I hope this saves someone time:
https://gist.github.com/khamusa/77ada9895db15dbee4e57cb715830a48

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment