2008年6月3日星期二

ActiveResource 源码 -- format

我机器上 ActiveResource gem 包的路径:
/var/lib/gems/1.8/gems/activeresource-2.0.2-/

跟format相关的文件有三个
lib/active_resource/formats.rb
lib/active_resource/formats/json_format.rb
lib/active_resource/formats/xml_format.rb

formats.rb
module ActiveResource
module Formats
# Lookup the format class from a mime type reference symbol. Example:
#
# ActiveResource::Formats[:xml] # => ActiveResource::Formats::XmlFormat
# ActiveResource::Formats[:json] # => ActiveResource::Formats::JsonFormat
def self.[](mime_type_reference)
ActiveResource::Formats.const_get(mime_type_reference.to_s.camelize + "Format")
end
end
end

require 'active_resource/formats/xml_format'
require 'active_resource/formats/json_format'
可以看到,format.rb其实是对另外两个文件的统一包装。
其中的const_get方法比较有意思。const_get方法是Module类提供的方法,意思是取Mudule中某常量的值。
ActiveResource::Formats.const_get()返回的是一个Module类。
我想这说明了两点:常量的值可以是任何对象。Module中嵌套的Module名也是常量,Module名是对Module类的引用。

json_format.rb和xml_format.rb没有太多好说的,都是调用ActiveSupport里的to_xxx和from_xxx方法。
xml_format.rb里的decode方法值得一提:
def decode(xml)
from_xml_data(Hash.from_xml(xml))
end

private
# Manipulate from_xml Hash, because xml_simple is not exactly what we
# want for ActiveResource.
def from_xml_data(data)
if data.is_a?(Hash) && data.keys.size == 1
data.values.first
else
data
end
end
可以看到,在decode方法里又调用了一次from_xml_data方法,为什么要这样呢?
因为ActiveSupport里Hash的from_xml方法是调用xml_simple的方法来实现的,而这个xml_simple返回的Hash往往在最外面还包了一层:

{"records"=>[{"name"=>"Matz", "id"=>1}, {"name"=>"David", "id"=>2}]}
{"hash"=>{"name"=>"David", "id"=>2}}

而实际上我们要的是里面的value:

[{"name"=>"Matz", "id"=>1}, {"name"=>"David", "id"=>2}]
{"name"=>"David", "id"=>2}

所以要用from_xml_data把数据再剥开一次。

ActiveSupport里Hash的from_xml方法定义如下(在activesupport/lib/active_support/core_ext/hash/conversions.rb里):
def from_xml(xml)
# TODO: Refactor this into something much cleaner that doesn't rely on XmlSimple
undasherize_keys(typecast_xml_value(XmlSimple.xml_in(xml,
'forcearray' => false,
'forcecontent' => true,
'keeproot' => true,
'contentkey' => '__content__')
))
end
哈哈,DHH自己都说了要Refactor这个方法,不过估计也就只写了个TODO在这里,一直没Refactor过。

另外有个问题,为什么这里可以直接调用XmlSimple的方法呢?
首先要在文件里require一下:
require 'xml_simple'
其次在vendor目录下已经引入了XmlSimple:
activesupport/lib/active_support/vendor/xml_simple.rb

没错,XmlSimple就只有这样一个文件,不信你去把gem安装的XmlSimple打开来看吧,在我机器上的路径是:
/var/lib/gems/1.8/gems/xml-simple-1.0.11/
一千多行的文件,XmlSimple的作者可真能折腾啊。

没有评论: