Ruby on Rails4/rake db_migrate時に行われる処理を読む
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
開始行:
[[Ruby on Rails4を読む]]
#contents
*はじめに [#yd126b3e]
結構時間が空いてそろそろ4.2出るんじゃね?って雰囲気ですが...
なお、ページタイトルが「db_migrate」となっているのはPukiW...
*Rakefile [#le058bac]
まずはエントリーポイントのRakefileです。
#code(Ruby){{
require File.expand_path('../config/application', __FILE__)
Rails.application.load_tasks
}}
railtiesに進み、load_tasksはRails::Applicationではなくス...
#code(Ruby){{
# Load Rake, railties tasks and invoke the registered hoo...
# Check <tt>Rails::Railtie.rake_tasks</tt> for more info.
def load_tasks(app=self)
require "rake"
run_tasks_blocks(app)
self
end
}}
コメントに書いてあるRailtie.rake_tasksを見てみます。(Rai...
#code(Ruby){{
def rake_tasks(&blk)
@rake_tasks ||= []
@rake_tasks << blk if blk
@rake_tasks
end
}}
何の変哲もないように見えますが、@rake_tasksを返しているの...
rake_tasks do
...
end
と書くことによりタスクの定義が、
rake_tasks
と書くことにより定義されたタスクの取得が可能です。という...
今回対象としているdb:migrate(active_record)のrake_tasks...
#code(Ruby){{
def run_tasks_blocks(app) #:nodoc:
extend Rake::DSL
each_registered_block(:rake_tasks) { |block| instance_e...
end
def each_registered_block(type, &block)
klass = self.class
while klass.respond_to?(type)
klass.public_send(type).each(&block)
klass = klass.superclass
end
end
}}
ここで先ほど書いたrake_tasksメソッドの挙動が利用されてい...
#code(Ruby){{
rake_tasks do
require "active_record/base"
namespace :db do
task :load_config do
ActiveRecord::Tasks::DatabaseTasks.database_configu...
if defined?(ENGINE_PATH) && engine = Rails::Engine....
if engine.paths['db/migrate'].existent
ActiveRecord::Tasks::DatabaseTasks.migrations_p...
end
end
end
end
load "active_record/railties/databases.rake"
end
}}
ifの中身が実行されるかとENGINE_PATHが定義されている場所を...
#code(Ruby){{
db_namespace = namespace :db do
task :load_config do
ActiveRecord::Base.configurations = ActiveRecor...
ActiveRecord::Migrator.migrations_paths = ActiveRecor...
end
(中略)
desc "Migrate the database (options: VERSION=x, VERBOSE...
task :migrate => [:environment, :load_config] do
ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? EN...
ActiveRecord::Migrator.migrate(ActiveRecord::Migrator...
ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.s...
end
db_namespace['_dump'].invoke if ActiveRecord::Base.du...
end
}}
こちらでもload_configが定義されています。同じタスクが複数...
*ActiveRecord::Migrator [#gb5deebc]
さて、というわけでdb:migrateが定義されている箇所がわかり...
#code(Ruby){{
def migrate(migrations_paths, target_version = nil, &block)
case
when target_version.nil?
up(migrations_paths, target_version, &block)
when current_version == 0 && target_version == 0
[]
when current_version > target_version
down(migrations_paths, target_version, &block)
else
up(migrations_paths, target_version, &block)
end
end
}}
バージョンは指定してないのでupに移ります。
#code(Ruby){{
def up(migrations_paths, target_version = nil)
migrations = migrations(migrations_paths)
migrations.select! { |m| yield m } if block_given?
self.new(:up, migrations, target_version).migrate
end
def migrations(paths)
paths = Array(paths)
files = Dir[*paths.map { |p| "#{p}/**/[0-9]*_*.rb" }]
migrations = files.map do |file|
version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]...
raise IllegalMigrationNameError.new(file) unless vers...
version = version.to_i
name = name.camelize
MigrationProxy.new(name, version, file, scope)
end
migrations.sort_by(&:version)
end
}}
念のため、migrations_pathsに入っているのは["<アプリのディ...
続いてインスタンスメソッドの方のmigrate。
#code(Ruby){{
def migrate
if !target && @target_version && @target_version > 0
raise UnknownMigrationVersionError.new(@target_version)
end
runnable.each do |migration|
Base.logger.info "Migrating to #{migration.name} (#{m...
begin
execute_migration_in_transaction(migration, @direct...
rescue => e
canceled_msg = use_transaction?(migration) ? "this ...
raise StandardError, "An error has occurred, #{canc...
end
end
end
def runnable
runnable = migrations[start..finish]
if up?
runnable.reject { |m| ran?(m) }
else
# skip the last migration if we're headed down, but n...
runnable.pop if target
runnable.find_all { |m| ran?(m) }
end
end
def execute_migration_in_transaction(migration, direction)
ddl_transaction(migration) do
migration.migrate(direction)
record_version_state_after_migrating(migration.version)
end
end
}}
start, finish, ran?はまあ想像がつくだろうと思うので省きま...
*MigrationProxy [#m927281c]
MIgrationProxy、一部省略してます。
#code(Ruby)){{
class MigrationProxy < Struct.new(:name, :version, :filen...
def initialize(name, version, filename, scope)
super
@migration = nil
end
delegate :migrate, :announce, :write, :disable_ddl_tran...
private
def migration
@migration ||= load_migration
end
def load_migration
require(File.expand_path(filename))
name.constantize.new(name, version)
end
end
}}
migrateが呼ばれると、migrationが参照され、load_migration...
*Migration [#c064191f]
個々のmigrationは以下のような感じです。
#code(Ruby){{
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name
t.string :email
t.timestamps
end
end
end
}}
Migrationクラスのインスタンスメソッドのmigrate。
#code(Ruby){{
def migrate(direction)
return unless respond_to?(direction)
case direction
when :up then announce "migrating"
when :down then announce "reverting"
end
time = nil
ActiveRecord::Base.connection_pool.with_connection do |...
time = Benchmark.measure do
exec_migration(conn, direction)
end
end
case direction
when :up then announce "migrated (%.4fs)" % time.real...
when :down then announce "reverted (%.4fs)" % time.real...
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
}}
個々のmigrationのchangeが呼ばれました。
さて、Migrationを見てもcreate_tableメソッドは見当たりませ...
#code(Ruby){{
def method_missing(method, *arguments, &block)
arg_list = arguments.map{ |a| a.inspect } * ', '
say_with_time "#{method}(#{arg_list})" do
unless @connection.respond_to? :revert
unless arguments.empty? || [:execute, :enable_exten...
arguments[0] = proper_table_name(arguments.first,...
arguments[1] = proper_table_name(arguments.second...
end
end
return super unless connection.respond_to?(method)
connection.send(method, *arguments, &block)
end
end
}}
*connection [#t72a65a4]
今回はRakefileを対象としているのでconnectionがどうなって...
active_record/railties.rb
#code(Ruby){{
module ActiveRecord
class Railtie < Rails::Railtie # :nodoc:
# This sets the database configuration from Configura...
# and then establishes the connection.
initializer "active_record.initialize_database" do |a...
ActiveSupport.on_load(:active_record) do
class ActiveRecord::NoDatabaseError
remove_possible_method :extend_message
def extend_message(message)
message << "Run `$ bin/rake db:create db:migr...
message
end
end
self.configurations = Rails.application.config.da...
establish_connection
end
end
}}
いまいちどのタイミングで接続が確立されているかわからなか...
connection_handling.rb
#code(Ruby){{
module ActiveRecord
module ConnectionHandling
def establish_connection(spec = nil)
spec ||= DEFAULT_ENV.call.to_sym
resolver = ConnectionAdapters::ConnectionSpecific...
spec = resolver.spec(spec)
unless respond_to?(spec.adapter_method)
raise AdapterNotFound, "database configuration sp...
end
remove_connection
connection_handler.establish_connection self, spec
end
}}
connection_specification.rb
#code(Ruby){{
module ActiveRecord
module ConnectionAdapters
class ConnectionSpecification #:nodoc:
class Resolver # :nodoc:
def spec(config)
spec = resolve(config).symbolize_keys
raise(AdapterNotSpecified, "database configurat...
path_to_adapter = "active_record/connection_ada...
begin
require path_to_adapter
rescue Gem::LoadError => e
raise Gem::LoadError, "Specified '#{spec[:ada...
rescue LoadError => e
raise LoadError, "Could not load '#{path_to_a...
end
adapter_method = "#{spec[:adapter]}_connection"
ConnectionSpecification.new(spec, adapter_method)
end
}
core.rb
#code(Ruby){{
module ActiveRecord
module Core
included do
def self.connection_handler
ActiveRecord::RuntimeRegistry.connection_handler ...
end
self.default_connection_handler = ConnectionAdapter...
}}
connection_pool.rb
#code(Ruby){{
module ActiveRecord
module ConnectionAdapters
class ConnectionHandler
def establish_connection(owner, spec)
@class_to_pool.clear
raise RuntimeError, "Anonymous class is not allow...
owner_to_pool[owner.name] = ConnectionAdapters::C...
end
}}
実はpメソッドでいろいろ調べてみるとnilが返ってきたりして...
*おわりに [#a3d2ca1c]
今回は「rake db:migrate」の動きを見てきました。ポイントと...
-Rakefileがどのように読み込まれていくか
-migration処理。クラスメソッド、インスタンスメソッド、い...
-個々のmigrationの処理。詳しくは見なかったけどデータベー...
これでデータベースが作成されサーバが起動できるようになり...
終了行:
[[Ruby on Rails4を読む]]
#contents
*はじめに [#yd126b3e]
結構時間が空いてそろそろ4.2出るんじゃね?って雰囲気ですが...
なお、ページタイトルが「db_migrate」となっているのはPukiW...
*Rakefile [#le058bac]
まずはエントリーポイントのRakefileです。
#code(Ruby){{
require File.expand_path('../config/application', __FILE__)
Rails.application.load_tasks
}}
railtiesに進み、load_tasksはRails::Applicationではなくス...
#code(Ruby){{
# Load Rake, railties tasks and invoke the registered hoo...
# Check <tt>Rails::Railtie.rake_tasks</tt> for more info.
def load_tasks(app=self)
require "rake"
run_tasks_blocks(app)
self
end
}}
コメントに書いてあるRailtie.rake_tasksを見てみます。(Rai...
#code(Ruby){{
def rake_tasks(&blk)
@rake_tasks ||= []
@rake_tasks << blk if blk
@rake_tasks
end
}}
何の変哲もないように見えますが、@rake_tasksを返しているの...
rake_tasks do
...
end
と書くことによりタスクの定義が、
rake_tasks
と書くことにより定義されたタスクの取得が可能です。という...
今回対象としているdb:migrate(active_record)のrake_tasks...
#code(Ruby){{
def run_tasks_blocks(app) #:nodoc:
extend Rake::DSL
each_registered_block(:rake_tasks) { |block| instance_e...
end
def each_registered_block(type, &block)
klass = self.class
while klass.respond_to?(type)
klass.public_send(type).each(&block)
klass = klass.superclass
end
end
}}
ここで先ほど書いたrake_tasksメソッドの挙動が利用されてい...
#code(Ruby){{
rake_tasks do
require "active_record/base"
namespace :db do
task :load_config do
ActiveRecord::Tasks::DatabaseTasks.database_configu...
if defined?(ENGINE_PATH) && engine = Rails::Engine....
if engine.paths['db/migrate'].existent
ActiveRecord::Tasks::DatabaseTasks.migrations_p...
end
end
end
end
load "active_record/railties/databases.rake"
end
}}
ifの中身が実行されるかとENGINE_PATHが定義されている場所を...
#code(Ruby){{
db_namespace = namespace :db do
task :load_config do
ActiveRecord::Base.configurations = ActiveRecor...
ActiveRecord::Migrator.migrations_paths = ActiveRecor...
end
(中略)
desc "Migrate the database (options: VERSION=x, VERBOSE...
task :migrate => [:environment, :load_config] do
ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? EN...
ActiveRecord::Migrator.migrate(ActiveRecord::Migrator...
ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.s...
end
db_namespace['_dump'].invoke if ActiveRecord::Base.du...
end
}}
こちらでもload_configが定義されています。同じタスクが複数...
*ActiveRecord::Migrator [#gb5deebc]
さて、というわけでdb:migrateが定義されている箇所がわかり...
#code(Ruby){{
def migrate(migrations_paths, target_version = nil, &block)
case
when target_version.nil?
up(migrations_paths, target_version, &block)
when current_version == 0 && target_version == 0
[]
when current_version > target_version
down(migrations_paths, target_version, &block)
else
up(migrations_paths, target_version, &block)
end
end
}}
バージョンは指定してないのでupに移ります。
#code(Ruby){{
def up(migrations_paths, target_version = nil)
migrations = migrations(migrations_paths)
migrations.select! { |m| yield m } if block_given?
self.new(:up, migrations, target_version).migrate
end
def migrations(paths)
paths = Array(paths)
files = Dir[*paths.map { |p| "#{p}/**/[0-9]*_*.rb" }]
migrations = files.map do |file|
version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]...
raise IllegalMigrationNameError.new(file) unless vers...
version = version.to_i
name = name.camelize
MigrationProxy.new(name, version, file, scope)
end
migrations.sort_by(&:version)
end
}}
念のため、migrations_pathsに入っているのは["<アプリのディ...
続いてインスタンスメソッドの方のmigrate。
#code(Ruby){{
def migrate
if !target && @target_version && @target_version > 0
raise UnknownMigrationVersionError.new(@target_version)
end
runnable.each do |migration|
Base.logger.info "Migrating to #{migration.name} (#{m...
begin
execute_migration_in_transaction(migration, @direct...
rescue => e
canceled_msg = use_transaction?(migration) ? "this ...
raise StandardError, "An error has occurred, #{canc...
end
end
end
def runnable
runnable = migrations[start..finish]
if up?
runnable.reject { |m| ran?(m) }
else
# skip the last migration if we're headed down, but n...
runnable.pop if target
runnable.find_all { |m| ran?(m) }
end
end
def execute_migration_in_transaction(migration, direction)
ddl_transaction(migration) do
migration.migrate(direction)
record_version_state_after_migrating(migration.version)
end
end
}}
start, finish, ran?はまあ想像がつくだろうと思うので省きま...
*MigrationProxy [#m927281c]
MIgrationProxy、一部省略してます。
#code(Ruby)){{
class MigrationProxy < Struct.new(:name, :version, :filen...
def initialize(name, version, filename, scope)
super
@migration = nil
end
delegate :migrate, :announce, :write, :disable_ddl_tran...
private
def migration
@migration ||= load_migration
end
def load_migration
require(File.expand_path(filename))
name.constantize.new(name, version)
end
end
}}
migrateが呼ばれると、migrationが参照され、load_migration...
*Migration [#c064191f]
個々のmigrationは以下のような感じです。
#code(Ruby){{
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name
t.string :email
t.timestamps
end
end
end
}}
Migrationクラスのインスタンスメソッドのmigrate。
#code(Ruby){{
def migrate(direction)
return unless respond_to?(direction)
case direction
when :up then announce "migrating"
when :down then announce "reverting"
end
time = nil
ActiveRecord::Base.connection_pool.with_connection do |...
time = Benchmark.measure do
exec_migration(conn, direction)
end
end
case direction
when :up then announce "migrated (%.4fs)" % time.real...
when :down then announce "reverted (%.4fs)" % time.real...
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
}}
個々のmigrationのchangeが呼ばれました。
さて、Migrationを見てもcreate_tableメソッドは見当たりませ...
#code(Ruby){{
def method_missing(method, *arguments, &block)
arg_list = arguments.map{ |a| a.inspect } * ', '
say_with_time "#{method}(#{arg_list})" do
unless @connection.respond_to? :revert
unless arguments.empty? || [:execute, :enable_exten...
arguments[0] = proper_table_name(arguments.first,...
arguments[1] = proper_table_name(arguments.second...
end
end
return super unless connection.respond_to?(method)
connection.send(method, *arguments, &block)
end
end
}}
*connection [#t72a65a4]
今回はRakefileを対象としているのでconnectionがどうなって...
active_record/railties.rb
#code(Ruby){{
module ActiveRecord
class Railtie < Rails::Railtie # :nodoc:
# This sets the database configuration from Configura...
# and then establishes the connection.
initializer "active_record.initialize_database" do |a...
ActiveSupport.on_load(:active_record) do
class ActiveRecord::NoDatabaseError
remove_possible_method :extend_message
def extend_message(message)
message << "Run `$ bin/rake db:create db:migr...
message
end
end
self.configurations = Rails.application.config.da...
establish_connection
end
end
}}
いまいちどのタイミングで接続が確立されているかわからなか...
connection_handling.rb
#code(Ruby){{
module ActiveRecord
module ConnectionHandling
def establish_connection(spec = nil)
spec ||= DEFAULT_ENV.call.to_sym
resolver = ConnectionAdapters::ConnectionSpecific...
spec = resolver.spec(spec)
unless respond_to?(spec.adapter_method)
raise AdapterNotFound, "database configuration sp...
end
remove_connection
connection_handler.establish_connection self, spec
end
}}
connection_specification.rb
#code(Ruby){{
module ActiveRecord
module ConnectionAdapters
class ConnectionSpecification #:nodoc:
class Resolver # :nodoc:
def spec(config)
spec = resolve(config).symbolize_keys
raise(AdapterNotSpecified, "database configurat...
path_to_adapter = "active_record/connection_ada...
begin
require path_to_adapter
rescue Gem::LoadError => e
raise Gem::LoadError, "Specified '#{spec[:ada...
rescue LoadError => e
raise LoadError, "Could not load '#{path_to_a...
end
adapter_method = "#{spec[:adapter]}_connection"
ConnectionSpecification.new(spec, adapter_method)
end
}
core.rb
#code(Ruby){{
module ActiveRecord
module Core
included do
def self.connection_handler
ActiveRecord::RuntimeRegistry.connection_handler ...
end
self.default_connection_handler = ConnectionAdapter...
}}
connection_pool.rb
#code(Ruby){{
module ActiveRecord
module ConnectionAdapters
class ConnectionHandler
def establish_connection(owner, spec)
@class_to_pool.clear
raise RuntimeError, "Anonymous class is not allow...
owner_to_pool[owner.name] = ConnectionAdapters::C...
end
}}
実はpメソッドでいろいろ調べてみるとnilが返ってきたりして...
*おわりに [#a3d2ca1c]
今回は「rake db:migrate」の動きを見てきました。ポイントと...
-Rakefileがどのように読み込まれていくか
-migration処理。クラスメソッド、インスタンスメソッド、い...
-個々のmigrationの処理。詳しくは見なかったけどデータベー...
これでデータベースが作成されサーバが起動できるようになり...
ページ名: