Safe data migrations
Something ago I wrote about the problems which arise when using models in your Rails migrations. Meanwhile I developed a really simple solution to this problem and today I wrapped it up in to a plugin called SafeDataMigrations.
How to use it? Install it in your Rails application:
ruby script/plugin installhttp://svn.remvee.net/plugins/safe_data_migrations
and apply it in your migration:
class DowncaseUserNames < ActiveRecord::Migration models :user def self.up User.find(:all).each do |user| user.update_attributes(:names, user.name.downcase) end end
Look at the README file for a more elaborate example.
So how does it work? It simply undefines the models your referring to and redefines an empty ActiveRecord class;
Object.send :remove_const, :User rescue nil class User < ActiveRecord::Base; end
Now you are sure to have a User
model available in your migration without any validations which may make data manipulations impossible. The undefining of an already available model also ensures you don’t need to use ActiveRecord::Base.reset_column_information
before updating new fields, unless you use your model before altering the table of course.
Update: As coderrr points out you don’t need to clobber the global scope model class because a nested model works fine too. I wrongly assumed introducing a new model class in side (!) a migration class would only reopen my original model class and keep validations intact. To illustrate:
class User; def top; end; end class Migration class User; def nested; end; end def self.go p User.instance_methods - Object.instance_methods end end Migration.go
yields ["nested"]
and not ["top", "nested"]
as I suspected. Apparently I was bitten too hard by a problem which arose when I used an original model class to even try the above. I’ll pull the plugin because it’s pointless.. Bad me, thanks coderrr!
Nice (and useful!) abstraction applied in our last project (where Remco came up with this when we were dealing with lots of data migrations).
I’m sure this little piece of code has — and, in the future, will — save me a lot of trouble. Thanks!
Lol, apparently your blog supports some sort of formatting. My stylish double-dash applied a strike-through to my text…
I don’t think you need to remove and redefine the class in the global scope. You can instead just define the class inside of your migration class:
The benefit of this is that the original model class is unaffected. Was there some case you found where this did not work?
http://coderrr.wordpress.com
Digging through commit logs I noticed I didn’t even try a nested class assuming I would just reopen the original. Actually I was so freaked by the problems my migration caused I even moved to using SQL for a while. How embarrassing.. Thanks for the heads up!