Railsソース読解(九)ActionView::Base_ユーザ要求railsにおける処理フロー(4)
13816 ワード
Railsソース読解(九)ActionView::Base_ユーザ要求railsにおける処理フロー(4)
せつぞく
ActionControllerでは@template.renderを使用してページ内容を生成します.この@templateはActionView::Base.newから出てきた例です.
ActionControllerの具体的なコード:
分析:
#1 controllerはviewのインスタンス(@template)を保持します.
newメソッドとActionView::Baseのnewパラメータに従って、viewもcontrollerのインスタンスを持っています.
ActionView::Baseの初期化方法:
#2 self、すなわち現在のcontrollerのインスタンスがviewに転送される
このように結論があります:viewとcontrollerは互いに相手の実例を持っていて、道理でVCが家を分けないと言っています(一緒に置いたaction_pack)
#3 ActionViewのview_pathsはcontrollerによって伝達され、クラスのview_pathsメソッド、すなわち共通パスによって伝達される.
コントロールにはviewのインスタンスがあるので、コントロールにはviewのview_pathsに含まれるパスを変更できます.
たとえば、Controllerのインスタンスメソッド:
プロシージャはclassのview_pathsをコピーし、このインスタンスのviewファイルとしてパスを探します.ここでは、view_pathsパスの先頭に追加する方法のみが表示されます.
ActionView::Baseでrenderの実行
つまりerbページの生成プロセスです.このプロセスが複雑なのは、パラメータが多いこと、テンプレートを使用すること、helperを使用することなどです.
renderのコード分析:
レンダリングに関係なく、最終的にはtemplateのrender_templateメソッドが呼び出されます.
この方法では、対応するviewテンプレートプロセッサhandler(例えばERBのhandler)を見つけ、compile(template)メソッドを呼び出し、必要な文字列を返し、処理した後、最終的な結果を返します.
ActionView::Base#render、layoutの使い方などを詳しく分析
1)#:file
template = self.view_paths.find_template(options[:file], template_format)
template.render_template(self, options[:locals])
ActionView::Templateのrender_templateメソッドが呼び出され、このメソッドはまた調整されます.
ActionView::Renderableのrender(view,local_assigns)を使用し、compile(local_assigns)を使用し、
続いてcompile!(render_symbol,local_assigns)を使用しました
compile!(render_symbol,local_assigns)ここでいろいろやった(略)結果、正しいhandler(erbにとって)がviewファイルの内容を処理しているのを見つけて、
結果のerb.srcで生成されたコード+local_assignsコードを合わせてsourceとし、
ActionView::Base::CompiledTemplates.module_eval(source,filename,0)として使用
ActionView::Base::CompilledTemplatesはActionView Baseで定義された過剰モジュールであり、このモジュールに含まれる方
メソッド(上module_eval)はincludeによってActionViewベースに入力されます
このように、local_assignsのコンテンツは、最終的に方法内変数として使用され、グローバル変数の干渉を回避する.
compile!の詳細:
local_assignsの変数など、erb解析ファイルのsrc文字列の内容などを含むメソッド文字列が生成されます.
例:
実験の一例1:
生成されたsourceの内容、文字列:
「 def _run_erb_app47views47users47index46html46erb_locals_xxx_xxx_a_xxx_xxx_b(local_assigns) old_output_buffer = output_buffer;xxx_xxx_a = local_assigns[:xxx_xxx_a];xxx_xxx_b = local_assigns[:xxx_xxx_b];;@output_buffer = ''; __in_erb_template=true ; @output_buffer.concat "users list"; @output_buffer ensure self.output_buffer = old_output_buffer end "
整理して、文字列の包装を取り除いて、最終的にきれいな形式は:
私が自分で実験した例2:
生成されたsourceの内容:
だいたい同じで貼らない
整理して、最終的にきれいな形式は:
生成されたメソッド名は、どのように規定されていますか?(47は/,46は.)
生成メソッドはいつ使用されますか?ActionView::Renderable#renderメソッド:
view.send(method_name(local_assigns)、local_assigns)do|*names|#ここで使います!!!
ここではsendで生成方法を呼び出した.
ここに&blockがあります.生成方法にyieldがあれば、このblockは実行されます.そうしないと実行されません.
上記の例を見ると、例1にはyieldがなく、blockがヒットせず、方法のみが実行される.
例2にはyieldがあり,メソッドだけでなくブロックも実行される.
さらに分析
layoutで使用するyieldにパラメータがない場合(yield)、blockが受信したパラメータ*namesが空になります.
ivar=:"@content_for_#{names.first|:layout}"#ivarデフォルトではlayout文字列を使用して@content_for_layoutを生成します
@content_for_layoutというインスタンス変数の値がデフォルトで返され、bufferに戻ります.
; @output_buffer.concat(( yield ).to_s)
yieldがパラメータを使用している場合、例えばyield:body_leftを使用している場合、erbが生成したsrcはこのようになります.
; @output_buffer.concat(( yield :body_left ).to_s)
このようにblockのパラメータ個数は1であり、names.firstは:body_left#であり、ivarは@content_for_body_leftである
このときview.instance_variable_get(ivar)が実行され、戻り値がyield:body_leftの位置に挿入されます.
リボン:
#1:fileの内容はcompile!でerbで処理してsrcコードを返し、その後module_evalで実行し、得られた結果は@output_bufferに格納されます.
2 layoutを使用していません.実はrender:fileはviewテンプレートを解析して実行し、役割は単一で、layoutというものを区別していません.layoutは制御側のことで、必要に応じてカスタマイズします.
#3 layoutでyield(:xxx=:layout)の位置は、インスタンス変数@content_for_xxxで置き換えられます
たとえば、ページに内容<%@content_for_body_left="I am Fantaxy!"%>がある場合、
では、「I am Fantaxy!」はyield:body_leftの位置を挿入します.(インスタンス変数に注意)
2)#:layout
レンダリングされたページにlayoutがある場合、optionsにはこの2つのパラメータが含まれます.
:file=>viewファイルのパス
:Layout=>テンプレートのパス
_render_with_layout(options,local_assigns,&block)メソッド:
#1レンダーパス
@content_for_layout=render(options)#1 layoutパラメータを削除すると、optionsにはファイルが残ります.
このようにレンダリングします:file、得られたレンダリングの内容はインスタンス変数@content_for_layoutに保存され、この値は上を参照してください.
render(:file=>partial_layout,:locals=>local_assigns)#2テンプレートを通常のものとする:fileレンダリング
レンダリングの途中で、yieldがあるので@content_for_xxxを使用して、@content_for_xxxをyieldの位置に挿入する必要があります(デフォルトでは@content_for_layoutを使用します)
#2 beginとensureの使用は元の環境を維持するためです
例えば、もともと@content_for_xxxという変数がありましたが、ここのhackは他の使用に影響しません.とても強いです!
3)render :partial => "xxx_file_path"
コード:render_partial(options)呼び出しチェーン:
呼び出し:ActionView::Partialsモジュールのrender_partial(options = {})
コアコール:pick_partial_template(partial_path).render_partial(self, options[:object], local_assigns)
呼び出し:RenderablePartial#render_partial(view,object=nil,local_assigns={},as=nil)メソッド
呼び出し:render_template(view, local_assigns)
呼び出し:render
ここでは主にTemplateのrenderメソッドを使って、このメソッドはcompileを呼び出します!、この方法の詳細は前を参照してください.
4)render :inline => 'xxx_ERB_string'
コード:InlineTemplate.new(options[:inline],options[:type]).render(self,options[:locals])
InlineTemplateにincludeがあるからRenderable
最終的に呼び出されたのはActionView::Renderable#renderメソッド
ここはちょっと違います.InlineTemplate.newの中に、source hackを入れました.コード:
#ActionView::InlineTemplate#initialize
def initialize(source, type = nil)
@source=source#ここ!!!
@extension = type
@method_segment = "inline_#{@source.hash.abs}"
end
これで!方法erbテンプレートからsourceを解析するステップは、ActionView::TemplateHandlers::ERB#compileで、スキップしました.
したがって、:inlineメソッドは、:textよりも良い点は、:inlineの値がerb形式の文字列であってもよいことである.(誰がこんなに使うのか、ライルズはちゃんと簡素化すべきか、派手なのはやめなさい)
まとめ:
#1 2つのrenderメソッドの位置が異なり、前者が主導し、前者が後者を呼び出す
ActionController::Base#render
ActionView::Base#render
#2 ActionView::Base#renderヘテロと脈絡
viewはユーザーに見せるもので、需要の変化が大きく、webの中で最も雑然とした場所に属し、統一的なパターンを形成しにくい.
railsでは使用上の簡単さ、一致などのためにerb、js、ajaxなどを柔らかくします.
応援してくれました.rjs、xml、erbなどのテンプレートをサポートしています.
しかし、複雑さはゆっくりと記入され、脈絡が一致し、基本的にERBテンプレートレンダリングの過程に合っています.
参考原理紹介:Rails(二)Rails_を手書きRuby_ERB使用_テンプレート_カスタム
||
| |
| |
====終了===
=== ===
== ==
を選択します. =
𞓜 |
せつぞく
ActionControllerでは@template.renderを使用してページ内容を生成します.この@templateはActionView::Base.newから出てきた例です.
ActionControllerの具体的なコード:
response.template = ActionView::Base.new(self.class.view_paths, {}, self)
分析:
#1 controllerはviewのインスタンス(@template)を保持します.
newメソッドとActionView::Baseのnewパラメータに従って、viewもcontrollerのインスタンスを持っています.
ActionView::Baseの初期化方法:
def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)#:nodoc:
@assigns = assigns_for_first_render
@assigns_added = nil
@controller = controller # !!!
@helpers = ProxyModule.new(self)
self.view_paths = view_paths
@_first_render = nil
@_current_render = nil
end
#2 self、すなわち現在のcontrollerのインスタンスがviewに転送される
このように結論があります:viewとcontrollerは互いに相手の実例を持っていて、道理でVCが家を分けないと言っています(一緒に置いたaction_pack)
#3 ActionViewのview_pathsはcontrollerによって伝達され、クラスのview_pathsメソッド、すなわち共通パスによって伝達される.
コントロールにはviewのインスタンスがあるので、コントロールにはviewのview_pathsに含まれるパスを変更できます.
たとえば、Controllerのインスタンスメソッド:
def prepend_view_path(path)
@view_paths = superclass.view_paths.dup if !defined?(@view_paths) || @view_paths.nil?
@view_paths.unshift(*path)
end
プロシージャはclassのview_pathsをコピーし、このインスタンスのviewファイルとしてパスを探します.ここでは、view_pathsパスの先頭に追加する方法のみが表示されます.
ActionView::Baseでrenderの実行
つまりerbページの生成プロセスです.このプロセスが複雑なのは、パラメータが多いこと、テンプレートを使用すること、helperを使用することなどです.
renderのコード分析:
#
#options Controller , : file ActionView::ReloadableTemplate。
# Controller 。
def render(options = {}, local_assigns = {}, &block) #:nodoc:
local_assigns ||= {}
case options
when Hash # =>
options = options.reverse_merge(:locals => {})
if options[:layout] #:layout
_render_with_layout(options, local_assigns, &block)
elsif options[:file] #:file
template = self.view_paths.find_template(options[:file], template_format)
template.render_template(self, options[:locals])
elsif options[:partial] #:partial
render_partial(options)
elsif options[:inline] #:inline
InlineTemplate.new(options[:inline], options[:type]).render(self, options[:locals])
elsif options[:text] #
options[:text]
end
when :update #ajax
update_page(&block)
else # , render :partial => "xxx" partial。
# , 。 ?
render_partial(:partial => options, :locals => local_assigns)
end
end
レンダリングに関係なく、最終的にはtemplateのrender_templateメソッドが呼び出されます.
この方法では、対応するviewテンプレートプロセッサhandler(例えばERBのhandler)を見つけ、compile(template)メソッドを呼び出し、必要な文字列を返し、処理した後、最終的な結果を返します.
ActionView::Base#render、layoutの使い方などを詳しく分析
1)#:file
template = self.view_paths.find_template(options[:file], template_format)
template.render_template(self, options[:locals])
ActionView::Templateのrender_templateメソッドが呼び出され、このメソッドはまた調整されます.
ActionView::Renderableのrender(view,local_assigns)を使用し、compile(local_assigns)を使用し、
続いてcompile!(render_symbol,local_assigns)を使用しました
compile!(render_symbol,local_assigns)ここでいろいろやった(略)結果、正しいhandler(erbにとって)がviewファイルの内容を処理しているのを見つけて、
結果のerb.srcで生成されたコード+local_assignsコードを合わせてsourceとし、
ActionView::Base::CompiledTemplates.module_eval(source,filename,0)として使用
ActionView::Base::CompilledTemplatesはActionView Baseで定義された過剰モジュールであり、このモジュールに含まれる方
メソッド(上module_eval)はincludeによってActionViewベースに入力されます
module CompiledTemplates #:nodoc:
# holds compiled template code
end
include CompiledTemplates
このように、local_assignsのコンテンツは、最終的に方法内変数として使用され、グローバル変数の干渉を回避する.
compile!の詳細:
local_assignsの変数など、erb解析ファイルのsrc文字列の内容などを含むメソッド文字列が生成されます.
例:
実験の一例1:
render :file => "users/index", :locals => {:xxx_xxx_a => 300, :xxx_xxx_b => 400}
生成されたsourceの内容、文字列:
「 def _run_erb_app47views47users47index46html46erb_locals_xxx_xxx_a_xxx_xxx_b(local_assigns) old_output_buffer = output_buffer;xxx_xxx_a = local_assigns[:xxx_xxx_a];xxx_xxx_b = local_assigns[:xxx_xxx_b];;@output_buffer = ''; __in_erb_template=true ; @output_buffer.concat "users list"; @output_buffer ensure self.output_buffer = old_output_buffer end "
整理して、文字列の包装を取り除いて、最終的にきれいな形式は:
def _run_erb_app47views47users47index46html46erb_locals_xxx_xxx_a_xxx_xxx_b(local_assigns)
old_output_buffer = output_buffer
;xxx_xxx_a = local_assigns[:xxx_xxx_a]
;xxx_xxx_b = local_assigns[:xxx_xxx_b]
;
;@output_buffer = ''
; __in_erb_template=true
; @output_buffer.concat "users list"
; @output_buffer
ensure
self.output_buffer = old_output_buffer
end
私が自分で実験した例2:
render :file => "layout/application_layout"
生成されたsourceの内容:
だいたい同じで貼らない
整理して、最終的にきれいな形式は:
def _run_erb_app47views47layouts47application_layout46html46erb(local_assigns)
old_output_buffer = output_buffer
;
;@output_buffer = ''
; __in_erb_template=true
; @output_buffer.concat "<html> \ n \ n<head> \ n <title> \ n "
; @output_buffer.concat(( " #{params[:controller]}##{params[:action]}" ).to_s)
; @output_buffer.concat "
</title>
</head>
<dody>
<div style=\"background-color: #ffebcd;\">
<h2>PAGE HEAD</h2>
</div>
<div>
"
; @output_buffer.concat(( yield ).to_s)
; @output_buffer.concat "
</div>
<div style=\"background-color: #f5f5dc;\">
<h2>PAGE FOOT</h2>
</div>
</dody>
</html>"
; @output_buffer
ensure
self.output_buffer = old_output_buffer
end
生成されたメソッド名は、どのように規定されていますか?(47は/,46は.)
def method_name(local_assigns)
if local_assigns && local_assigns.any?
method_name = method_name_without_locals.dup
method_name << "_locals_#{local_assigns.keys.map { |k| k.to_s }.sort.join('_')}"
else
method_name = method_name_without_locals
end
method_name.to_sym
end
生成メソッドはいつ使用されますか?ActionView::Renderable#renderメソッド:
#ActionView::Renderable#render
def render(view, local_assigns = {})
compile(local_assigns)
view.with_template self do
view.send(:_evaluate_assigns_and_ivars)
view.send(:_set_controller_content_type, mime_type) if respond_to?(:mime_type)
view.send(method_name(local_assigns), local_assigns) do |*names| # !!!
ivar = :@_proc_for_layout
if !view.instance_variable_defined?(:"@content_for_#{names.first}") && view.instance_variable_defined?(ivar) && (proc = view.instance_variable_get(ivar))
view.capture(*names, &proc) # proc layout,
elsif view.instance_variable_defined?(ivar = :"@content_for_#{names.first || :layout}") #
view.instance_variable_get(ivar)
end
end
end
end
view.send(method_name(local_assigns)、local_assigns)do|*names|#ここで使います!!!
ここではsendで生成方法を呼び出した.
ここに&blockがあります.生成方法にyieldがあれば、このblockは実行されます.そうしないと実行されません.
上記の例を見ると、例1にはyieldがなく、blockがヒットせず、方法のみが実行される.
例2にはyieldがあり,メソッドだけでなくブロックも実行される.
さらに分析
layoutで使用するyieldにパラメータがない場合(yield)、blockが受信したパラメータ*namesが空になります.
ivar=:"@content_for_#{names.first|:layout}"#ivarデフォルトではlayout文字列を使用して@content_for_layoutを生成します
@content_for_layoutというインスタンス変数の値がデフォルトで返され、bufferに戻ります.
; @output_buffer.concat(( yield ).to_s)
yieldがパラメータを使用している場合、例えばyield:body_leftを使用している場合、erbが生成したsrcはこのようになります.
; @output_buffer.concat(( yield :body_left ).to_s)
このようにblockのパラメータ個数は1であり、names.firstは:body_left#であり、ivarは@content_for_body_leftである
このときview.instance_variable_get(ivar)が実行され、戻り値がyield:body_leftの位置に挿入されます.
リボン:
#1:fileの内容はcompile!でerbで処理してsrcコードを返し、その後module_evalで実行し、得られた結果は@output_bufferに格納されます.
2 layoutを使用していません.実はrender:fileはviewテンプレートを解析して実行し、役割は単一で、layoutというものを区別していません.layoutは制御側のことで、必要に応じてカスタマイズします.
#3 layoutでyield(:xxx=:layout)の位置は、インスタンス変数@content_for_xxxで置き換えられます
たとえば、ページに内容<%@content_for_body_left="I am Fantaxy!"%>がある場合、
では、「I am Fantaxy!」はyield:body_leftの位置を挿入します.(インスタンス変数に注意)
2)#:layout
レンダリングされたページにlayoutがある場合、optionsにはこの2つのパラメータが含まれます.
:file=>viewファイルのパス
:Layout=>テンプレートのパス
_render_with_layout(options,local_assigns,&block)メソッド:
def _render_with_layout(options, local_assigns, &block) #:nodoc:
partial_layout = options.delete(:layout) # layout , layout
if block_given?
begin
@_proc_for_layout = block
concat(render(options.merge(:partial => partial_layout)))
ensure
@_proc_for_layout = nil
end
else
begin # ,
original_content_for_layout = @content_for_layout if defined?(@content_for_layout)
@content_for_layout = render(options) #1 layout , :file, @content_for_layout
if (options[:inline] || options[:file] || options[:text])
@cached_content_for_layout = @content_for_layout
render(:file => partial_layout, :locals => local_assigns) #2 :file
else
render(options.merge(:partial => partial_layout))
end
ensure
@content_for_layout = original_content_for_layout
end
end
end
#1レンダーパス
@content_for_layout=render(options)#1 layoutパラメータを削除すると、optionsにはファイルが残ります.
このようにレンダリングします:file、得られたレンダリングの内容はインスタンス変数@content_for_layoutに保存され、この値は上を参照してください.
render(:file=>partial_layout,:locals=>local_assigns)#2テンプレートを通常のものとする:fileレンダリング
レンダリングの途中で、yieldがあるので@content_for_xxxを使用して、@content_for_xxxをyieldの位置に挿入する必要があります(デフォルトでは@content_for_layoutを使用します)
#2 beginとensureの使用は元の環境を維持するためです
例えば、もともと@content_for_xxxという変数がありましたが、ここのhackは他の使用に影響しません.とても強いです!
3)render :partial => "xxx_file_path"
コード:render_partial(options)呼び出しチェーン:
呼び出し:ActionView::Partialsモジュールのrender_partial(options = {})
コアコール:pick_partial_template(partial_path).render_partial(self, options[:object], local_assigns)
呼び出し:RenderablePartial#render_partial(view,object=nil,local_assigns={},as=nil)メソッド
呼び出し:render_template(view, local_assigns)
呼び出し:render
ここでは主にTemplateのrenderメソッドを使って、このメソッドはcompileを呼び出します!、この方法の詳細は前を参照してください.
4)render :inline => 'xxx_ERB_string'
コード:InlineTemplate.new(options[:inline],options[:type]).render(self,options[:locals])
InlineTemplateにincludeがあるからRenderable
最終的に呼び出されたのはActionView::Renderable#renderメソッド
ここはちょっと違います.InlineTemplate.newの中に、source hackを入れました.コード:
#ActionView::InlineTemplate#initialize
def initialize(source, type = nil)
@source=source#ここ!!!
@extension = type
@method_segment = "inline_#{@source.hash.abs}"
end
これで!方法erbテンプレートからsourceを解析するステップは、ActionView::TemplateHandlers::ERB#compileで、スキップしました.
したがって、:inlineメソッドは、:textよりも良い点は、:inlineの値がerb形式の文字列であってもよいことである.(誰がこんなに使うのか、ライルズはちゃんと簡素化すべきか、派手なのはやめなさい)
まとめ:
#1 2つのrenderメソッドの位置が異なり、前者が主導し、前者が後者を呼び出す
ActionController::Base#render
ActionView::Base#render
#2 ActionView::Base#renderヘテロと脈絡
viewはユーザーに見せるもので、需要の変化が大きく、webの中で最も雑然とした場所に属し、統一的なパターンを形成しにくい.
railsでは使用上の簡単さ、一致などのためにerb、js、ajaxなどを柔らかくします.
応援してくれました.rjs、xml、erbなどのテンプレートをサポートしています.
しかし、複雑さはゆっくりと記入され、脈絡が一致し、基本的にERBテンプレートレンダリングの過程に合っています.
参考原理紹介:Rails(二)Rails_を手書きRuby_ERB使用_テンプレート_カスタム
||
| |
| |
====終了===
=== ===
== ==
を選択します. =
𞓜 |