ActiveRecordのestablish_connectionを読む

ActiveRecord における DB との接続確立方法をきちんと理解できてなかったので、pry-byebug を使いながらコードを読み解いてみる。

Railsのversion は 4.2.0 という前提で。

establish_connection

blog.livedoor.jp

sonots さん解説の通り、Rails を起動すると establish_connection メソッドが呼び出される。

# lib/active_record/connection_handling.rb

    def establish_connection(spec = nil)
      spec     ||= DEFAULT_ENV.call.to_sym
      resolver =   ConnectionAdapters::ConnectionSpecification::Resolver.new configurations
      spec     =   resolver.spec(spec)

      unless respond_to?(spec.adapter_method)
        raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
      end

      remove_connection
      connection_handler.establish_connection self, spec
    end

binding.pry でデバッグ

適当な箇所に binding.pry を挿入して rails server するとコードを追いかけやすくてよい。

self は何?

[12] pry(ActiveRecord::Base)> self
=> ActiveRecord::Base

なぜ self が ActiveRecord::Base になるかというと、ConnectionHandling モジュールを extendしてるから。 https://github.com/rails/rails/blob/v4.2.0/activerecord/lib/active_record/base.rb#L276

spec とは?

[11] pry(ActiveRecord::Base)> p spec
:development

resolver.spec(spec) の結果は?

[1] pry(ActiveRecord::Base):1> resolver.spec(spec)
=> #<ActiveRecord::ConnectionAdapters::ConnectionSpecification:0x007fa52311ae20
 @adapter_method="mysql2_connection",
 @config={:adapter=>"mysql2", :encoding=>"utf8mb4", :database=>"development", :pool=>5, :username=>"root", :password=>nil, :socket=>"/tmp/mysql.sock"}>

connection_handler とは?

def self.connection_handler
  ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
end

def self.connection_handler=(handler)
  ActiveRecord::RuntimeRegistry.connection_handler = handler
end

self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new

https://github.com/rails/rails/blob/v4.2.0/activerecord/lib/active_record/core.rb#L100-L102

により、ActiveRecord::ConnectionAdapters::ConnectionHandler のインスタンスということになる。

そして...

# lib/active_record/connection_handling.rb

    def establish_connection(spec = nil)
      spec     ||= DEFAULT_ENV.call.to_sym
      resolver =   ConnectionAdapters::ConnectionSpecification::Resolver.new configurations
      spec     =   resolver.spec(spec)

      unless respond_to?(spec.adapter_method)
        raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
      end

      remove_connection
      connection_handler.establish_connection self, spec
    end

の最後の一行は

# lib/active_record/connection_adapters/abstract/connection_pool.rb
def establish_connection(owner, spec)
  @class_to_pool.clear
  raise RuntimeError, "Anonymous class is not allowed." unless owner.name
  owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec)
end

lib/active_record/connection_adapters/abstract/connection_pool.rb に行き着く。

実は owner_to_pool に instance を代入しているだけで接続していない。

いつ接続するのか?

[Ruby] 例えば、ActiveRecord の connection_pool を止める - sonots:blog

同じく sonots さんの解説の通り、クエリが投げられる時に connection pool から取得 or 新規接続という感じになっている。

#connection

#checkout

#acquire_connection

#checkout_new_connection

#new_connection

という流れで、

def new_connection
  Base.send(spec.adapter_method, spec.config)
end

がコネクションを貼ってる箇所になり、spec.adapter_method を send で呼び出すことになる。

[10] pry(ActiveRecord::Base)> p spec.adapter_method
=> "mysql2_connection"

まとめ

establish_connectionでは接続確立せず、実際はクエリを投げるタイミングである。

また、spec.adapter_methodには 例えば "mysql2_connection" などが格納されており、これを動的に実行することでconfig/database.yml に書かれた DB に接続する。