Action Pack: Record identification

InfoQの記事 Ruby on Rails 2.0が公式リリースをもとに、Rails2.0の新機能を順に確認しつつ、知らない機能があれば詳しく調べてしまおうというもくろみの3つ目、Action PackのRecord identification。

InfoQの説明だけ見てると、なんのこっちゃという感じだが、サンプルを見てみるとなんとなくイメージはついてくる。
# person is a Person object, which by convention will
# be mapped to person_url for lookup
redirect_to(person)
link_to(person.name, person)
form_for(person)
URL生成系の各種メソッドが、引数としてモデルオブジェクトを取ることができるようになった、ということらしい。正直、あんまり使ったことないから便利さはよくわからないんだけど、新機能らしい。…さすがにこれだけで終わるわけにはいかないので、それぞれのメソッドを簡単に調べてみる。まずはredirect_toから。

・gems\actionpack-2.0.2\lib\action_controller\base.rb
module ActionController #:nodoc:
class Base
class << self
def redirect_to(options = {}, response_status = {}) #:doc:

if options.is_a?(Hash) && options[:status]
status = options.delete(:status)
elsif response_status[:status]
status = response_status[:status]
else
status = 302
end

case options
when %r{^\w+://.*}
raise DoubleRenderError if performed?
logger.info("Redirected to #{options}") if logger && logger.info?
response.redirect(options, interpret_status(status))
response.redirected_to = options
@performed_redirect = true

when String
redirect_to(request.protocol + request.host_with_port
+ options, :status=>status)

when :back
request.env["HTTP_REFERER"] ? redirect_to(request.env["HTTP_REFERER"],
:status=>status) : raise(RedirectBackError)

when Hash
redirect_to(url_for(options), :status=>status)
response.redirected_to = options

else
redirect_to(url_for(options), :status=>status)
end
end
モデルオブジェクトが渡された際に実行されるのは、一番下のelse~のところだろうか。ここだけじゃよくわからないので、さらにurl_forを追ってみる。
module ActionController #:nodoc:
class Base
class << self
def url_for(options = nil) #:doc:
case options || {}
when String
options
when Hash
@url.rewrite(rewrite_options(options))
else
polymorphic_url(options)
end
end
モデルオブジェクトが渡されて来た場合には、polymorphic_urlが呼び出されるようだ。
・gems\actionpack-2.0.2\lib\action_controller\polymorphic_routes.rb
module ActionController
module PolymorphicRoutes
def polymorphic_url(record_or_hash_or_array, options = {})
record = extract_record(record_or_hash_or_array)

namespace = extract_namespace(record_or_hash_or_array)

args = case record_or_hash_or_array
when Hash; [ record_or_hash_or_array ]
when Array; record_or_hash_or_array.dup
else [ record_or_hash_or_array ]
end

inflection =
case
when options[:action] == "new"
args.pop
:singular
when record.respond_to?(:new_record?) && record.new_record?
args.pop
:plural
else
:singular
end

named_route = build_named_route_call(record_or_hash_or_array,
namespace, inflection, options)
send!(named_route, *args)
end
モデルの情報を色々展開して、最後はbuild_named_route_callを呼び出し。
def build_named_route_call(records, namespace, inflection, options = {})
records = Array.new([extract_record(records)]) unless records.is_a?(Array)
base_segment = "#{RecordIdentifier.send!("#{inflection}_class_name", records.pop)}_"

method_root = records.reverse.inject(base_segment) do |string, name|
segment = "#{RecordIdentifier.send!("singular_class_name", name)}_"
segment << string
end

action_prefix(options) + namespace + method_root + routing_type(options)
end
結局、RecordIdentifierを使ってurl生成のための情報をひっぱってきて、組み立てて返している。やっとタイトルにつながった。

・gems\actionpack-2.0.2\lib\action_controller\record_identifier.rb
module ActionController
module RecordIdentifier
extend self
def plural_class_name(record_or_class)
singular_class_name(record_or_class).pluralize
end

def singular_class_name(record_or_class)
class_from_record_or_class(record_or_class).name.underscore.tr('/', '_')
end
このルートで使われているのが確認できるのはこれくらいだろうか。
link_toもform_forも、入り口が違うだけで最終的には同じルートを通っていた。細かい点でわからないところはいくつかあるが、機能の確認としてはここまでで終わっておく。

関連リンク:
Rails2.0リリース
Rails2.0のscaffoldは前とだいぶ違うらしい
Migration
Rails2.0でブックマークアプリ
Rails2.0の変更点
Action Pack: Resources
Action Pack: Multiview

コメント