Search code examples
ruby-on-railsrails-migrations

Preserving Data on Rails Migration with Override on Getter/Setter


I'm doing a migration where I need to both (a) change a column's data type and (b) preserve the data in that column (ie, convert it to the new data type and keep it in the same column). The new column type requires me to override the getters and setters and this causes the migration to fail.

There is a User table that includes a field for the IP address of the user's connection: ip_addr. This is currently a string and I want to change it to integer. The up method of the migration looks like this:

def up
  add_column :users, :curip, :integer
  User.reset_column_information
  User.all.each do |u|
    u.update_attribute :curip, User.ip_str_to_int(u.ip_addr)
  end
  remove_column :users, :ip_addr
  rename_column :users, :curip, :ip_addr
end

(User.ip_str_to_int does the math to convert the IP address quads to an integer.)

I also have methods that override the getter & setter for ip_addr to call User.ip_str_to_int and the corresponding method User.ip_int_to_str. These methods look something like this:

def ip_addr
  val = read_attribute(:ip_addr)
  User.ip_int_to_str(val)
end

def ip_addr=(val)
  write_attribute(:ip_addr, User.ip_str_to_int(val))
end

By now you've probably guessed the problem. When the migration runs, it chokes, because the overridden getter/setters expect the column to contain an integer, but at the time that the migration happens, the column actually contains a string. I've tested the migration and getter/setter methods separately, and they're all fine. For my own development environment, I can simply comment out the getters/setters to run the migration, then put them back in. But this is a kludge and doesn't work for production.

Any suggestions? I know that I wouldn't have this problem if I wasn't trying to keep the same column name, but changing the column name means changing other code.

Environment: sqlite 3; Ruby 2.1.4; Rails 3.2.13.


Solution

  • You could modify the getter method to handle the case of data being a string...

    def ip_addr
      val = read_attribute(:ip_addr)
      return val if val.is_a? String
      User.ip_int_to_str(val)
    end