script/generateで作られるビューファイルの拡張子が、Rails2.0では".html.erb"になる件
会社の同僚が・・・というか自分も含めて、今回のお仕事ではRailsを使うことになったわけだが。
本を読んでまじめに勉強している同僚が、「ビューの拡張子が.html.erbとかになってるけど、本とかWebの記事だと.rhtmlになってる。なんで?」
とか言ってきたので調べてみる。
結論としては以下のURLにあるとおり、Rails2.0以降では複数のビューエンジンに対応できるようにする為、拡張子でもってテンプレートエンジンを切り替えられる構造にしている。で、現行のデフォルトがerb。
ともあれ、そのためにscaffoldやcontrollerでの生成でデフォルトが".html.erb"に切り替えられたようである。
第4回 テンプレートファイルの拡張子 - Ruby on Rails 2.0 日記 - Ruby on Rails with OIAX
参考:
ActionView::Base
以下は、なんか説明だけじゃ満足できなかったのでソースを追った課程。
まず今日のエントリにもあるとおり、自分はローカルの環境にRailsの1.2.6を入れている。ということで、1.2.6の方のViewがどうなっているかあたってみた。
2.0.2の方もあるのでかなり端折ります。
actionpack-1.13.6/lib/action_view/base.rb:
require 'erb' # ←eRubyのPureRuby実装であるerbを使用している。 module ActionView #:nodoc: #... class Base # ←ActionView::Base include ERB::Util # ←やっぱりerbだ! #... def render(...) #... render_template(options[:type] || :rhtml, options[:inline], nil, options[:locals] || {}) # ↑options[:type]のデフォルトが:rhtmlになっている。恐らくここから始まる。 #... end def render_template(template_extension, ...) #... # ↓render_templateでoptions[:type]で指定されたのがtemplate_extentsion. compile_and_render_template(template_extension, template, file_path, local_assigns) #... end def compile_and_render_template(extension, ...) #... compile_template(extension, template, file_path, local_assigns) # ↑まだまだ続く・・・ #... end #... private #... def create_template_source(extension, template, render_symbol, locals) # ↓ようやく発見 if template_requires_setup?(extension) body = case extension.to_sym when :rxml "controller.response.content_type ||= 'application/xml'\n" + "xml ||= Builder::XmlMarkup.new(:indent => 2)\n" + template + "\nxml.target!\n" when :rjs "controller.response.content_type ||= 'text/javascript'\n" + "update_page do |page|\n#{template}\nend" end else body = ERB.new(template, nil, @@erb_trim_mode).src end #... end
・・・という具合に、結局ファイルの拡張子で2種類+それ以外はERBと処理を振り分けているのが分かった。
- .rxml → Builder::XmlMarkupが処理する。
- .rjs → うー・・・よく分からないんだけど、JavaScript?
- それ以外(含.rhtml) → ERBに任せる。
ちなみに、ではscript/generatorをcontroller+action名付で実行された時に、rails-1.2.6でaction名.rhtmlを生成しているのはどこかというと
rails-1.2.6/lib/rails_generator/generators/components/controller/controller_generator.rb:
class ControllerGenerator < Rails::Generator::NamedBase def manifest record do |m| #... # View template for each action. actions.each do |action| path = File.join('app/views', class_path, file_name, "#{action}.rhtml") m.template 'view.rhtml', path, :assigns => { :action => action, :path => path } end end end end
という形で、".rhtml"というのを生成している箇所を突き止めることができた。
続いてrails-2.0.2の方を行きます。こちらも同様に、ActionView::Baseクラスから始まります。
rails-2.0.2/vendor/rails/actionpack/lib/action_view/base.rb:
# base.rbにはERBのrequireは見あたらない・・・。 module ActionView #:nodoc: #... class Base # ←ActionView::Base #... def render(...) #... render_template(options[:type], options[:inline], nil, options[:locals]) #... end def render_template(template_extension, template, file_path = nil, local_assigns = {}) #:nodoc: handler = self.class.handler_for_extension(template_extension) if template_handler_is_compilable?(handler) compile_and_render_template(handler, template, file_path, local_assigns) else template ||= read_template_file(file_path, template_extension) # Make sure that a lazyily-read template is loaded. delegate_render(handler, template, local_assigns) end end
ここで一旦止まり、handler入れる値のhandler_for_extensionを見てみます。引数はテンプレートの拡張子っぽいです。
同じクラス内に定義されています。
def self.handler_for_extension(extension) (extension && @@template_handlers[extension.to_sym]) || @@default_template_handlers end
クラス変数のtemplate_handlersのハッシュに、拡張子をキーで引き当てています。無ければdefault_template_handlersクラス変数が返されています。
すると、このdefのすぐ下に見つかります。
register_default_template_handler :erb, TemplateHandlers::ERB register_template_handler :rjs, TemplateHandlers::RJS register_template_handler :builder, TemplateHandlers::Builder # TODO: Depreciate old template extensions register_template_handler :rhtml, TemplateHandlers::ERB register_template_handler :rxml, TemplateHandlers::Builder
見つけました。.erbや.rhtmlなどに応じたTemplateHandlersのクラスが返されています。少し戻って、render_templateメソッドでこれによりTemplateHandlersのクラス名がセットされたhandler変数は、この後長い旅をしてActionView::Base#delegate_compileメソッドでようやく実行されます。
def delegate_compile(handler, template) handler.new(self).compile(template) end
さて、ではTemplateHandlers::ERBはどこかというと
rails-2.0.2/vendor/rails/actionpack/lib/action_view/template_handlers/erb.rb:
require 'erb' class ERB module Util HTML_ESCAPE = { '&' => '&', '"' => '"', '>' => '>', '<' => '<' } def html_escape(s) s.to_s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] } end end end module ActionView module TemplateHandlers class ERB < TemplateHandler def compile(template) ::ERB.new(template, nil, @view.erb_trim_mode).src end end end end
という質素な作りになっています。ここでようやくピンポイントでerbを使うようになりました。
そしてscript/generatorの方も・・・
#rails-2.0.2/vendor/rails/railties/lib/rails_generator/ # generators/components/controller/controller_generator.rb # class ControllerGenerator < Rails::Generator::NamedBase #... actions.each do |action| path = File.join('app/views', class_path, file_name, "#{action}.html.erb") #... end
ようやく見つけました。rails-1.2.6と同じ箇所で、単純に拡張子を".html.erb"にしています。
長い旅でしたが、ようやくRailsの1.2系と2.0系でのViewの変更点の一部、デフォルトの拡張子の変更箇所を特定できました。
( ´ー`)フゥー...