理由
- モデルがカラムの情報をキャッシュして持っているから
- 間違ってたら突っ込み希望です
参照サイト
- Active Record Migrations — Ruby on Rails Guides
- activerecord - Model.reset_column_information does not reload columns in rails migration - Stack Overflow
-
reset_column_information - IwazerReport
- => db:migrateでカラム変更してもモデルがカラム情報を保持しているのでreset_column_informationしないとうまく動かないという理解
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