Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails column_hash type different from actual database type

I'm building my first Rails app and I'm running into this weird problem. I'm using Postgres, which supports the ENUM type that is very useful for me. Rails doesn't support this, however, so I'm using many "executes" in my migrations. They all run successfully and seem to work. My database is just the way I want when I examine it through pgAdmin. I'm using 3 ENUM types in the users table at the moment:

gender ('female', 'male')
interested_in ('men', 'women', 'men_and_women')
relationship_status ('single', ..., 'divorced')

For the application side I'm using the classy_enum gem. Then I tried updating the user profile to check if everything was working. At first I erased the interested_in from the edit profile form because it had some different logic for it. Everything worked fine. Then I added interested_in, updated the logic for the new type, and submitted the form again. I got this error:

PG::InvalidTextRepresentation: ERROR: invalid input value for enum interested_in: "0" :
UPDATE "users" SET "interested_in" = $1, "remember_token" = $2, "updated_at" = $3 WHERE "users"."id" = 1

It's trying to save 0 to an enum field that doesn't include this option and then it obviously fails. So, the first time I saw a similar error I thought "where on earth is this 0 coming from??" but it turned out Rails was converting the value to an integer before saving it, and whenever you convert a string with non-numeric characters the result is 0. That's what I suppose is happening again. So I went to the console and typed:

2.0.0p247 :001 > User.columns_hash['interested_in'].type
 => :integer

When I run the same command for gender and relationship_status I get nil, which makes sense cause Rails doesn't understand the ENUM type:

2.0.0p247 :007 > User.columns_hash['gender'].type
 => nil

In pgAdmin I have:

enter image description here

and

enter image description here

respectively.

I've tried restarting my webserver (Pow), the postgres server, but the types still don't match. How can this be possible? How do the Rails database adapters work? What can I do to resolve this messed up situation?

like image 441
Ariel Avatar asked Dec 14 '25 16:12

Ariel


1 Answers

Well, I'm answering my own question but I won't accept it yet, because I still hope for someone with more experience to shed some light on this problem.

I had a hunch it had something to do with the name interested_in, which starts with "int". I then changed the name to is_interested_in but no luck, so I gave up on the hypothesis. After spending some hours puzzled by the problem, dropping my whole database, migrating it again, dropping again, loading it from structure.sql all with no success, I decided to give it a shot again and changed the name of the type to user_interested_in (because that doesn't even start with "i"). Again, no luck. More hours passed by and since I couldn't find anything online or come up with any other hypothesis, I checked carefully the names of the other enum types and saw that they didn't include "int" at all in any position of the string. I then changed the type name from interested_in to attracted_to and guess what? It worked.

User.columns_hash['interested_in'].type
 => nil

My guess is that there's a bug in Rails. It seems like it tries to be smart and guess the column type with some logic like:

model_column_type = :integer if db_column_type.include?("int")

But that's kinda crazy. Why doesn't it check for a full match? Doesn't make any sense to me.

EDIT

Well, that's it. Just checked Rails source and that's exactly what it does:

# rails / activerecord / lib / active_record / connection_adapters / column.rb

require 'set'

module ActiveRecord
  # :stopdoc:
  module ConnectionAdapters
    # An abstract definition of a column in a table.
    class Column
      TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].to_set
      FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set
      ...

      def simplified_type(field_type)
          case field_type
          when /int/i
            :integer
          when /float|double/i
            :float
          .
          .
          .
          when /boolean/i
            :boolean
          end
        end
    end
  end

That's a serious problem for databases that support the creation of types. Does the regex really have to be so inclusive? Makes me wonder if I should create an issue.

like image 126
Ariel Avatar answered Dec 17 '25 21:12

Ariel



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!