Ruby
Rails

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

More than 5 years have passed since last update.


理由


  • モデルがカラムの情報をキャッシュして持っているから


    • 間違ってたら突っ込み希望です




参照サイト


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