ผมจะมีวิธีไล่หามันใน rails code ได้อย่างไร
เรามาลองไล่ code ของ ActiveRecord กัน
เริ่มจากบรรทัดแรก
ActiveRecord::Base.establish_connection(
:adapter => 'mysql',
:host => 'localhost',
:username => 'root',
:password => '',
:database => 'test'
)
จะเห็นว่ามันเรียก class method จาก class
ActiveRecord::Base
แต่ถ้าเราไปลองเปิด file "base.rb" ไล่หา method นี้ดู ก็จะหาไม่เจอ
ที่เป็นเช่นนี้ ก็เพราะเจ้า ruby มัน open class ได้ คนที่เขียน rails ก็เลยอาศัยคุณสมบัตินี้ มา implement
code ในลักษณะ Partition class ออกเป็นส่วนๆ ตามความรับผิดชอบ
ดังนั้น เจ้าเนื้อหาใน ActiveRecord::Base ก็เลยกระจายอยู่ในหลายๆ file
ลอง grep หาดู
$ grep -Rl establish_connection *
abstract/connection_specification.rb
ลองเปิด connection_specification.rb มาดู ก็จะพบ method หน้าตาแบบนี้
def self.establish_connection(spec = nil)
case spec
when nil
raise AdapterNotSpecified unless defined? RAILS_ENV
establish_connection(RAILS_ENV)
when ConnectionSpecification
clear_active_connection_name
@active_connection_name = name
@@defined_connections[name] = spec
when Symbol, String
if configuration = configurations[spec.to_s]
else
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
เห็น switch เยอะๆแล้วตาลายนิดหน่อย case ของเราจะตกลงตรงช่อง else
# เริ่มด้วยการแปลง key ของ Hash table เราให้เป็น symbol ก่อน
spec = spec.symbolize_keys
# parameter ที่บังคับใส่ก็คือ :adapter
unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
# หา adapter_method ที่รับผิดชอบ connection ของ database นี้
# กรณี mysql ก็จะเป็น mysql_connection
adapter_method = "#{spec[:adapter]}_connection"
# check ว่ามี class method นี้อยู่จริงหรือไม่
unless respond_to?(adapter_method) then raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter" end
remove_connection
# ตรงนี้แหล่ะที่ทำผมแปลกใจ มัน recursive call ตัวเองอีกครั้งด้วย parameter ที่ห่อไว้ใน ConnectionSpecification object
establish_connection(ConnectionSpecification.new(spec, adapter_method))
แกะแล้วมี surprise เล็กน้อย เนื่องจากมัน recursive call ตัวเองด้วย parameter ที่ถูกแปลงไปแล้ว
(เจ้า ruby มันเป็น dynamic type ดังนั้น code ในลักษณะ Polymorphism ก็เลยต้องเขียนออกมาอย่างนี้)
ในการ recursive ครั้งที่สอง มันก็จะมาตก code ส่วนนี้
clear_active_connection_name
@active_connection_name = name
# spec ก็คือ ConnectionSpecification object
@@defined_connections[name] = spec
จะเห็นว่าหลังจากจบคำสั่ง establish_connection, เจ้า activerecord มันไม่ได้รีบร้อนจะเปิด connection ต่อ database ให้เรา
แต่อย่างไร, มันจะรอให้เราต้องการ access ข้อมูลจริงๆก่อน จึงจะเริ่มต้นเปิด connection ให้เรา
ตัว key ที่สำคัญใน code ข้างบน ก็คือ adapter_method ที่ชื่อ mysql_connection
ลอง grep หาดู ก็จะพบว่ามัน define ไว้ใน file "mysql_adapter.rb"
ลองเปิดดู
module ActiveRecord
class Base
...
...
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
mysql = Mysql.init
mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey]
ConnectionAdapters::MysqlAdapter.new(mysql, logger, [host, username, password, database, port, socket], config)
end
...
end
end
จะเห็นว่ามี keyword ตัวหนึ่งที่ชื่อ logger
แล้ว เจ้า logger หล่ะมาจากไหน
เนื่องจากเจ้า method mysql_connection มันอยู่ใน scope ของ class ActiveRecord::Base
ก็ลองไปเปิด file "base.rb" ดู และหาคำว่า logger ก็จะพบบรรทัดนี้
class Base
# Accepts a logger conforming to the interface of Log4r or
# the default Ruby 1.8+ Logger class, which is then passed
# on to any new database connections made and which can be
# retrieved on both a class and instance level by calling +logger+.
cattr_accessor :logger, :instance_writer => false
Bingo!!
ถ้าเราต้องการ Logger ก็เพียงแต่ supply มันให้ เจ้า ActiveRecord::Base แบบนี้
require 'logger'
ActiveRecord::Base.logger = Logger.new(STDOUT)
2 comments:
เปิดหูเปิดตามากๆ ขอบคุณมากครับ
ไล่ code กันหลายไฟล์เลย =='
Post a Comment