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
@@ -0,0 +1,162 @@
+## 理由
+
+ * モデルがカラムの情報をキャッシュして持っているから
+ * 間違ってたら突っ込み希望です
+
+## 参照サイト
+
+* [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
+```