第一部分:
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进行物理连接