From 6734bc98e7a90dd7c4dce47750721a0d19170d83 Mon Sep 17 00:00:00 2001
From: Mike Howard <mike@clove.com>
Date: Sun, 24 Jul 2011 16:09:38 -0600
Subject: Updated migrations.textile to clarify Using Models in Your Migrations

Rewrote entire section including example code, error message, and work
around.
---
 railties/guides/source/migrations.textile | 103 ++++++++++++++++++++++++------
 1 file changed, 85 insertions(+), 18 deletions(-)

(limited to 'railties/guides/source/migrations.textile')

diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile
index c88e3cc338..e51ee0f535 100644
--- a/railties/guides/source/migrations.textile
+++ b/railties/guides/source/migrations.textile
@@ -477,7 +477,7 @@ Several methods are provided that allow you to control all this:
 
 For example, this migration
 
-<ruby>
+<pre>
 class CreateProducts < ActiveRecord::Migration
   def change
     suppress_messages do
@@ -496,7 +496,7 @@ class CreateProducts < ActiveRecord::Migration
     end
   end
 end
-</ruby>
+</pre>
 
 generates the following output
 
@@ -514,40 +514,107 @@ If you just want Active Record to shut up then running +rake db:migrate VERBOSE=
 
 h3. Using Models in Your Migrations
 
-When creating or updating data in a migration it is often tempting to use one of your models. After all they exist to provide easy access to the underlying data. This can be done but some caution should be observed.
+When creating or updating data in a migration it is often tempting to use one of your models. After all they exist to provide easy access to the underlying data. This can be done, but some caution should be observed.
 
-Consider for example a migration that uses the +Product+ model to update a row in the corresponding table. Alice later updates the +Product+ model, adding a new column and a validation on it. Bob comes back from holiday, updates the source and runs outstanding migrations with +rake db:migrate+, including the one that used the +Product+ model. When the migration runs the source is up to date and so the +Product+ model has the validation added by Alice. The database however is still old and so does not have that column and an error ensues because that validation is on a column that does not yet exist.
+For example, problems occur when the model uses database columns which are (1) not currently in the database and (2) will be created by this or a subsequent migration.
 
-Frequently I just want to update rows in the database without writing out the SQL by hand: I'm not using anything specific to the model. One pattern for this is to define a copy of the model inside the migration itself, for example:
+Consider this example, where Alice and Bob are working on the same code base which contains a +Product+ model:
 
-<ruby>
-class AddPartNumberToProducts < ActiveRecord::Migration
-  class Product < ActiveRecord::Base
+Bob goes on vacation.
+
+Alice creates a migration for the +products+ table which adds a new column and initializes it.
+She also adds a validation to the Product model for the new column.
+
+<pre>
+# db/migrate/20100513121110_add_flag_to_product.rb
+
+class AddFlagToProduct < ActiveRecord::Migration
+  def change
+    add_column :products, :flag, :int
+    Product.all.each { |f| f.update_attributes!(:flag => 'false') }
   end
+end
+</pre>
+
+<pre>
+# app/model/product.rb
+
+class Product < ActiveRecord::Base
+  validates_presence_of :flag
+end
+</pre>
+
+Alice adds a second migration which adds and initializes another column to the +products+ table and also adds a validation to the Product model for the new column.
 
+<pre>
+# db/migrate/20100515121110_add_fuzz_to_product.rb
+
+class AddFuzzToProduct < ActiveRecord::Migration
   def change
-    ...
+    add_column :products, :fuzz, :string
+    Product.all.each { |f| f.update_attributes! :fuzz => 'fuzzy' }
   end
 end
-</ruby>
-The migration has its own minimal copy of the +Product+ model and no longer cares about the +Product+ model defined in the application.
+</pre>
 
-h4. Dealing with Changing Models
+<pre>
+# app/model/product.rb
 
-For performance reasons information about the columns a model has is cached. For example if you add a column to a table and then try and use the corresponding model to insert a new row it may try to use the old column information. You can force Active Record to re-read the column information with the +reset_column_information+ method, for example
+class Product < ActiveRecord::Base
+  validates_presence_of :flag
+  validates_presence_of :fuzz
+end
+</pre>
 
-<ruby>
-class AddPartNumberToProducts < ActiveRecord::Migration
+Both migrations work for Alice.
+
+Bob comes back from vacation and:
+
+# updates the source - which contains both migrations and the latests version of the Product model.
+# runs outstanding migrations with +rake db:migrate+, which includes the one that updates the +Product+ model.
+
+The migration crashes because when the model attempts to save, it tries to validate the second added column, which is not in the database when the _first_ migration runs.
+
+<pre>
+rake aborted!
+An error has occurred, this and all later migrations canceled:
+
+undefined method `fuzz' for #<Product:0x000001049b14a0>
+</pre>
+
+A fix for this is to create a local model within the migration. This keeps rails from running the validations, so that the migrations run to completion.
+
+When using a faux model, it's a good idea to call +Product.reset_column_information+ to refresh the ActiveRecord cache for the Product model prior to updating data in the database.
+
+If Alice had done this instead, there would have been no problem:
+
+<pre>
+# db/migrate/20100513121110_add_flag_to_product.rb
+
+class AddFlagToProduct < ActiveRecord::Migration
   class Product < ActiveRecord::Base
   end
+  def change
+    add_column :products, :flag, :int
+    Product.reset_column_information  
+    Product.all.each { |f| f.update_attributes!(:flag => false) }
+  end
+end
+</pre>
 
+<pre>
+# db/migrate/20100515121110_add_fuzz_to_product.rb
+
+class AddFuzzToProduct < ActiveRecord::Migration
+  class Product < ActiveRecord::Base
+  end
   def change
-    add_column :product, :part_number, :string
+    add_column :products, :fuzz, :string
     Product.reset_column_information
-    ...
+    Product.all.each { |f| f.update_attributes! :fuzz => 'fuzzy' }
   end
 end
-</ruby>
+</pre>
 
 
 h3. Schema Dumping and You
-- 
cgit v1.2.3

