使用带有rails的postgres ENUM会产生`PG :: DatatypeMismatch`

时间:2016-06-29 22:13:24

标签: ruby-on-rails postgresql ruby-on-rails-3 ruby-on-rails-4 enums

尝试更新Postgres ENUM列的值会引发以下异常:

  

ActiveRecord :: StatementInvalid异常:PG :: DatatypeMismatch:错误:列“interesting_column”的类型为interesting_thing但表达式的类型为整数

     

第1行:更新“interesting_table”SET“interesting_column”= 0,“updated_a ...

     

提示:您需要重写或转换表达式。

InterestingTable.first.update_attributes!(normal_column: 'food')
  # => perfectly fine
InterestingTable.first.update_attributes!(interesting_column: 'foo')
  # => above exception

以下是创建表的迁移:

class CreateInterestingTables < ActiveRecord::Migration
  def up
    execute <<-SQL
      CREATE TYPE normal_thing AS ENUM ('food', 'water', 'shelter');
      CREATE TYPE interesting_thing AS ENUM ('foo', 'bar', 'baz');
    SQL

    create_table :interesting_tables do |t|
      t.column :normal_column, :normal_thing
      t.column :interesting_column, :interesting_thing
    end
  end

  def down
    drop_table :interesting_tables
    execute 'DROP TYPE normal_thing'
    execute 'DROP TYPE interesting_thing'
  end
end

1 个答案:

答案 0 :(得分:3)

问题是,当列在数据库中具有正确的类型时,活动记录将尝试将其解释为integer。您可以通过运行验证:

InterestingTable.columns
# => [#<ActiveRecord::ConnectionAdapters::PostgreSQLColumn:0x007f7567f82260
#      @coder=nil,
#      @default=nil,
#      @limit=nil,
#      @name="id",
#      @null=false,
#      @precision=nil,
#      @primary=true,
#      @scale=nil,
#      @sql_type="integer",
#      @type=:integer>,
#     #<ActiveRecord::ConnectionAdapters::PostgreSQLColumn:0x007f7568075690
#      @coder=nil,
#      @default=nil,
#      @limit=nil,
#      @name="normal_column",
#      @null=true,
#      @precision=nil,
#      @primary=false,
#      @scale=nil,
#      @sql_type="normal_thing",
#      @type=nil>,
#     #<ActiveRecord::ConnectionAdapters::PostgreSQLColumn:0x007f7568075668
#      @coder=nil,
#      @default=nil,
#      @limit=nil,
#      @name="interesting_column",
#      @null=true,
#      @precision=nil,
#      @primary=false,
#      @scale=nil,
#      @sql_type="interesting_thing",
#      @type=:integer>]

请注意第二列的类型是nil,而最后一列的类型是integer。如果您的字符串没有以数字开头,则String#to_i会返回0,因此您会收到您尝试分配0的错误。

但为什么呢?原因 - interesting_thing包含子字符串int,适配器认为它是integer。这似乎是一个长期存在的错误,直到rails 4.2之前还没有修复。 The offending method.

可能的解决方案:

  • 迁移到rails 4.2 +
  • 将您的ENUM重命名为无法匹配的内容
  • 猴子修补适配器。这是quick fix