Ruby on Rails/リクエスト処理を読む
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
開始行:
#contents
*はじめに [#hf57bbe9]
[[Ruby on Rails/サーバ起動時の処理を読む]]では、WEBrickサ...
http://localhost:3000/accountbook/
というhttpリクエストを受け取った場合にどのような処理が行...
*DispatcherServlet($GEM_HOME/gems/lib/rails/webrick_serve...
WEBrickサーブレットはサーバからserviceメソッドを呼ばれま...
+handle_fileメソッドを呼んで通常ファイルを処理する
+通常ファイルとして処理できなかった場合はhandle_dispatch...
handle_dispatchメソッドの前半部分は以下のようになっていま...
def handle_dispatch(req, res, origin = nil) #:nodoc:
data = StringIO.new
Dispatcher.dispatch(
CGI.new("query", create_env_table(req, origin), Stri...
ActionController::CgiRequest::DEFAULT_SESSION_OPTION...
data
)
*Dispatcher($GEM_HOME/gems/rails/lib/dispatcher.rb) [#q7a...
Dispatcher.dispatchメソッドです。条件分岐や例外処理を省略...
request, response = ActionController::CgiRequest.new(cgi...
prepare_application
controller = ActionController::Routing::Routes.recognize...
controller.process(request, response).out(output)
reset_after_dispatch
ActionController::CgiRequest, CgiResponseはそれぞれ、Acti...
**Dispatcher.prepare_application [#v9ffb7f6]
prepare_applicationメソッドです。
def prepare_application
if Dependencies.load?
ActionController::Routing::Routes.reload
self.preparation_callbacks_run = false
end
prepare_breakpoint
require_dependency 'application' unless Object.const_d...
ActiveRecord::Base.verify_active_connections! if defin...
run_preparation_callbacks
end
Dependencies.load?がtrueの場合は毎回ルーティング処理をや...
# In the development environment your application's code...
# every request. This slows down response time but is p...
# since you don't have to restart the webserver when you...
config.cache_classes = false
読み込み方法がいつ設定されるかというとRails::Initialize#p...
def initialize_dependency_mechanism
Dependencies.mechanism = configuration.cache_classes ?...
end
次にブレークポイントの設定を行っていますがとりあえず無視...
次にapplication.rbが読み込まれています。ApplicationContro...
次のActiveRecord::Base.verify_active_connections!は、まだ...
コールバックも登録してないはずなので無視しましょう。
*ActionController::Routing::Routes.recognize ($GEM_HOME/g...
それではリクエストからコントローラを引き当てているらしいA...
def recognize(request)
params = recognize_path(request.path, extract_request_...
request.path_parameters = params.with_indifferent_access
"#{params[:controller].camelize}Controller".constantize
end
request.pathは要求されたパスでhttp://localhost:3000/accou...
recongnize_pathメソッドです。前回設定したroutes(ActionCo...
def recognize_path(path, environment={})
path = CGI.unescape(path)
routes.each do |route|
result = route.recognize(path, environment) and retu...
end
raise RoutingError, "no route found to match #{path.in...
end
ActionController::Routing::Route#rocognizeメソッドです。...
def recognize(path, environment={})
write_recognition
recognize path, environment
end
**ActionController::Routing::Route#write_recognition [#d5...
write_recognitionメソッドに進みましょう。
def write_recognition
# Create an if structure to extract the params from a ...
body = "params = parameter_shell.dup\n#{recognition_ex...
body = "if #{recognition_conditions.join(" && ")}\n#{b...
# Build the method declaration and compile it
method_decl = "def recognize(path, env={})\n#{body}\ne...
instance_eval method_decl, "generated code (#{__FILE__...
method_decl
end
どうやらrecognizeメソッドは動的に書かれているようです。動...
def recognize(path, env={})
if (match = #{Regexp.new(recognition_pattern).inspect}...
params = parameter_shell.dup
#{recognition_extraction * "\n"}
params
end
end
***ActionController::Routing::Route#recognition_pattern [...
recognition_patternメソッドは以下のようになっています。
def recognition_pattern(wrap = true)
pattern = ''
segments.reverse_each do |segment|
pattern = segment.build_pattern pattern
end
wrap ? ("\\A" + pattern + "\\Z") : pattern
end
前回を参照すると、segmentsには以下の順序でオブジェクトが...
+DividerSegment('/'), is_optional == true
+ControllerSegment(:controller), is_optional == false
+DividerSegment('/'), is_optional == true
+DynamicSegment(:action), is_optional == true
+DividerSegment('/'), is_optional == true
+DynamicSegment(:id), is_optional == true
+DividerSegment('/'), is_optional == true
DividerSegment(の親クラスのStaticSegment)のbuild_patter...
def build_pattern(pattern)
escaped = Regexp.escape(value)
if optional? && ! pattern.empty?
"(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Reg...
elsif optional?
Regexp.optionalize escaped
else
escaped + pattern
end
end
DynamicSegmentのbuild_patternメソッドです。ControllerSegm...
def build_pattern(pattern)
chunk = regexp_chunk
chunk = "(#{chunk})" if Regexp.new(chunk).number_of_ca...
pattern = "#{chunk}#{pattern}"
optional? ? Regexp.optionalize(pattern) : pattern
end
def regexp_chunk
regexp ? "(#{regexp.source})" : "([^#{Routing::SEPARAT...
end
ControllerSegmentではregexp_chunkメソッドがオーバーライド...
def regexp_chunk
possible_names = Routing.possible_controllers.collect ...
"(?i-:(#{(regexp || Regexp.union(*possible_names)).sou...
end
順を追って正規表現を構築してみましょう。
+(?:/)?
+(?:([^/;.,?]+)(?:/)?)?
+(?:(?:/)?\Z|/([^/;.,?]+)(?:/)?)
+(?:([^/;.,?]+)(?:(?:/)?\Z|/([^/;.,?]+)(?:/)?))?
+(?:(?:/)?\Z|/([^/;.,?]+)(?:(?:/)?\Z|/([^/;.,?]+)(?:/)?))
+(?i-:(accountbook))(?:(?:/)?\Z|/([^/;.,?]+)(?:(?:/)?\Z|/...
+(?:(?:/)?\Z|/(?i-:(accountbook))(?:(?:/)?\Z|/([^/;.,?]+)...
ん〜、見てると気分悪くなりますが、確認してみましょう。
:http://localhost:3000/accountbook/|/(?i-:(accountbook))(...
:http://localhost:3000/accountbook/show/1|/(?i-:(accountb...
合っているようです。
***ActionController::Routing::Route#recognition_extractio...
次に、recognition_extractionメソッドです。
def recognition_extraction
next_capture = 1
extraction = segments.collect do |segment|
x = segment.match_extraction next_capture
next_capture += Regexp.new(segment.regexp_chunk).num...
x
end
extraction.compact
end
各segmentについてどうなるかを見ていきましょう。
+match_extractionの結果はnil, next_capture => 1
+match_extractionの結果は"params[:controller] = match[1]....
+match_extractionの結果はnil, next_capture => 2
+match_extractionの結果は"params[:action] = match[2] if m...
+match_extractionの結果はnil, next_capture => 3
+match_extractionの結果は"params[:id] = match[3] if match...
+match_extractionの結果はnil, next_capture => 4
以上のことから動的に書かれたrecognizeメソッドは以下のよう...
def recognize(path, env={})
if (match = /\A(?:(?:/)?\Z|/((?i-:(accountbook)))(?:(?...
params = parameter_shell.dup
params[:controller] = match[1].downcase if match[1]
params[:action] = match[2] if match[2]
params[:id] = match[3] if match[3]
params
end
end
魔法ですね:-)
**Dependencies.load_missing_constant ($GEM_HOME/gems/acti...
結果、ActionController::Routing::Routes.recognizeメソッド...
・・・ん?AccountbookControllerっていつの間に読み込まれた...
dependencies.rbが怪しいと思ったのですがどう呼ばれているか...
def load_file(path, const_paths = loadable_constants_f...
p path
p caller
で引っかかったのが、
"script/../config/../app/controllers/accountbook_control...
[".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesuppor...
".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesuppor...
".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesuppor...
".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesuppor...
".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesuppor...
".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesuppor...
".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/actionpack-1...
いつの間にかconst_missingなんてできたんですねぇ。ともかく...
*ActionController::Base.scaffold ($GEM_HOME/gems/actionpa...
一番単純なRailsアプリケーションは以下のようになっています。
app/controllers/application.rb
class ApplicationController < ActionController::Base
# Pick a unique cookie name to distinguish our session...
session :session_key => '_accountbook_session_id'
end
app/controllers/accountbook_controller.rb
class AccountbookController < ApplicationController
scaffold :outgo
end
クラス定義中でscaffoldメソッドを使用するとhttp://localhos...
まず前半部分
def scaffold(model_id, options = {})
options.assert_valid_keys(:class_name, :suffix)
singular_name = model_id.to_s
class_name = options[:class_name] || singular_name....
plural_name = singular_name.pluralize
suffix = options[:suffix] ? "_#{singular_name}"...
optionsは指定しないので、
:singular_name|"outgo"
:class_name|"Outgo"
:plural_name|"outgos"
:suffix|""
ということになります。次に、options[:suffix]は定義してい...
def index
list
end
その後、各種メソッドが定義されています。長いのでlistメソ...
def list
@outgo_pages, @outgos = paginate :outgos, :per_page =>...
render_scaffold "list"
end
def render_scaffold(action=nil)
action ||= caller_method_name(caller)
# logger.info ("testing template:" + "#{self.class.con...
if template_exists?("#{self.class.controller_path}/#{a...
render :action => action
else
@scaffold_class = Outgo
@scaffold_singular_name, @scaffold_plural_name = "ou...
@scaffold_suffix = ""
add_instance_variables_to_assigns
@template.instance_variable_set("@content_for_layout...
if !active_layout.nil?
render :file => active_layout, :use_full_path => t...
else
render :file => scaffold_path('layout')
end
end
end
def scaffold_path(template_name)
File.dirname(__FILE__) + "/templates/scaffolds/" + tem...
end
以上をふまえた上でActionController::Base.processメソッド...
*ActionController::Base ($GEM_HOME/gems/actionpack/lib/ac...
まず、ActionController::Base.processメソッドです。オブジ...
def process(request, response) #:nodoc:
new.process(request, response)
end
インスタンスメソッドの方のprocessです。
def process(request, response, method = :perform_action,...
initialize_template_class(response)
assign_shortcuts(request, response)
initialize_current_url
assign_names
forget_variables_added_to_assigns
log_processing
send(method, *arguments)
assign_default_content_type_and_charset
response
ensure
process_cleanup
end
**ActionController::Base#initialize_template_class [#o8ab...
まず、initialize_template_classメソッドです。
def initialize_template_class(response)
raise "You must assign a template class through Action...
response.template = self.class.view_class.new(self.cla...
response.redirected_to = nil
@performed_render = @performed_redirect = false
end
@@template_classはいつ設定されたんだ?と思ったところ、ト...
ActionController::Base.template_class = ActionView::Base
action_controller.rbを見たついでに、ActionControllerおよ...
view_classメソッドです。
def self.view_class
@view_class ||=
# create a new class based on the default template c...
returning Class.new(ActionView::Base) do |view_class|
view_class.send(:include, master_helper_module)
end
end
master_helper_moduleはhelper.rbで定義されています。ここで...
次に、view_rootメソッドですがtemplate_rootメソッドを参照...
def initialize_framework_views
for framework in ([ :action_controller, :action_mailer...
framework.to_s.camelize.constantize.const_get("Base"...
end
end
view_pathはアプリケーションディレクトリ/app/viewsです。
**ActionController::Base#assign_names [#i61cc11e]
processメソッドに戻って、次のassign_shotcutsメソッドでは...
次のinitialize_current_urlメソッドではUrlRewriterオブジェ...
次のassign_namesメソッドはまじめに見ましょう。先ほどコン...
def assign_names
@action_name = (params['action'] || 'index')
end
**ActionController::Base#perform_action [#gd09901d]
さて、perform_actionメソッドです。いろいろ条件分岐してい...
def perform_action
if self.class.action_methods.include?(action_name)
send(action_name)
render unless performed?
action_methodsはActionController::Baseのサブクラスで定義...
**ActionView::Base.paginate ($GEM_HOME/gems/actionpack/li...
さて、indexメソッドですがlistメソッドを呼んでいるだけなの...
def list
@outgo_pages, @outgos = paginate :outgos, :per_page =>...
render_scaffold "list"
end
paginateメソッドに進みましょう。定義されているファイルは...
def paginate(collection_id, options={})
Pagination.validate_options!(collection_id, options, t...
paginator_and_collection_for(collection_id, options)
end
validate_options!メソッドはoptionsへのデフォルト値のマー...
次に、paginator_and_collection_forメソッドです。
def paginator_and_collection_for(collection_id, options)...
klass = options[:class_name].constantize
page = params[options[:parameter]]
count = count_collection_for_pagination(klass, options)
paginator = Paginator.new(self, count, options[:per_pa...
collection = find_collection_for_pagination(klass, opt...
return paginator, collection
end
options[:parameter]は'page'なので、リクエスト中のpage=nを...
count_collection_for_paginationメソッドです。どうやらここ...
def count_collection_for_pagination(model, options)
model.count(:conditions => options[:conditions],
:joins => options[:join] || options[:joins],
:include => options[:include],
:select => options[:count])
end
*ActiveRecord::Base [#r1f7821f]
**ActiveRecord::Base.count ($GEM_HOME/gems/activerecord/l...
countメソッドです。
def count(*args)
calculate(:count, *construct_count_options_from_legacy...
end
続いてcalculateメソッドです。
def calculate(operation, column_name, options = {})
validate_calculation_options(operation, options)
column_name = options[:select] if options[:select]
column_name = '*' if column_name == :all
column = column_for column_name
catch :invalid_query do
if options[:group]
return execute_grouped_calculation(operation, colu...
else
return execute_simple_calculation(operation, colum...
end
end
0
end
column_nameは'*'になります。そのため、columnは空配列です...
def execute_simple_calculation(operation, column_name, c...
value = connection.select_value(construct_calculation_...
type_cast_calculated_value(value, column, operation)
end
construct_calculation_sqlメソッドは淡々とSQLを構築してい...
**ActiveRecord::Base.connection ($GEM_HOME/gems/activerec...
connectionメソッドは前回も見たconnection_specification.rb...
def connection
if @active_connection_name && (conn = active_connectio...
conn
else
# retrieve_connection sets the cache key.
conn = retrieve_connection
active_connections[@active_connection_name] = conn
end
end
end
実際にデータベースと通信する必要があるまで接続しないとい...
def self.retrieve_connection #:nodoc:
# Name is nil if establish_connection hasn't been call...
# some class along the inheritance chain up to AR::Bas...
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
接続はまだないのでelsifの方が実行されます。どうにも混乱す...
def self.connection=(spec) #:nodoc:
if spec.kind_of?(ActiveRecord::ConnectionAdapters::Abs...
active_connections[name] = spec
elsif spec.kind_of?(ConnectionSpecification)
config = spec.config.reverse_merge(:allow_concurrenc...
self.connection = self.send(spec.adapter_method, con...
elsif spec.nil?
raise ConnectionNotEstablished
else
establish_connection spec
end
end
specはConnectionSpecificationのためadapter_methodが呼ばれ...
**ActiveRecord::Base.columns ($GEM_HOME/gems/activerecord...
それでは予告通りcolumnsメソッドです。
def columns
unless @columns
@columns = connection.columns(table_name, "#{name} C...
@columns.each {|column| column.primary = column.name...
end
@columns
end
table_nameメソッドに進みましょう。
def table_name
reset_table_name
end
def reset_table_name #:nodoc:
base = base_class
name =
# STI subclasses always use their superclass' table.
unless self == base
base.table_name
else
# Nested classes are prefixed with singular parent...
if parent < ActiveRecord::Base && !parent.abstract...
contained = parent.table_name
contained = contained.singularize if parent.plur...
contained << '_'
end
name = "#{table_name_prefix}#{contained}#{undecora...
end
set_table_name(name)
name
end
base_classメソッドはActiveRecord::Baseを直接継承している...
def undecorated_table_name(class_name = base_class.name)
table_name = Inflector.underscore(Inflector.demodulize...
table_name = Inflector.pluralize(table_name) if plural...
table_name
end
Inflectorの各メソッドは$GEM_HOME/gems/activesupport/lib/a...
*ActionView::Base [#k6ced6da]
今までのところで表示するための情報は取得できた気がするの...
def render_scaffold(action=nil)
if template_exists?("#{self.class.controller_path}/#{a...
render :action => action
else
@scaffold_class = Outgo
@scaffold_singular_name, @scaffold_plural_name = "ou...
@scaffold_suffix = ""
add_instance_variables_to_assigns
@template.instance_variable_set("@content_for_layout...
if !active_layout.nil?
render :file => active_layout, :use_full_path => t...
else
render :file => scaffold_path('layout')
end
end
end
def scaffold_path(template_name)
File.dirname(__FILE__) + "/templates/scaffolds/" + tem...
end
まず初めのif。アプリケーションディレクトリ/app/views/list...
その後いろいろ設定を行い、デフォルトのテンプレートを使っ...
**ActionView::Base#render_file ($GEM_HOME/gems/actionpack...
それではrender_fileメソッドです。渡される引数を考慮すると...
template_file_name = template_path
template_extension = template_path.split('.').last
template_source = nil # Don't read the source until we k...
render_template(template_extension, template_source, tem...
render_templateメソッドです。
def render_template(template_extension, template, file_p...
if handler = @@template_handlers[template_extension]
template ||= read_template_file(file_path, template_...
delegate_render(handler, template, local_assigns)
else
compile_and_render_template(template_extension, temp...
end
end
template_handlerを登録した覚えはないのでelseのcompile_and...
def compile_and_render_template(extension, template = ni...
# convert string keys to symbols if requested
local_assigns = local_assigns.symbolize_keys if @@loca...
# compile the given template, if necessary
if compile_template?(template, file_path, local_assigns)
template ||= read_template_file(file_path, extension)
compile_template(extension, template, file_path, loc...
end
# Get the method name for this template and run it
method_name = @@method_names[file_path || template]
evaluate_assigns
send(method_name, local_assigns) do |*name|
instance_variable_get "@content_for_#{name.first || ...
end
end
初回ということでcompile_templateメソッドに進みます。長い...
def compile_template(extension, template, file_name, loc...
render_symbol = assign_method_name(extension, template...
render_source = create_template_source(extension, temp...
line_offset = @@template_args[render_symbol].size
CompiledTemplates.module_eval(render_source, file_name...
@@compile_time[render_symbol] = Time.now
end
assign_method_nameメソッドはファイル名などからキーを生成...
次のcreate_template_sourceメソッドは・・・また変なことを...
def create_template_source(extension, template, render_s...
body = ERB.new(template, nil, @@erb_trim_mode).src
@@template_args[render_symbol] ||= {}
locals_keys = @@template_args[render_symbol].keys | lo...
@@template_args[render_symbol] = locals_keys.inject({}...
locals_code = ""
locals_keys.each do |key|
locals_code << "#{key} = local_assigns[:#{key}]\n"
end
"def #{render_symbol}(local_assigns)\n#{locals_code}#{...
end
ふうむ、上のメソッドで何でsendなんてしてるんだろうと思っ...
*おわりに [#db682088]
今回はRuby on Railsのうち、リクエストが来たときにどのよう...
-これぐらいの規模になると、ある設定がどこでされてるのかが...
-オンデマンド設定が加わってくると一層わかりづらくなる
-動的に書かれるコードが多い
といったところです。
それではみなさんもよいコードリーディングを。
終了行:
#contents
*はじめに [#hf57bbe9]
[[Ruby on Rails/サーバ起動時の処理を読む]]では、WEBrickサ...
http://localhost:3000/accountbook/
というhttpリクエストを受け取った場合にどのような処理が行...
*DispatcherServlet($GEM_HOME/gems/lib/rails/webrick_serve...
WEBrickサーブレットはサーバからserviceメソッドを呼ばれま...
+handle_fileメソッドを呼んで通常ファイルを処理する
+通常ファイルとして処理できなかった場合はhandle_dispatch...
handle_dispatchメソッドの前半部分は以下のようになっていま...
def handle_dispatch(req, res, origin = nil) #:nodoc:
data = StringIO.new
Dispatcher.dispatch(
CGI.new("query", create_env_table(req, origin), Stri...
ActionController::CgiRequest::DEFAULT_SESSION_OPTION...
data
)
*Dispatcher($GEM_HOME/gems/rails/lib/dispatcher.rb) [#q7a...
Dispatcher.dispatchメソッドです。条件分岐や例外処理を省略...
request, response = ActionController::CgiRequest.new(cgi...
prepare_application
controller = ActionController::Routing::Routes.recognize...
controller.process(request, response).out(output)
reset_after_dispatch
ActionController::CgiRequest, CgiResponseはそれぞれ、Acti...
**Dispatcher.prepare_application [#v9ffb7f6]
prepare_applicationメソッドです。
def prepare_application
if Dependencies.load?
ActionController::Routing::Routes.reload
self.preparation_callbacks_run = false
end
prepare_breakpoint
require_dependency 'application' unless Object.const_d...
ActiveRecord::Base.verify_active_connections! if defin...
run_preparation_callbacks
end
Dependencies.load?がtrueの場合は毎回ルーティング処理をや...
# In the development environment your application's code...
# every request. This slows down response time but is p...
# since you don't have to restart the webserver when you...
config.cache_classes = false
読み込み方法がいつ設定されるかというとRails::Initialize#p...
def initialize_dependency_mechanism
Dependencies.mechanism = configuration.cache_classes ?...
end
次にブレークポイントの設定を行っていますがとりあえず無視...
次にapplication.rbが読み込まれています。ApplicationContro...
次のActiveRecord::Base.verify_active_connections!は、まだ...
コールバックも登録してないはずなので無視しましょう。
*ActionController::Routing::Routes.recognize ($GEM_HOME/g...
それではリクエストからコントローラを引き当てているらしいA...
def recognize(request)
params = recognize_path(request.path, extract_request_...
request.path_parameters = params.with_indifferent_access
"#{params[:controller].camelize}Controller".constantize
end
request.pathは要求されたパスでhttp://localhost:3000/accou...
recongnize_pathメソッドです。前回設定したroutes(ActionCo...
def recognize_path(path, environment={})
path = CGI.unescape(path)
routes.each do |route|
result = route.recognize(path, environment) and retu...
end
raise RoutingError, "no route found to match #{path.in...
end
ActionController::Routing::Route#rocognizeメソッドです。...
def recognize(path, environment={})
write_recognition
recognize path, environment
end
**ActionController::Routing::Route#write_recognition [#d5...
write_recognitionメソッドに進みましょう。
def write_recognition
# Create an if structure to extract the params from a ...
body = "params = parameter_shell.dup\n#{recognition_ex...
body = "if #{recognition_conditions.join(" && ")}\n#{b...
# Build the method declaration and compile it
method_decl = "def recognize(path, env={})\n#{body}\ne...
instance_eval method_decl, "generated code (#{__FILE__...
method_decl
end
どうやらrecognizeメソッドは動的に書かれているようです。動...
def recognize(path, env={})
if (match = #{Regexp.new(recognition_pattern).inspect}...
params = parameter_shell.dup
#{recognition_extraction * "\n"}
params
end
end
***ActionController::Routing::Route#recognition_pattern [...
recognition_patternメソッドは以下のようになっています。
def recognition_pattern(wrap = true)
pattern = ''
segments.reverse_each do |segment|
pattern = segment.build_pattern pattern
end
wrap ? ("\\A" + pattern + "\\Z") : pattern
end
前回を参照すると、segmentsには以下の順序でオブジェクトが...
+DividerSegment('/'), is_optional == true
+ControllerSegment(:controller), is_optional == false
+DividerSegment('/'), is_optional == true
+DynamicSegment(:action), is_optional == true
+DividerSegment('/'), is_optional == true
+DynamicSegment(:id), is_optional == true
+DividerSegment('/'), is_optional == true
DividerSegment(の親クラスのStaticSegment)のbuild_patter...
def build_pattern(pattern)
escaped = Regexp.escape(value)
if optional? && ! pattern.empty?
"(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Reg...
elsif optional?
Regexp.optionalize escaped
else
escaped + pattern
end
end
DynamicSegmentのbuild_patternメソッドです。ControllerSegm...
def build_pattern(pattern)
chunk = regexp_chunk
chunk = "(#{chunk})" if Regexp.new(chunk).number_of_ca...
pattern = "#{chunk}#{pattern}"
optional? ? Regexp.optionalize(pattern) : pattern
end
def regexp_chunk
regexp ? "(#{regexp.source})" : "([^#{Routing::SEPARAT...
end
ControllerSegmentではregexp_chunkメソッドがオーバーライド...
def regexp_chunk
possible_names = Routing.possible_controllers.collect ...
"(?i-:(#{(regexp || Regexp.union(*possible_names)).sou...
end
順を追って正規表現を構築してみましょう。
+(?:/)?
+(?:([^/;.,?]+)(?:/)?)?
+(?:(?:/)?\Z|/([^/;.,?]+)(?:/)?)
+(?:([^/;.,?]+)(?:(?:/)?\Z|/([^/;.,?]+)(?:/)?))?
+(?:(?:/)?\Z|/([^/;.,?]+)(?:(?:/)?\Z|/([^/;.,?]+)(?:/)?))
+(?i-:(accountbook))(?:(?:/)?\Z|/([^/;.,?]+)(?:(?:/)?\Z|/...
+(?:(?:/)?\Z|/(?i-:(accountbook))(?:(?:/)?\Z|/([^/;.,?]+)...
ん〜、見てると気分悪くなりますが、確認してみましょう。
:http://localhost:3000/accountbook/|/(?i-:(accountbook))(...
:http://localhost:3000/accountbook/show/1|/(?i-:(accountb...
合っているようです。
***ActionController::Routing::Route#recognition_extractio...
次に、recognition_extractionメソッドです。
def recognition_extraction
next_capture = 1
extraction = segments.collect do |segment|
x = segment.match_extraction next_capture
next_capture += Regexp.new(segment.regexp_chunk).num...
x
end
extraction.compact
end
各segmentについてどうなるかを見ていきましょう。
+match_extractionの結果はnil, next_capture => 1
+match_extractionの結果は"params[:controller] = match[1]....
+match_extractionの結果はnil, next_capture => 2
+match_extractionの結果は"params[:action] = match[2] if m...
+match_extractionの結果はnil, next_capture => 3
+match_extractionの結果は"params[:id] = match[3] if match...
+match_extractionの結果はnil, next_capture => 4
以上のことから動的に書かれたrecognizeメソッドは以下のよう...
def recognize(path, env={})
if (match = /\A(?:(?:/)?\Z|/((?i-:(accountbook)))(?:(?...
params = parameter_shell.dup
params[:controller] = match[1].downcase if match[1]
params[:action] = match[2] if match[2]
params[:id] = match[3] if match[3]
params
end
end
魔法ですね:-)
**Dependencies.load_missing_constant ($GEM_HOME/gems/acti...
結果、ActionController::Routing::Routes.recognizeメソッド...
・・・ん?AccountbookControllerっていつの間に読み込まれた...
dependencies.rbが怪しいと思ったのですがどう呼ばれているか...
def load_file(path, const_paths = loadable_constants_f...
p path
p caller
で引っかかったのが、
"script/../config/../app/controllers/accountbook_control...
[".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesuppor...
".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesuppor...
".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesuppor...
".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesuppor...
".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesuppor...
".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesuppor...
".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/actionpack-1...
いつの間にかconst_missingなんてできたんですねぇ。ともかく...
*ActionController::Base.scaffold ($GEM_HOME/gems/actionpa...
一番単純なRailsアプリケーションは以下のようになっています。
app/controllers/application.rb
class ApplicationController < ActionController::Base
# Pick a unique cookie name to distinguish our session...
session :session_key => '_accountbook_session_id'
end
app/controllers/accountbook_controller.rb
class AccountbookController < ApplicationController
scaffold :outgo
end
クラス定義中でscaffoldメソッドを使用するとhttp://localhos...
まず前半部分
def scaffold(model_id, options = {})
options.assert_valid_keys(:class_name, :suffix)
singular_name = model_id.to_s
class_name = options[:class_name] || singular_name....
plural_name = singular_name.pluralize
suffix = options[:suffix] ? "_#{singular_name}"...
optionsは指定しないので、
:singular_name|"outgo"
:class_name|"Outgo"
:plural_name|"outgos"
:suffix|""
ということになります。次に、options[:suffix]は定義してい...
def index
list
end
その後、各種メソッドが定義されています。長いのでlistメソ...
def list
@outgo_pages, @outgos = paginate :outgos, :per_page =>...
render_scaffold "list"
end
def render_scaffold(action=nil)
action ||= caller_method_name(caller)
# logger.info ("testing template:" + "#{self.class.con...
if template_exists?("#{self.class.controller_path}/#{a...
render :action => action
else
@scaffold_class = Outgo
@scaffold_singular_name, @scaffold_plural_name = "ou...
@scaffold_suffix = ""
add_instance_variables_to_assigns
@template.instance_variable_set("@content_for_layout...
if !active_layout.nil?
render :file => active_layout, :use_full_path => t...
else
render :file => scaffold_path('layout')
end
end
end
def scaffold_path(template_name)
File.dirname(__FILE__) + "/templates/scaffolds/" + tem...
end
以上をふまえた上でActionController::Base.processメソッド...
*ActionController::Base ($GEM_HOME/gems/actionpack/lib/ac...
まず、ActionController::Base.processメソッドです。オブジ...
def process(request, response) #:nodoc:
new.process(request, response)
end
インスタンスメソッドの方のprocessです。
def process(request, response, method = :perform_action,...
initialize_template_class(response)
assign_shortcuts(request, response)
initialize_current_url
assign_names
forget_variables_added_to_assigns
log_processing
send(method, *arguments)
assign_default_content_type_and_charset
response
ensure
process_cleanup
end
**ActionController::Base#initialize_template_class [#o8ab...
まず、initialize_template_classメソッドです。
def initialize_template_class(response)
raise "You must assign a template class through Action...
response.template = self.class.view_class.new(self.cla...
response.redirected_to = nil
@performed_render = @performed_redirect = false
end
@@template_classはいつ設定されたんだ?と思ったところ、ト...
ActionController::Base.template_class = ActionView::Base
action_controller.rbを見たついでに、ActionControllerおよ...
view_classメソッドです。
def self.view_class
@view_class ||=
# create a new class based on the default template c...
returning Class.new(ActionView::Base) do |view_class|
view_class.send(:include, master_helper_module)
end
end
master_helper_moduleはhelper.rbで定義されています。ここで...
次に、view_rootメソッドですがtemplate_rootメソッドを参照...
def initialize_framework_views
for framework in ([ :action_controller, :action_mailer...
framework.to_s.camelize.constantize.const_get("Base"...
end
end
view_pathはアプリケーションディレクトリ/app/viewsです。
**ActionController::Base#assign_names [#i61cc11e]
processメソッドに戻って、次のassign_shotcutsメソッドでは...
次のinitialize_current_urlメソッドではUrlRewriterオブジェ...
次のassign_namesメソッドはまじめに見ましょう。先ほどコン...
def assign_names
@action_name = (params['action'] || 'index')
end
**ActionController::Base#perform_action [#gd09901d]
さて、perform_actionメソッドです。いろいろ条件分岐してい...
def perform_action
if self.class.action_methods.include?(action_name)
send(action_name)
render unless performed?
action_methodsはActionController::Baseのサブクラスで定義...
**ActionView::Base.paginate ($GEM_HOME/gems/actionpack/li...
さて、indexメソッドですがlistメソッドを呼んでいるだけなの...
def list
@outgo_pages, @outgos = paginate :outgos, :per_page =>...
render_scaffold "list"
end
paginateメソッドに進みましょう。定義されているファイルは...
def paginate(collection_id, options={})
Pagination.validate_options!(collection_id, options, t...
paginator_and_collection_for(collection_id, options)
end
validate_options!メソッドはoptionsへのデフォルト値のマー...
次に、paginator_and_collection_forメソッドです。
def paginator_and_collection_for(collection_id, options)...
klass = options[:class_name].constantize
page = params[options[:parameter]]
count = count_collection_for_pagination(klass, options)
paginator = Paginator.new(self, count, options[:per_pa...
collection = find_collection_for_pagination(klass, opt...
return paginator, collection
end
options[:parameter]は'page'なので、リクエスト中のpage=nを...
count_collection_for_paginationメソッドです。どうやらここ...
def count_collection_for_pagination(model, options)
model.count(:conditions => options[:conditions],
:joins => options[:join] || options[:joins],
:include => options[:include],
:select => options[:count])
end
*ActiveRecord::Base [#r1f7821f]
**ActiveRecord::Base.count ($GEM_HOME/gems/activerecord/l...
countメソッドです。
def count(*args)
calculate(:count, *construct_count_options_from_legacy...
end
続いてcalculateメソッドです。
def calculate(operation, column_name, options = {})
validate_calculation_options(operation, options)
column_name = options[:select] if options[:select]
column_name = '*' if column_name == :all
column = column_for column_name
catch :invalid_query do
if options[:group]
return execute_grouped_calculation(operation, colu...
else
return execute_simple_calculation(operation, colum...
end
end
0
end
column_nameは'*'になります。そのため、columnは空配列です...
def execute_simple_calculation(operation, column_name, c...
value = connection.select_value(construct_calculation_...
type_cast_calculated_value(value, column, operation)
end
construct_calculation_sqlメソッドは淡々とSQLを構築してい...
**ActiveRecord::Base.connection ($GEM_HOME/gems/activerec...
connectionメソッドは前回も見たconnection_specification.rb...
def connection
if @active_connection_name && (conn = active_connectio...
conn
else
# retrieve_connection sets the cache key.
conn = retrieve_connection
active_connections[@active_connection_name] = conn
end
end
end
実際にデータベースと通信する必要があるまで接続しないとい...
def self.retrieve_connection #:nodoc:
# Name is nil if establish_connection hasn't been call...
# some class along the inheritance chain up to AR::Bas...
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
接続はまだないのでelsifの方が実行されます。どうにも混乱す...
def self.connection=(spec) #:nodoc:
if spec.kind_of?(ActiveRecord::ConnectionAdapters::Abs...
active_connections[name] = spec
elsif spec.kind_of?(ConnectionSpecification)
config = spec.config.reverse_merge(:allow_concurrenc...
self.connection = self.send(spec.adapter_method, con...
elsif spec.nil?
raise ConnectionNotEstablished
else
establish_connection spec
end
end
specはConnectionSpecificationのためadapter_methodが呼ばれ...
**ActiveRecord::Base.columns ($GEM_HOME/gems/activerecord...
それでは予告通りcolumnsメソッドです。
def columns
unless @columns
@columns = connection.columns(table_name, "#{name} C...
@columns.each {|column| column.primary = column.name...
end
@columns
end
table_nameメソッドに進みましょう。
def table_name
reset_table_name
end
def reset_table_name #:nodoc:
base = base_class
name =
# STI subclasses always use their superclass' table.
unless self == base
base.table_name
else
# Nested classes are prefixed with singular parent...
if parent < ActiveRecord::Base && !parent.abstract...
contained = parent.table_name
contained = contained.singularize if parent.plur...
contained << '_'
end
name = "#{table_name_prefix}#{contained}#{undecora...
end
set_table_name(name)
name
end
base_classメソッドはActiveRecord::Baseを直接継承している...
def undecorated_table_name(class_name = base_class.name)
table_name = Inflector.underscore(Inflector.demodulize...
table_name = Inflector.pluralize(table_name) if plural...
table_name
end
Inflectorの各メソッドは$GEM_HOME/gems/activesupport/lib/a...
*ActionView::Base [#k6ced6da]
今までのところで表示するための情報は取得できた気がするの...
def render_scaffold(action=nil)
if template_exists?("#{self.class.controller_path}/#{a...
render :action => action
else
@scaffold_class = Outgo
@scaffold_singular_name, @scaffold_plural_name = "ou...
@scaffold_suffix = ""
add_instance_variables_to_assigns
@template.instance_variable_set("@content_for_layout...
if !active_layout.nil?
render :file => active_layout, :use_full_path => t...
else
render :file => scaffold_path('layout')
end
end
end
def scaffold_path(template_name)
File.dirname(__FILE__) + "/templates/scaffolds/" + tem...
end
まず初めのif。アプリケーションディレクトリ/app/views/list...
その後いろいろ設定を行い、デフォルトのテンプレートを使っ...
**ActionView::Base#render_file ($GEM_HOME/gems/actionpack...
それではrender_fileメソッドです。渡される引数を考慮すると...
template_file_name = template_path
template_extension = template_path.split('.').last
template_source = nil # Don't read the source until we k...
render_template(template_extension, template_source, tem...
render_templateメソッドです。
def render_template(template_extension, template, file_p...
if handler = @@template_handlers[template_extension]
template ||= read_template_file(file_path, template_...
delegate_render(handler, template, local_assigns)
else
compile_and_render_template(template_extension, temp...
end
end
template_handlerを登録した覚えはないのでelseのcompile_and...
def compile_and_render_template(extension, template = ni...
# convert string keys to symbols if requested
local_assigns = local_assigns.symbolize_keys if @@loca...
# compile the given template, if necessary
if compile_template?(template, file_path, local_assigns)
template ||= read_template_file(file_path, extension)
compile_template(extension, template, file_path, loc...
end
# Get the method name for this template and run it
method_name = @@method_names[file_path || template]
evaluate_assigns
send(method_name, local_assigns) do |*name|
instance_variable_get "@content_for_#{name.first || ...
end
end
初回ということでcompile_templateメソッドに進みます。長い...
def compile_template(extension, template, file_name, loc...
render_symbol = assign_method_name(extension, template...
render_source = create_template_source(extension, temp...
line_offset = @@template_args[render_symbol].size
CompiledTemplates.module_eval(render_source, file_name...
@@compile_time[render_symbol] = Time.now
end
assign_method_nameメソッドはファイル名などからキーを生成...
次のcreate_template_sourceメソッドは・・・また変なことを...
def create_template_source(extension, template, render_s...
body = ERB.new(template, nil, @@erb_trim_mode).src
@@template_args[render_symbol] ||= {}
locals_keys = @@template_args[render_symbol].keys | lo...
@@template_args[render_symbol] = locals_keys.inject({}...
locals_code = ""
locals_keys.each do |key|
locals_code << "#{key} = local_assigns[:#{key}]\n"
end
"def #{render_symbol}(local_assigns)\n#{locals_code}#{...
end
ふうむ、上のメソッドで何でsendなんてしてるんだろうと思っ...
*おわりに [#db682088]
今回はRuby on Railsのうち、リクエストが来たときにどのよう...
-これぐらいの規模になると、ある設定がどこでされてるのかが...
-オンデマンド設定が加わってくると一層わかりづらくなる
-動的に書かれるコードが多い
といったところです。
それではみなさんもよいコードリーディングを。
ページ名: