u16suzuの blog

日々学んだことのメモブログです。

今日は rake db 系のコードを読んでみた。 いつもお世話になっているコマンドなので, 内部の処理をあまり理解せずに使っていることに違和感を感じていた。 とくにいくつか,名前がにていて何が異なるのかがわからないコマンドがいくつかあった。

rake db 系のコマンドは以下で定義されている。

/activerecord/lib/activerecord/tasks/database.rake

気になったコマンドをいくつか見てみた。

rake db:migrate:reset

drop, create, migrate をまとめてやる。

desc がコメントアウトされていて -T で表示されないようになっている。 理由はわからない。

# desc 'Resets your database using your migrations for the current environment'
task :reset => ['db:drop', 'db:create', 'db:migrate']

rake db:reset

drop, reset をする。 db/schema.rb から復元する。 seeds の読み込みも行う。 (とコメントに書いてある。)

# desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.'
  task :reset => [:environment, :load_config] do
    db_namespace["drop"].invoke
    db_namespace["setup"].invoke
  end

次にrake db コマンドの実行からどのようにDB操作の処理が行われているのかを追ってみた

RailsMySQL, PostgreSQL, SQLite に対応しているので, database.rb にある設定を読み込んで 各 DB に対応する DB操作を行うクラスを選択している。 この処理は gems/activerecord-4.2.3/lib/active_record/tasks/database_tasks.rbにある register_task が行っている。

ActiveRecord::Tasks::DatabaseTasks が include された時点で, MySQL, PostgreSLQ, SQLite のタスクとキーのペアが登録される。

def register_task(pattern, task)
  @tasks ||= {}
  @tasks[pattern] = task
end

register_task(/mysql/,        ActiveRecord::Tasks::MySQLDatabaseTasks)
register_task(/postgresql/,   ActiveRecord::Tasks::PostgreSQLDatabaseTasks)
register_task(/sqlite/,       ActiveRecord::Tasks::SQLiteDatabaseTasks)

class_for_adapter メソッドが, DBアダプタから, そのDBに対する ActiveRecord::Tasks を返す。 class_for_adapter の内部処理的には, adapter 引数で指定した DBタイプが @tasks にキーとしてあるかを確認して あれば, そのキーを返すという処理をしている。 detect を使っているのは 正規表現でマッチングさせたいからだと思う。 Array#detect メソッドは Array#find のエイリアス

  def class_for_adapter(adapter)
    key = @tasks.keys.detect { |pattern| adapter[pattern] }
    unless key
      raise DatabaseNotSupported, "Rake tasks not supported by '#{adapter}' adapter"
    end
    @tasks[key]
  end

database_configuration メソッドは database.yaml を読み込むメソッドrailties-4.2.3/lib/rails/application/configuration.rb にある。

単純に yaml をパースしてオブジェクトを返すようだが, なぜ ERB.new しているのかはまだ調べ切れていない。

  # Loads and returns the entire raw configuration of database from
  # values stored in `config/database.yml`.
  def database_configuration
    path = paths["config/database"].existent.first
    yaml = Pathname.new(path) if path

    config = if yaml && yaml.exist?
      require "yaml"
      require "erb"
      YAML.load(ERB.new(yaml.read).result) || {}
    elsif ENV['DATABASE_URL']
      # Value from ENV['DATABASE_URL'] is set to default database connection
      # by Active Record.
      {}
    else
      raise "Could not load database configuration. No such file - #{paths["config/database"].instance_variable_get(:@paths)}"
    end

    config
  rescue Psych::SyntaxError => e
    raise "YAML syntax error occurred while parsing #{paths["config/database"].first}. " \
          "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
          "Error: #{e.message}"
  rescue => e
    raise e, "Cannot load `Rails.application.database_configuration`:\n#{e.message}", e.backtrace
  end