Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails 3.1: can't write to column in same migration that adds it

I had an add_column migration that would run fine. However, after running it and firing up a console, I would find the first_name and last_name columns completely empty. I tried using save! instead and it had the same effect--no errors reported. Here's the original:

class UserAddFirstNameAndLastName < ActiveRecord::Migration
  def change
    # add column first name, last name string
    add_column :users, :first_name, :string
    add_column :users, :last_name, :string

    User.all.each do |u|
      u.first_name = 'first name'
      u.last_name = 'last name'
      u.save
    end
  end
end

I also thought this might be some class loading issue, so I inserted the line User to force the user class to reload before the loop. No dice.

When I split this up into two migrations, the desired effect was achieved. Does someone have an explanation for this? I swear I've even done this in the same project with past migrations.

Other notes: Devise for user engine, added the new columns to attr_accessible in User class before running migration.

like image 741
Eric Hu Avatar asked Jan 20 '12 00:01

Eric Hu


1 Answers

You're loading the Users class somewhere before your migration runs so User is a little confused about its own structure. The solution is to call reset_column_information after adding your column:

Resets all the cached information about columns, which will cause them to be reloaded on the next request.

The most common usage pattern for this method is probably in a migration, when just after creating a table you want to populate it with some default values

The Using Models in Your Migrations section of the Migrations Guide might be worth a look as well.

Try rolling back and using a migration like this:

def change
  # add column first name, last name string
  add_column :users, :first_name, :string
  add_column :users, :last_name, :string

  User.reset_column_information

  User.all.each do |u|
    u.first_name = 'first name'
    u.last_name = 'last name'
    u.save
  end
end

I checked this with three migrations like this:

# 1: Don't touch Model before the new columns.
def change
  add_column :models, :some_column, :string
  Model.all.each { |m| m.some_column = 'pancakes'; m.save }
end

# 2: Pull in Model before adding the new columns.
def change
  puts Model.all.count
  add_column :models, :some_column, :string
  Model.all.each { |m| m.some_column = 'pancakes'; m.save }
end

# 3: Pull in Model before adding the new columns but use reset_column_information
def change
  puts Model.all.count
  add_column :models, :some_column, :string
  Model.reset_column_information
  Model.all.each { |m| m.some_column = 'pancakes'; m.save }
end

The first one works just fine, the second one adds some_column but leaves it with NULL values, the third one also works.

I'd guess that something in your application initialization (possibly from Devise) is causing User and its schema to be loaded, then you add a column. But, apparently, User only partly knows about the new column as the u.first_name call works but something is cached inside User to prevents the attribute from being written to the database.

like image 177
mu is too short Avatar answered Nov 04 '22 19:11

mu is too short