Migration ã®æ©ãæ¹
Rails ã§ã¯ãã¼ã¿ãã¼ã¹ã®ãã¼ãã«ãä½æããã®ã«ãdb/migrate/ ã«ãã¤ã°ã¬ã¼ã·ã§ã³ç¨ã®ãã¡ã¤ã«
# $RAILS_ROOT/db/migrate/001_create_entries.rb class CreateEntries < ActiveRecord::Migration def self.up create_table :entries do |t| t.column :title, :string t.column :body, :text end end def self.down drop_table :entries end end
ãä½ã£ããã¨ã
% rake db:migrate
ã¨ãããã¨ã§ã"entries" ã¨ããååã®ãã¼ãã«ããã¼ã¿ãã¼ã¹ã«ä½æãããããã®å é¨åä½ããã¤ãã追ãããã¦ã¿ãã
rake db:migrate
ã¾ã㯠rake ã³ãã³ãã®åãããã
rake ã¯ã©ããã Rakefile ã親ãã£ã¬ã¯ããªæ¹åã«æ¢ãã¦ããããããrake db:migrate ã¨ããã¨ãã®å®è¡éç¨ã¯æ¬¡ã®éãã
- rake db:migrate
- $RAILS_ROOT/Rakefile ã®ãã¼ã($RAILS_ROOT 㯠Rails ã¢ããªã®ãã¼ã¹ãã£ã¬ã¯ããª)
- $GEMSHOME/rails-1.2.3/lib/tasks/rails.rb ã®ãã¼ã
- 3 ã® rails.rb 㯠$GEMSHOME/rails-1.2.3/lib/tasks/*.rake, $RAILS_ROOT/lib/tasks/**/*.rake, $RAILS_ROOT/vendor/plugins/**/tasks/**/*.rake ããã¼ãã
ãã㦠db:migrate ã®å®ç¾©ã¯ãGEMSHOME/rails-1.2.3/lib/tasks/database.rake ã«ãããï¼ããã¯ä¸ã® 4 ã§ãã¼ãããããã¡ã¤ã«ã®ä¸ã¤ã§ããï¼
namespace :db do desc "Migrate the database through scripts in db/migrate. Target specific version with VERSION=x" task :migrate => :environment do ActiveRecord::Migrator.migrate("db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil) Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby end ... end
ActiveRecord::Migrator, ActiveRecord::Migration
ActiveRecord::Migrator.migrate ããã¤ã°ã¬ã¼ã·ã§ã³ã®ã³ã¢ãªå¦çé¨åã§ããã
ã³ã¼ããè¦ãåã«ãããããã¡ã½ããã®éã®å¼ã³åºãé¢ä¿ã示ãã³ã¼ã«ã°ã©ããæ²ãã¦ãããã
Migrator.migrate |--SchemaStatements#initialize_schema_information |--Migration.up |--Migrator#migrate |--Migration.migrate |--CreateEntries.up |--SchemaStatements#create_table
åãååã ãå®ä½ã®ç°ãªã migrate ã¨ããã¡ã½ãããï¼ã¤ãããã¨ã«æ³¨æã
# $GEMSHOME/activerecord-1.15.3/lib/active_record/migration.rb # (ä»¥ä¸ GEMSHOME/activerecord-1.15.3/lib/active_record ãçç¥) # Migrator.migrate class Migrator#:nodoc: class << self def migrate(migrations_path, target_version = nil) Base.connection.initialize_schema_information #(A) case when target_version.nil?, current_version < target_version up(migrations_path, target_version) #(B) when current_version > target_version down(migrations_path, target_version) when current_version == target_version return # You're on the right version end end .. end
(A)ã® initialize_schema_information ã®å®ç¾©ã¯ active_record/connection_adapters/schema_statements.rb ã«ããããã¼ã¸ã§ã³ç®¡çç¨ã®ãã¼ãã« schema_info ãå®éã«ä½ã£ã¦ãããæ¢ã«ä½ããã¦ãããåã«ç¡è¦ã
(B) rake db:migrate ã¨ããã³ãã³ãã§ã¯ãã¼ã¸ã§ã³ã®æå®ã¯ãªãã(ãã¼ã¸ã§ã³ãæå®ããã¨ãã«ã¯ãrake db:migrate VERSION=3 ã®ããã«ãã)
target_version 㯠nil ã«ãªãã®ã§ã(B) ã®è¡ãå®è¡ãããã
# migration.rb # Migrator.up def up(migrations_path, target_version = nil) self.new(:up, migrations_path, target_version).migrate end
Migrator ã®ã¤ã³ã¹ã¿ã³ã¹ãä½ãã*ã¤ã³ã¹ã¿ã³ã¹ã¡ã½ããã®* migrate() ãå®è¡ã
# migration.rb # Migrator#migrate def migrate migration_classes.each do |(version, migration_class)| Base.logger.info("Reached target version: #{@target_version}") and break if reached_target_version?(version) next if irrelevant_migration?(version) Base.logger.info "Migrating to #{migration_class} (#{version})" migration_class.migrate(@direction) #(A) set_schema_version(version) end end
migration_class 㯠CreateEntries ã®ãããªã¯ã©ã¹ãªãã¸ã§ã¯ããå
¥ã£ã¦ããããã©ããªãã®ã§ã½ã¼ã¹ã³ã¼ãã¯ç´¹ä»ããªãããmigration_classes ã¨ããã¡ã½ããã§ã¯ãdb/migrate/ ã®ä¸ã§ ([0-9]+)_([_a-z0-9]*).rb ã¨ããæ£è¦è¡¨ç¾ã«ããããããã¡ã¤ã«ããã¼ãããã½ã¼ãããä¸ã§é
åã«ãã¦è¿ãã¦ãããã¤ã¾ã 001_create_entries.rb ã¨ãããããªãã¿ã®ååã®ãã¡ã¤ã«ããã¼ããã¦ããã®ã§ããã
(A) ã«ããã¦ã¯ migration_class == CreateEntries ã§ãããCreateEntries.migrate ã¯åå¨ããªãã®ã§ããã®ã¹ã¼ãã¼ã¯ã©ã¹ã§ãã Migration ã® migrate ãå¼ã³åºãããã
# migration.rb # Migration.migrate # Execute this migration in the named direction def migrate(direction) return unless respond_to?(direction) case direction when :up then announce "migrating" when :down then announce "reverting" end result = nil time = Benchmark.measure { result = send("real_#{direction}") } #(A) case direction when :up then announce "migrated (%.4fs)" % time.real; write when :down then announce "reverted (%.4fs)" % time.real; write end result end
(A) send("real_#{direction}") ã®é¨åãããããããããä»ã®é¨åã§ã¡ã½ããã®å¥åãå®ç¾©ããã¦ããã®ã§ãreal_up => up, real_down => down ã¨èãã¦ããã°ããã
# $RAILS_ROOT/db/migrate/001_create_entries.rb # CreateEntries.up class CreateEntries < ActiveRecord::Migration def self.up create_table :entries do |t| t.column :title, :string t.column :body, :text end end ... end
create_table ãã©ãã§å®ç¾©ããã¦ãããã¨è¨ãã°ãSchemaStatements ã«ããã¦ã§ããã
(ãã¼ã¿ãã¼ã¹åºæã¢ããã¿) <---(ç¶æ¿)--- AbstractAdapter <---(mix-in)--- DatabaseStatements, SchemaStatements
ã¨ããå½¢ã§ããã¼ã¿ãã¼ã¹åºæã¢ããã¿(ãã¨ãã° MysqlAdapter) ã« SchemaStatements ã®ã¡ã½ãããåãè¾¼ã¾ãã¦ãããããããMigration 㨠ãã¼ã¿ãã¼ã¹åºæã¢ããã¿ã«ã¯ç´æ¥ã®é¢ä¿ã¯ãªãã¯ããã©ããã£ã¦ create_table ã¯å¼ã³åºããã¦ããã®ãï¼ éµã¯ Migration.method_missing ã«ããã
# migration.rb # Migration.method_missing def method_missing(method, *arguments, &block) say_with_time "#{method}(#{arguments.map { |a| a.inspect }.join(", ")})" do arguments[0] = Migrator.proper_table_name(arguments.first) unless arguments.empty? || method == :execute ActiveRecord::Base.connection.send(method, *arguments, &block) end end
Migration ã¯ã©ã¹ã§æªå®ç¾©ã®ã¯ã©ã¹ã¡ã½ããã®å¼ã³åºãã¯ã¯ãã¹ã¦ä¸ã® method_missing ã«è»¢éããããå¼æ°ãå°ã調æ´ããå¾ããã¹ã¦ ActiveRecord::Base.connection (ãã¼ã¿ãã¼ã¹åºæã¢ããã¿ã®ã¤ã³ã¹ã¿ã³ã¹ï¼ã¸ããã«è»¢éãããã
ãããã£ã¦ CreateEntries.up ã®ä¸ã§ã¯ãã¼ã¿ãã¼ã¹ã¢ããã¿ã®ä»»æã®ã¤ã³ã¹ã¿ã³ã¹ã¡ã½ãããå¼ã³åºãå¯è½ã ã(ãã¨ãã° DatabaseStatements#execute ã DatabaseStatements#select_all, SchemaStatements#create_table, SchemaStatements#add_index ãªã©ï¼
SchemaStatements#create_table
create_table ã¯å®éã«ãã¼ãã«ããã¼ã¿ãã¼ã¹ã«ä½æããã
# connection_adapters/abstract/schema_statements.rb # SchemaStatements#create_table def create_table(name, options = {}) table_definition = TableDefinition.new(self) table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false yield table_definition #(A) if options[:force] drop_table(name, options) rescue nil end create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE " create_sql << "#{name} (" create_sql << table_definition.to_sql #(B) create_sql << ") #{options[:options]}" execute create_sql end
(A) ã®é¨åãããã¤ã°ã¬ã¼ã·ã§ã³ãã¡ã¤ã«ã§ããªãã¿ã®ãã¿ã³
create_table :some_entities do |t| ... end
ã®ãããã¯å¼æ° t 㯠TableDefinition ã¤ã³ã¹ã¿ã³ã¹ã§ãããã¨ããããããããã¯ã®ä¸ã§ã¦ã¼ã¶ã¼ã«ã¹ãã¼ããå®ç¾©ãããããã table_definition.to_sql 㧠SQL æã«å¤æã(B)ã execute() ã§å®è¡ãã¦ããã¼ã¿ãã¼ã¹ã«ãã¼ãã«ãä½ãããã§ããã
以ä¸ããã§ãããã§ããã