1. mechamogera

    Posted

    mechamogera
Changes in title
+railsでdb:migrateした後railsプロセス再起動が必要な理由
Changes in tags
+Rails
3.2.17
+Ruby
1.9.3p448
Changes in body
Source | HTML | Preview

理由

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

参照サイト

db:migrate後のモデル操作例

  • 以下のようなコードを用意する
# 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カラムを追加する
# 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で持っていないカラムは無視してそう

# 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 
# 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接続にいってあとは保持しているっぽい
# 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 
# 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