railsでdb:migrateした後railsプロセス再起動が必要な理由

## 理由

 * モデルがカラムの情報をキャッシュして持っているから
  * 間違ってたら突っ込み希望です

## 参照サイト

* [Active Record Migrations — Ruby on Rails Guides](http://guides.rubyonrails.org/migrations.html#using-models-in-your-migrations)
* [activerecord - Model.reset_column_information does not reload columns in rails migration - Stack Overflow](http://stackoverflow.com/questions/9115347/model-reset-column-information-does-not-reload-columns-in-rails-migration)
* [reset_column_information - IwazerReport](http://www.iwazer.com/~iwazawa/diary/2007/06/reset-column-information.html)

 * => db:migrateでカラム変更してもモデルがカラム情報を保持しているのでreset_column_informationしないとうまく動かないという理解

## db:migrate後のモデル操作例

 * 以下のようなコードを用意する

```rb
# db/migrate/20140306063809_create_products.rb
class CreateProducts < ActiveRecord::Migration
  def change
    create_table :products do |t| 
      t.string :name

      t.timestamps
    end 
  end 
end

# app/models/product.rb
class Product < ActiveRecord::Base    
  attr_accessible :name
end
```

 * db:migrateする
 * Productデータを用意しておく
 * Productにhogeカラムを追加する

```rb
# db/migrate/20140306065532_add_hoge_to_product.rb
class AddHogeToProduct < ActiveRecord::Migration
  def change
    Product.first
    add_column :products, :hoge, :string
    Product.first.update_attributes! hoge: 'muga'
  end 
end

# app/models/product.rb
class Product < ActiveRecord::Base
  attr_accessible :name, :hoge
end
```
 * db:migrateする
 * Product.firstを確認する => hogeがnil
  * 最初のProduct.firstをなくした場合にはhogeはmuga => 初回参照時にDB参照かな
  * 最初のProduct.firstをなくしてinitializerでProductモデルを利用した場合はhogeがnil
  * Product.reset_column_informationをしてupdate_attributes!するとhogeはmuga
  * config.cache_classes = trueにするとdevelopmentでrails sした環境でも確認可能

## ちょっとコードを追ってみる

 * デバッグ中心でしか見てないけど、、、

 * update_attributes!の中を追ってみるとsave!でcolumnで持っていないカラムは無視してそう

```rb
# activerecord-3.2.17/lib/active_record/persistence.rb

# save!で呼び出されている
def update(attribute_names = @attributes.keys)
  attributes_with_values = arel_attributes_values(false, false, attribute_names) # ここで保持していないカラムは無視
  return 0 if attributes_with_values.empty?
  klass = self.class
  stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values) # updateのsqlクエリ生成
  klass.connection.update stmt
end 
```

```rb
# activerecord-3.2.3/lib/active_record/attribute_methods.rb
...
    def column_for_attribute(name)
      self.class.columns_hash[name.to_s] # カラム持ってないとnil
    end
...
    def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
      attrs      = {}
      klass      = self.class
      arel_table = klass.arel_table

      attribute_names.each do |name|
        if (column = column_for_attribute(name)) && (include_primary_key || !column.primary) # column_for_attributeでnilになると何もしない

          if include_readonly_attributes || !self.class.readonly_attributes.include?(name)

            value = if klass.serialized_attributes.include?(name)
                      @attributes[name].serialized_value
                    else
                      # FIXME: we need @attributes to be used consistently.
                      # If the values stored in @attributes were already type
                      # casted, this code could be simplified
                      read_attribute(name)
                    end

            attrs[arel_table[name]] = value
          end
        end
      end

      attrs
    end
```

 * カラム初アクセスの時にDB接続にいってあとは保持しているっぽい

```rb
# activerecord-3.2.17/lib/active_record/model_schema.rb 

# Returns an array of column objects for the table associated with this class.
def columns
  @columns ||= connection.schema_cache.columns[table_name].map do |col|
    col = col.dup
    col.primary = (col.name == primary_key)
    col
  end
end 
```

```rb
# activerecord-3.2.17/lib/active_record/connection_adapters/schema_cache.rb

module ActiveRecord
  module ConnectionAdapters  
    class SchemaCache
      attr_reader :primary_keys, :tables
      attr_reader :connection
 
      def initialize(conn)
        @connection = conn
        @tables     = {}
 
        @columns = Hash.new do |h, table_name|
          h[table_name] = connection.columns(table_name, "#{table_name} Columns")
        end
        ...
      end
...
      # Get the columns for a table
      def columns(table = nil)
        if table
          @columns[table]
        else
          @columns
        end
      end
...
    end
  end
end 
```