ActiveRecordのmigrationまわり眺めてた
ActiveRecordでいくつかのメソッドでクエリを読もうと思っていたはずが、 なんか迷子になってしまいました。まさにぼんやり勉強しています、だ。
それでmigrationファイルの生成について、
rails g migration filename をしっかり書くと、templateの方で 属性名を書いてくれそうだなーとか、
属性名等が書いてあるmigrationファイルは設定ファイルっぽいけれど、
#change
メソッドの実行みたいに元のMigrationのクラスで書いてあるから、
設定ファイルじゃなくて、ブロックを与えられたrubyのメソッドなのかなぁ、
とまで思って力つきて本題に戻る事にしたので、メモだけ投下。
rails/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
def create_migration_file set_local_assigns! validate_file_name! migration_template @migration_template, "db/migrate/#{file_name}.rb" end
set_local_assignsの適当なところだけ読み取ってみると、
def set_local_assigns! @migration_template = "migration.rb" case file_name when /^(add|remove)_.*_(?:to|from)_(.*)/ @migration_action = $1 @table_name = normalize_table_name($2) when /join_table/ if attributes.length == 2 @migration_action = 'join' @join_tables = pluralize_table_names? ? attributes.map(&:plural_name) : attributes.map(&:singular_name) set_index_names end when /^create_(.+)/ @table_name = normalize_table_name($1) @migration_template = "create_table_migration.rb" end end
$1とかは //.matchでなくて//=~などで正規表現のマッチングを行ったとき、 マッチした部分が頭の方から$1, $2として取得できるらしいので、それっぽい。
set_local_assignsの適当なところだけ読み取ってみると、
- migrationファイルを作る時のファイル名でmigrationファイルのテンプレート名を決める
- migrationファイルのテンプレートに渡すローカル変数を取得するためのメソッドを呼び出す
set_index_namesなど、indexを追加する場合は、generatorで生成してもらえそうな気配ですが、 列の追加とかは無理そう。
また、テンプレートの一例を見ると、
class <%= migration_class_name %> < ActiveRecord::Migration <%- if migration_action == 'add' -%> def change <% attributes.each do |attribute| -%> <%- if attribute.reference? -%> add_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> <%- elsif attribute.token? -%> add_column :<%= table_name %>, :<%= attribute.name %>, :string<%= attribute.inject_options %> add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>, unique: true <%- else -%> add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> <%- if attribute.has_index? -%> add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> <%- end -%> <%- end -%> <%- end -%> end <%- elsif migration_action == 'join' -%> def change create_join_table :<%= join_tables.first %>, :<%= join_tables.second %> do |t| <%- attributes.each do |attribute| -%> <%= '# ' unless attribute.has_index? -%>t.index <%= attribute.index_name %><%= attribute.inject_index_options %> <%- end -%> end end <%- else -%> def change <% attributes.each do |attribute| -%> <%- if migration_action -%> <%- if attribute.reference? -%> remove_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> <%- else -%> <%- if attribute.has_index? -%> remove_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> <%- end -%> remove_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> <%- end -%> <%- end -%> <%- end -%> end <%- end -%> end
存外ガリゴリ書いてるな! という感じです。 なんか、こういうの見るとちょっとだけ嬉しくなってしまいます。
読みにくいので、テーブルの作成の方を読むと
class <%= migration_class_name %> < ActiveRecord::Migration def change create_table :<%= table_name %> do |t| <% attributes.each do |attribute| -%> <% if attribute.password_digest? -%> t.string :password_digest<%= attribute.inject_options %> <% elsif attribute.token? -%> t.string :<%= attribute.name %><%= attribute.inject_options %> <% else -%> t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %> <% end -%> <% end -%> <% if options[:timestamps] %> t.timestamps <% end -%> end <% attributes.select(&:token?).each do |attribute| -%> add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>, unique: true <% end -%> <% attributes_with_index.each do |attribute| -%> add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> <% end -%> end end
上のmigration.rbを読んでいて、どこでローカル変数を割り当てているのかよく分からなかったけれど、 ActiveRecord::Migrationを読んでみて、どのタイミングSQL発行しているのかな、ということで、
って思ったけど、あれ、createメソッドとかがあって、 設定ファイルっぽいブロックを受け取って、 そのブロックの値を読んで実行しているっぽい?
# class FixTLMigration < ActiveRecord::Migration # def change # revert do # create_table(:horses) do |t| # t.text :content # t.datetime :remind_at # end # end # create_table(:apples) do |t| # t.string :variety # end # end # end
に対して、
def run(*migration_classes) opts = migration_classes.extract_options! dir = opts[:direction] || :up dir = (dir == :down ? :up : :down) if opts[:revert] if reverting? # If in revert and going :up, say, we want to execute :down without reverting, so revert { run(*migration_classes, direction: dir, revert: true) } else migration_classes.each do |migration_class| migration_class.new.exec_migration(@connection, dir) end end end def exec_migration(conn, direction) @connection = conn if respond_to?(:change) # こことか見てるとそんな感じがした if direction == :down revert { change } else change end else send(direction) end ensure @connection = nil end