深入分析rails数据库连接流程

 
第一部分:
Method 1)
def  self.establish_connection(spec  =  nil)
     case spec                                                                        
# 1
        when nil
          
raise  AdapterNotSpecified unless defined? RAILS_ENV
          establish_connection(RAILS_ENV)
        when ConnectionSpecification                                        
# 2
          clear_active_connection_name
          @active_connection_name 
=  name
          @@defined_connections[name] 
=  spec
        when Symbol, String                                                     
# 3
          p configurations
          
if  configuration  =  configurations[spec.to_s]
            establish_connection(configuration)
          
else
            
raise  AdapterNotSpecified,  " #{spec} database is not configured "
          end
        
else                                                                                 # 4
            spec  =  spec.symbolize_keys
         unless spec.key?(:adapter) then 
raise  AdapterNotSpecified,  " database configuration does not specify adapter "  end
          adapter_method 
=   " #{spec[:adapter]}_connection "
          unless respond_to?(adapter_method) then 
raise  AdapterNotFound,  " database configuration specifies nonexistent #{spec[:adapter]} adapter "  end
          remove_connection
          establish_connection(ConnectionSpecification.new(spec, adapter_method))
      end
    end
如果不带参数调用 establish_connection,进入#1,rails会给一个默认参数,那就是你得rails环境 (production, development, test),这是进入#2,rails会根据你得参数在databse.yml下找到对应的配置,此时是一个 hash对象,进入#3, 首先查看你得hash中key为:adpater的item,不存在就抛错,如果存在(本例为mysql),则继续看是否存在mysql_connection方法,不存在就抛错,你可以在#{activerecordhome}/lib/active_record/connection_adapters,看到各种数据库驱动接口文件,每个文件重新打开了ActionRecode:Base类,并定义了数据库类型_connection的方法,比如mysql_adapter.rb含有mysql_connection , db2_adapter.rb含有db2_adapter.rb等等,如果存在这样的方法,说明你得配置文件正确,于是开始调用remove_connection(见Method2),然后 实例化一个ConnectionSpecification作为参数进入#2调用clear_active_connection_name(见Method3),然后将类的实例变量@active_connection_name复值为类名,再将一个key为类名,value为上面那个ConnectionSpecification实例变量的item添加到类变量 @@defined_connections(一个hash)
 
 
Method 2)
  def  self.remove_connection(klass = self)
      spec 
=  @@defined_connections[klass.name]
      konn 
=  active_connections[klass.name]
      @@defined_connections.delete_if { 
| key, value |  value  ==  spec }
      active_connections.delete_if { 
| key, value |  value  ==  konn }
      konn.disconnect! 
if  konn
      spec.config 
if  spec
    end
 
     删除@@defined_connections hash中value即配置相同的item
     删除@@active_connections hash中value即数据库连接相同的item,并关闭数据库连接
    返回配置hash
 
Method 3)
    
def  clear_active_connection_name  # :nodoc:
        @active_connection_name  =  nil
        subclasses.each { 
| klass |  klass.clear_active_connection_name }
      end
 
清除 @active_connection_name以及递归清除子类的 @active_connection_name
 
 
第二部分
以一个普通的查询开始
比如有个model类User
class User < ActiveRecord::Base;end
调用User.count
Method 4)
def  connection
                 
        
if  @active_connection_name  &&  (conn  =  active_connections[@active_connection_name])
           conn
                     
        
else                                                                                                                          
          
#  retrieve_connection sets the cache key.
          conn  =  retrieve_connection
          active_connections[@active_connection_name] 
=  conn
        end
      end
 
显然如果存在数据库连接active_connections[@active_connection_name],则返回这个连接对象以供数据库操作
如果不存在,那么调用方法retrieve_connection(见Method5),并将Method5返回的连接对象缓存到hash
@@active_connections,以@active_connection_name为key,连接对象为value
 
 
Method 5)
def  self.retrieve_connection  # :nodoc:
       #  Name is nil if establish_connection hasn't been called for
       #  some class along the inheritance chain up to AR::Base yet.
        if  name  =  active_connection_name
          
if  conn  =  active_connections[name]
           
#  Verify the connection.
          conn.verify!(@@verification_timeout)
           elsif spec 
=  @@defined_connections[name]
           
#  Activate this connection specification.
          klass  =  name.constantize
          klass.connection 
=  spec
          conn 
=  active_connections[name]
        end
      end
 
      conn 
or   raise  ConnectionNotEstablished
    end
 
首先调用active_connection_name(见Method6)获取连接key,如果key存在,再利用这个key,从hash
@@active_connections取出连接,如果连接存在则调用verify!,否则利用key,从hash @@defined_connections取出配置,通过调用constantize,将key转化为一个类对象,并调用该类对象的
connection =方法(以配置为参数)(见Method7 )
 
Method6:
def  active_connection_name  # :nodoc:
        @active_connection_name  ||=
           
if  active_connections[name]  ||  @@defined_connections[name]
             name
           elsif self 
==  ActiveRecord::Base
             nil
           
else
             superclass.active_connection_name
           end
      end
 
返回active_connection_name
 
Method7:
def self . connection = (spec)  # :nodoc:
       if  spec . kind_of ? (ActiveRecord :: ConnectionAdapters :: AbstractAdapter)                         # 1
        active_connections[name]  =  spec
      
elsif  spec . kind_of ? (ConnectionSpecification)                                                                  # 2
        config  =  spec . config . reverse_merge( : allow_concurrency  =>  @ @allow_concurrency )
        self
. connection  =  self . send (spec . adapter_method ,  config)
      
elsif  spec . nil ?                                                                      # 3
        raise ConnectionNotEstablished
      
else                                                                                                              # 4
        establish_connection spec
      end
    end
如果直接传入的是一个连接对象,那么push到hash @@active_connections,以类名为key
如果传入是配置对象,则开始调用适配器方法(见Method8),在使用mysql的情况下是调用
mysql_connection,传入参数配置hash
然后跳到#1
如果未传参数,直接抛错
如果上面上个条件都不满足,则调用establish_connection
 
通常情况下,此方法进入#2,通过Method8返回连接对象,再进入#1
 
 
 
Method8
见文件mysql_adapter.rb
def  self.mysql_connection(config)  #  :nodoc:
      config  =  config.symbolize_keys
      host     
=  config[:host]
      port     
=  config[:port]
      socket   
=  config[:socket]
      username 
=  config[:username] ? config[:username].to_s :  ' root '
      password 
=  config[:password].to_s
 
      
if  config.has_key?(:database)
        database 
=  config[:database]
      
else
        
raise  ArgumentError,  " No database specified. Missing argument: database. "
      end
 
      require_mysql     
# 1
      mysql  =  Mysql.init  # 2
      mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher])  if  config[:sslkey]    # 3
 
      ConnectionAdapters::MysqlAdapter.new(mysql, logger, [host, username, password, database, port, socket], config)
# 4
    end
 
前面很好理解,从#1开始,#1加载active_record/vendor/mysql,#2调用init (Method9)初始化active_record/vendor/mysql文件中定义的类Mysql,返回Mysql类的实例mysql,#3看config中是否含有sskey,如果有就调用ssl_set,我发现并不存在这个方法,通过调用mysql.respond_to?(:ssl_set)发现运行过程中返回false,可能调用这个方法还需要其他的lib,默认的rails1.2.3是没有的,但因为sskey这种key,所以也不会出错 #4实例化ConnectionAdapters::MysqlAdapter(Method10)
 
Method9
 见文件mysql.rb
class   <<  Mysql
 
def  init()
    Mysql::new :INIT
 end
   ## 。。。。。
end
 
 
def  initialize( * args)
    @client_flag 
=  0
    @max_allowed_packet 
=  MAX_ALLOWED_PACKET
    @query_with_result 
=  true
    @status 
=  :STATUS_READY
    
if  args[0]  !=  :INIT then
      real_connect(
* args)
    end
 end
 
 
实例化Mysql传入参数:INIT,所以并不调用real_connect
 
 
Method10
见文件mysql_adapter.rb
 
def  initialize(connection, logger, connection_options, config)
        super(connection, logger)
        @connection_options, @config 
=  connection_options, config
 
        connect
      end
 
 实际调用 connect(Method11
 
Method11
见文件mysql_adapter.rb
 
  def  connect
          encoding 
=  @config[:encoding]
          
if  encoding
            @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
          end
          @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) 
if  @config[:sslkey]
          @connection.real_connect(
* @connection_options)  # 1
          execute( " SET NAMES '#{encoding}' " if  encoding
 
          
#  By default, MySQL 'where id is null' selects the last inserted id.
           #  Turn this off. http://dev.rubyonrails.org/ticket/6778
          execute( " SET SQL_AUTO_IS_NULL=0 " )
        end
 
首先设置编码,最终的是#1,调用连接对象 real_connect进行物理连接
 

你可能感兴趣的:(ruby,on,rails)