はじめに †Ruby on Rails/サーバ起動時の処理を読むでは、WEBrickサーバが起動するまでを読みました。それでは、 http://localhost:3000/accountbook/ というhttpリクエストを受け取った場合にどのような処理が行われるかを見ていくことにしましょう。 DispatcherServlet($GEM_HOME/gems/lib/rails/webrick_server.rb) †WEBrickサーブレットはサーバからserviceメソッドを呼ばれます。serviceメソッドでは以下の処理が行われています。
handle_dispatchメソッドの前半部分は以下のようになっています。 def handle_dispatch(req, res, origin = nil) #:nodoc: data = StringIO.new Dispatcher.dispatch( CGI.new("query", create_env_table(req, origin), StringIO.new(req.body || "")), ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, data ) Dispatcher($GEM_HOME/gems/rails/lib/dispatcher.rb) †Dispatcher.dispatchメソッドです。条件分岐や例外処理を省略すると以下の処理が実行されます。 request, response = ActionController::CgiRequest.new(cgi, session_options), ActionController::CgiResponse.new(cgi) prepare_application controller = ActionController::Routing::Routes.recognize(request) controller.process(request, response).out(output) reset_after_dispatch ActionController::CgiRequest, CgiResponseはそれぞれ、ActionController::AbstractRequest, AbstractResponseから派生しているようです。CgiRequest, CgiResponseは$GEM_HOME/gems/lib/actionpack/lib/action_controller/cgi_process.rb、AbstractRequestは同じディレクトリのrequest.rb、AbstractResponseは同じディレクトリのresponse.rbにそれぞれ定義されています。 Dispatcher.prepare_application †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_defined?(:ApplicationController) ActiveRecord::Base.verify_active_connections! if defined?(ActiveRecord) run_preparation_callbacks end Dependencies.load?がtrueの場合は毎回ルーティング処理をやり直しているようです。Dependencies.load?メソッドはスクリプト読み込みの方法がloadの場合trueです。アプリケーションディレクトリにあるconfig/environments/development.rbを見ると以下のようになっています。ちなみに、test.rbやproduction.rbはtrueになっているのでリクエストごとに再読込は行われません。 # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development # since you don't have to restart the webserver when you make code changes. config.cache_classes = false 読み込み方法がいつ設定されるかというとRails::Initialize#processメソッドから呼ばれるinitialize_dependency_mechanismメソッドで設定されます。 def initialize_dependency_mechanism Dependencies.mechanism = configuration.cache_classes ? :require : :load end 次にブレークポイントの設定を行っていますがとりあえず無視することにします。 次にapplication.rbが読み込まれています。ApplicationControllerはアプリケーションディレクトリのapp/controllers/application.rbに定義されているクラスです。require_dependencyメソッドは$GEM_HOME/gems/activesupport/lib/active_support/dependencies.rbに定義されており、ファイルを読み込むことで新しく定義された定数の検出などいろいろ興味深いのですが長くなるので止めます(説明するのがめんどくさいとも言う:-)) 次のActiveRecord::Base.verify_active_connections!は、まだ接続がないはずなので何もしないはずです。 コールバックも登録してないはずなので無視しましょう。 ActionController::Routing::Routes.recognize ($GEM_HOME/gems/action_pack/lib/action_controller/routing.rb) †それではリクエストからコントローラを引き当てているらしいActionController::Routing::Routes.recognizeに移ります。ActionController::Routing::Routesは前回見たようにActionController::Routing::RouteSetオブジェクトです。 def recognize(request) params = recognize_path(request.path, extract_request_environment(request)) request.path_parameters = params.with_indifferent_access "#{params[:controller].camelize}Controller".constantize end request.pathは要求されたパスでhttp://localhost:3000/accountbook/にアクセスしたとしたら"/accountbook/"となります。またextract_request_environmentメソッドはリクエスト情報からリクエストメソッドだけを取り出しているようです(プラグインとかのオーバーライドを想定しているようです)。 recongnize_pathメソッドです。前回設定したroutes(ActionController::Routing::Routeオブジェクトの配列)からリクエストパスに反応するものを検索しています。 def recognize_path(path, environment={}) path = CGI.unescape(path) routes.each do |route| result = route.recognize(path, environment) and return result end raise RoutingError, "no route found to match #{path.inspect} with #{environment.inspect}" end ActionController::Routing::Route#rocognizeメソッドです。無限再帰? def recognize(path, environment={}) write_recognition recognize path, environment end ActionController::Routing::Route#write_recognition †write_recognitionメソッドに進みましょう。 def write_recognition # Create an if structure to extract the params from a match if it occurs. body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams" body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend" # Build the method declaration and compile it method_decl = "def recognize(path, env={})\n#{body}\nend" instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" method_decl end どうやらrecognizeメソッドは動的に書かれているようです。動的に書かれるrecognizeメソッドは以下のようになります。 def recognize(path, env={}) if (match = #{Regexp.new(recognition_pattern).inspect}.match(path)) 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(の親クラスのStaticSegment)のbuild_patternメソッドは以下のようになっています。 def build_pattern(pattern) escaped = Regexp.escape(value) if optional? && ! pattern.empty? "(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})" elsif optional? Regexp.optionalize escaped else escaped + pattern end end DynamicSegmentのbuild_patternメソッドです。ControllerSegmentも同じものが使われます。 def build_pattern(pattern) chunk = regexp_chunk chunk = "(#{chunk})" if Regexp.new(chunk).number_of_captures == 0 pattern = "#{chunk}#{pattern}" optional? ? Regexp.optionalize(pattern) : pattern end def regexp_chunk regexp ? "(#{regexp.source})" : "([^#{Routing::SEPARATORS.join}]+)" end ControllerSegmentではregexp_chunkメソッドがオーバーライドされています。Routing.possible_controllersメソッドはアプリケーションディレクトリにあるapp/controllers内に「何とか_controller.rb」があるとしたら「何とか」が返ってくることになります。 def regexp_chunk possible_names = Routing.possible_controllers.collect { |name| Regexp.escape name } "(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))" end 順を追って正規表現を構築してみましょう。
ん〜、見てると気分悪くなりますが、確認してみましょう。
合っているようです。 ActionController::Routing::Route#recognition_extraction †次に、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).number_of_captures x end extraction.compact end 各segmentについてどうなるかを見ていきましょう。
以上のことから動的に書かれたrecognizeメソッドは以下のようになります。 def recognize(path, env={}) if (match = /\A(?:(?:/)?\Z|/((?i-:(accountbook)))(?:(?:/)?\Z|/([^/;.,?]+)(?:(?:/)?\Z|/([^/;.,?]+)(?:/)?))\Z/.match(path)) 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/activesupport/lib/activesupport/dependencies.rb) †結果、ActionController::Routing::Routes.recognizeメソッドの戻り値としてユーザ定義のAccountbookControllerが返されます。 ・・・ん?AccountbookControllerっていつの間に読み込まれたのでしょうか?今まで見てきた中でaccountbook_controller.rbを読み込んでいるところは見かけなかったのですが。 dependencies.rbが怪しいと思ったのですがどう呼ばれているかさっぱりわからないので実際の読み込みを行っているload_fileメソッドに罠(?)を仕掛けてみました。 def load_file(path, const_paths = loadable_constants_for_path(path)) p path p caller で引っかかったのが、 "script/../config/../app/controllers/accountbook_controller.rb" [".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:94:in `require_or_load'", ".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:250:in `load_missing_constant'", ".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:454:in `const_missing'", ".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:466:in `const_missing'", ".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/inflector.rb:250:in `constantize'", ".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/core_ext/string/inflections.rb:148:in `constantize'", ".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/routing.rb:1284:in `recognize'" いつの間にかconst_missingなんてできたんですねぇ。ともかく、const_missingの仕組みとRubyのクラス名⇔ファイル名マッピング規則により、AccountbookControllerが要求されたときに自動的にaccountbook_controller.rbが読み込まれているようです。 ActionController::Base.scaffold ($GEM_HOME/gems/actionpack/lib/action_controller/scaffolding.rb) †一番単純なRailsアプリケーションは以下のようになっています。 app/controllers/application.rb class ApplicationController < ActionController::Base # Pick a unique cookie name to distinguish our session data from others' session :session_key => '_accountbook_session_id' end app/controllers/accountbook_controller.rb class AccountbookController < ApplicationController scaffold :outgo end クラス定義中でscaffoldメソッドを使用するとhttp://localhost:3000/accountbook/にアクセスすることでoutgosテーブルの内容が表示されるようになります。ActionController::Baseに移る前にscaffoldメソッドについて見ていきましょう。 まず前半部分 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.camelize plural_name = singular_name.pluralize suffix = options[:suffix] ? "_#{singular_name}" : "" optionsは指定しないので、
ということになります。次に、options[:suffix]は定義していないのでindexメソッドが定義されます。 def index list end その後、各種メソッドが定義されています。長いのでlistメソッドおよび関連メソッドだけ載せます。 def list @outgo_pages, @outgos = paginate :outgos, :per_page => 10 render_scaffold "list" end def render_scaffold(action=nil) action ||= caller_method_name(caller) # logger.info ("testing template:" + "#{self.class.controller_path}/#{action}") if logger if template_exists?("#{self.class.controller_path}/#{action}") render :action => action else @scaffold_class = Outgo @scaffold_singular_name, @scaffold_plural_name = "outgo", "outgos" @scaffold_suffix = "" add_instance_variables_to_assigns @template.instance_variable_set("@content_for_layout", @template.render_file(scaffold_path(action.sub(/$/, "")), false)) if !active_layout.nil? render :file => active_layout, :use_full_path => true else render :file => scaffold_path('layout') end end end def scaffold_path(template_name) File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml" end 以上をふまえた上でActionController::Base.processメソッドを見ていくことにしましょう。 ActionController::Base ($GEM_HOME/gems/actionpack/lib/action_controller/base.rb) †まず、ActionController::Base.processメソッドです。オブジェクトを生成して同名のインスタンスメソッドを呼び出しています。 def process(request, response) #:nodoc: new.process(request, response) end インスタンスメソッドの方のprocessです。 def process(request, response, method = :perform_action, *arguments) #:nodoc: 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 †まず、initialize_template_classメソッドです。 def initialize_template_class(response) raise "You must assign a template class through ActionController.template_class= before processing a request" unless @@template_class response.template = self.class.view_class.new(self.class.view_root, {}, self) response.redirected_to = nil @performed_render = @performed_redirect = false end @@template_classはいつ設定されたんだ?と思ったところ、トップレベルのaction_controller.rbが読み込まれたときに設定されていました。 ActionController::Base.template_class = ActionView::Base action_controller.rbを見たついでに、ActionControllerおよびActionView, ActiveRecordは様々なメソッドを別ファイルに定義しておいてBaseクラスにincludeするというスタイルのようです。 view_classメソッドです。 def self.view_class @view_class ||= # create a new class based on the default template class and include helper methods returning Class.new(ActionView::Base) do |view_class| view_class.send(:include, master_helper_module) end end master_helper_moduleはhelper.rbで定義されています。ここではまだアプリケーションディレクトリにあるapp/helpers/*_helper.rbが取り込まれるわけではなさそうです。 次に、view_rootメソッドですがtemplate_rootメソッドを参照しています。これまたどこで設定されたんだろう?とgrepをかけたところ、Rails::Initializer#initialize_framework_viewsメソッドで設定されていました。 def initialize_framework_views for framework in ([ :action_controller, :action_mailer ] & configuration.frameworks) framework.to_s.camelize.constantize.const_get("Base").template_root ||= configuration.view_path end end view_pathはアプリケーションディレクトリ/app/viewsです。 ActionController::Base#assign_names †processメソッドに戻って、次のassign_shotcutsメソッドではその名の通りrequest, responseの各メンバーに対してのショートカットアクセス方法を設定しています。 次のinitialize_current_urlメソッドではUrlRewriterオブジェクトを生成しています。 次のassign_namesメソッドはまじめに見ましょう。先ほどコントローラを引き当てたときに設定されたアクションを設定しています。って、http://localhost:3000/accountbook/とアクセスするはずなのでparams['action']はnilです。結果、@action_nameとして'index'が設定されることになります。 def assign_names @action_name = (params['action'] || 'index') end ActionController::Base#perform_action †さて、perform_actionメソッドです。いろいろ条件分岐していますが一番上だけ見ればいいはずです。 def perform_action if self.class.action_methods.include?(action_name) send(action_name) render unless performed? action_methodsはActionController::Baseのサブクラスで定義されたメソッドです。action_nameが'index'でscaffoldメソッドによりAccountbookControllerにはindexメソッドが定義されているのでindexメソッドが呼ばれることになります。 ActionView::Base.paginate ($GEM_HOME/gems/actionpack/lib/action_controller/pagination.rb) †さて、indexメソッドですがlistメソッドを呼んでいるだけなのでlistメソッドを再掲します。 def list @outgo_pages, @outgos = paginate :outgos, :per_page => 10 render_scaffold "list" end paginateメソッドに進みましょう。定義されているファイルはセクションのところに書いてある通りです。 def paginate(collection_id, options={}) Pagination.validate_options!(collection_id, options, true) paginator_and_collection_for(collection_id, options) end validate_options!メソッドはoptionsへのデフォルト値のマージ、collecion_idから生成される単数系名、クラス名を設定しています。Paginationが変更されるわけではないので!ではないようなという気がします。 次に、paginator_and_collection_forメソッドです。 def paginator_and_collection_for(collection_id, options) #:nodoc: klass = options[:class_name].constantize page = params[options[:parameter]] count = count_collection_for_pagination(klass, options) paginator = Paginator.new(self, count, options[:per_page], page) collection = find_collection_for_pagination(klass, options, paginator) return paginator, collection end options[:parameter]は'page'なので、リクエスト中のpage=nを取得することになりますが今回はnilです。 count_collection_for_paginationメソッドです。どうやらここからはActiveRecordに舞台が移るようです。 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 †ActiveRecord::Base.count ($GEM_HOME/gems/activerecord/lib/active_record/calcuration.rb) †countメソッドです。 def count(*args) calculate(:count, *construct_count_options_from_legacy_args(*args)) 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, column_name, column, options) else return execute_simple_calculation(operation, column_name, column, options) end end 0 end column_nameは'*'になります。そのため、columnは空配列です。column_forメソッド中でcolumnsメソッドが呼ばれてテーブルの列情報を取得しているようですが後で見ることにします。options[:group]は指定していないのでexecute_simple_calculationメソッドに進みます。それにしてもcatchが使われてるのって始めてみました:-) def execute_simple_calculation(operation, column_name, column, options) #:nodoc: value = connection.select_value(construct_calculation_sql(operation, column_name, options)) type_cast_calculated_value(value, column, operation) end construct_calculation_sqlメソッドは淡々とSQLを構築しています。それではデータベースとの通信部分に移りましょう。 ActiveRecord::Base.connection ($GEM_HOME/gems/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb) †connectionメソッドは前回も見たconnection_specification.rbに定義されています。 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 end 実際にデータベースと通信する必要があるまで接続しないという処理が行われています。続いてretrieve_connectionメソッドです。 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 接続はまだないのでelsifの方が実行されます。どうにも混乱するのですが、nameはActiveRecord::Baseです。なので、すぐ下にあるconnection=メソッドが呼ばれます(本当はquery_cache.rbでActiveRecord::Base.connection=が再定義されてそっち経由で呼ばれているようですが紛らわしいので)。 def self.connection=(spec) #:nodoc: if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter) active_connections[name] = spec elsif spec.kind_of?(ConnectionSpecification) config = spec.config.reverse_merge(:allow_concurrency => @@allow_concurrency) self.connection = self.send(spec.adapter_method, config) elsif spec.nil? raise ConnectionNotEstablished else establish_connection spec end end specはConnectionSpecificationのためadapter_methodが呼ばれることになります。これで実際のDBMSとの接続が行われます。その結果をconnectionに代入して・・・読む身となっては:-<な書き方ですね。上のactive_connections[name] = specが実行されるので無限再帰ではありません。 ActiveRecord::Base.columns ($GEM_HOME/gems/activerecord/lib/active_record/base.rb) †それでは予告通りcolumnsメソッドです。 def columns unless @columns @columns = connection.columns(table_name, "#{name} Columns") @columns.each {|column| column.primary = column.name == primary_key} 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 table name. if parent < ActiveRecord::Base && !parent.abstract_class? contained = parent.table_name contained = contained.singularize if parent.pluralize_table_names contained << '_' end name = "#{table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}" end set_table_name(name) name end base_classメソッドはActiveRecord::Baseを直接継承しているクラスを返します。今回の場合はOutgoです。次に、parentはObjectなはずなのでifの中身は実行されずundecorated_table_nameメソッドに処理が移ります。 def undecorated_table_name(class_name = base_class.name) table_name = Inflector.underscore(Inflector.demodulize(class_name)) table_name = Inflector.pluralize(table_name) if pluralize_table_names table_name end Inflectorの各メソッドは$GEM_HOME/gems/activesupport/lib/active_support/inflector.rbを参照してください。ともかくこれでクラスOutgoからデータを取得するテーブル名outgosが取得できました。 ActionView::Base †今までのところで表示するための情報は取得できた気がするので次は実際に表示している部分を見ることにしましょう。以下はscaffoldメソッドによって自動生成されるメソッドです。しばらく間が開いているので念のために書いておくとAccountbookControllerクラスに定義されています。 def render_scaffold(action=nil) if template_exists?("#{self.class.controller_path}/#{action}") render :action => action else @scaffold_class = Outgo @scaffold_singular_name, @scaffold_plural_name = "outgo", "outgos" @scaffold_suffix = "" add_instance_variables_to_assigns @template.instance_variable_set("@content_for_layout", @template.render_file(scaffold_path(action.sub(/$/, "")), false)) if !active_layout.nil? render :file => active_layout, :use_full_path => true else render :file => scaffold_path('layout') end end end def scaffold_path(template_name) File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml" end まず初めのif。アプリケーションディレクトリ/app/views/listなんとかはないのでelseに進みます。なんとかと言っているのはちゃんとわけがあるので興味がある方はご覧ください。 その後いろいろ設定を行い、デフォルトのテンプレートを使ってページの生成を行っています。なお、active_layoutはnilなのでelseの方が実行されます。 ActionView::Base#render_file ($GEM_HOME/gems/actionpack/lib/action_view/base.rb) †それではrender_fileメソッドです。渡される引数を考慮すると以下の部分が実行されます。local_assignsは第3引数でデフォルトは空ハッシュです。 template_file_name = template_path template_extension = template_path.split('.').last template_source = nil # Don't read the source until we know that it is required render_template(template_extension, template_source, template_file_name, local_assigns) render_templateメソッドです。 def render_template(template_extension, template, file_path = nil, local_assigns = {}) #:nodoc: if handler = @@template_handlers[template_extension] template ||= read_template_file(file_path, template_extension) # Make sure that a lazyily-read template is loaded. delegate_render(handler, template, local_assigns) else compile_and_render_template(template_extension, template, file_path, local_assigns) end end template_handlerを登録した覚えはないのでelseのcompile_and_render_templateメソッドに進みましょう。 def compile_and_render_template(extension, template = nil, file_path = nil, local_assigns = {}) #:nodoc: # convert string keys to symbols if requested local_assigns = local_assigns.symbolize_keys if @@local_assigns_support_string_keys # 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, local_assigns) 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 || 'layout'}" end end 初回ということでcompile_templateメソッドに進みます。長いので通らないところとか例外処理とか省略。 def compile_template(extension, template, file_name, local_assigns) render_symbol = assign_method_name(extension, template, file_name) render_source = create_template_source(extension, template, render_symbol, local_assigns.keys) line_offset = @@template_args[render_symbol].size CompiledTemplates.module_eval(render_source, file_name, -line_offset) @@compile_time[render_symbol] = Time.now end assign_method_nameメソッドはファイル名などからキーを生成しています。 次のcreate_template_sourceメソッドは・・・また変なことをやらかしてますね。例によって通らないところ省略です。 def create_template_source(extension, template, render_symbol, locals) body = ERB.new(template, nil, @@erb_trim_mode).src @@template_args[render_symbol] ||= {} locals_keys = @@template_args[render_symbol].keys | locals @@template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h } locals_code = "" locals_keys.each do |key| locals_code << "#{key} = local_assigns[:#{key}]\n" end "def #{render_symbol}(local_assigns)\n#{locals_code}#{body}\nend" end ふうむ、上のメソッドで何でsendなんてしてるんだろうと思ったらまた動的にメソッド書いてるんですね。ともかくこれでページが書かれました。 おわりに †今回はRuby on Railsのうち、リクエストが来たときにどのような処理が行われるかを見ていきました。感想としては、
といったところです。 それではみなさんもよいコードリーディングを。 |