Railsソース読解(九)ActionView::Base_ユーザ要求railsにおける処理フロー(4)

13816 ワード

Railsソース読解(九)ActionView::Base_ユーザ要求railsにおける処理フロー(4)
 
せつぞく
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使用_テンプレート_カスタム
 
            ||
           |  |
          |    |
====終了===
===           ===
==               ==
を選択します.                    =
𞓜                      |