Thursday, May 17, 2007

Update a newly added column in a migration

One of the very interesting features I like about Rails is migrations. It is a version control system that keeps track of all database changes. You can easily move your database to any previous version with its schema and data.

During my last project, I have tried to create a migration that adds a column to a table and then updates that column.
def self.up
add_column :file_types, :mime_type, :string
q = FileType.find_by_name('quicktime')
q.update_attributes :mime_type => 'video/quicktime'
end
If you run that migration, the new column would be added successfully but no data would be updated. Why is that ?!

The problem is that you are trying to update the column, mime_type, immediately after adding it and before allowing the model, FileType, to detect the new changes (strange, I know, but true).

The solution, as documented, is simple. You just need to call
reset_column_information to ensure that the model has the latest column data before the update process.

Here is the code modified:
def self.up
add_column :file_types, :mime_type, :string
q = FileType.find_by_name('quicktime')
FileType.reset_column_information
q.update_attributes :mime_type => 'video/quicktime'
end
And here is the code of reset_column_information
def reset_column_information
read_methods.each { |name| undef_method(name) }
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @read_methods = @inheritance_column = nil
end
It simply resets all the cached information about columns, which will cause them to be reloaded on the next request.

Although this problem has a solution, a really worse problem should be mentioned here. In the first case, when you don't call reset_column_information, you don't get any error! The column simply doesn't update. Additionally, if you go back to the previous version and then re-run the migration, surprise, you get no problems and the column updates successfully!

I don't know if this is a reported bug, but it is a strange behavior. However, this won't prevent me from developing more and more Rails applications.

1 comment:

A.Abd-ElHaffiez said...

mmm. for optimization issues it should be:
def self.up
add_column :file_types, :mime_type, :string, :default=>'video/quicktime'
FileType.reset_column_information
end

I don't think there was a need to find_by_name the FileTypes. Also, there was no need to iterate on the filetypes as long as the default value was constant and not an evaluated function.