在application.html.erb中加入binding.pry



  
    Pragprog Books Online Store
    <%= csrf_meta_tags %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  
  

      <% binding.pry %>
      
        <%= yield %>      
     


得调用栈如下

[1] pry(#<#>)> _bsi_
=> {0=>#<binding:70045758642500 #<class:0x007f69cf29f9d8="">#_app_views_layouts_application_html_erb___3541629653556534568_70046211675620 /home/z/test_rails/depot/app/views/layouts/application.html.erb:39>,
 1=>#<binding:70045759069160 actionview::template#block="" in="" render="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" template.rb:159="">,
 2=>#<binding:70045759503020 activesupport::notifications.instrument="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" activesupport-5.0.2="" lib="" active_support="" notifications.rb:166="">,
 3=>#<binding:70045759928860 actionview::template#instrument="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" template.rb:354="">,
 4=>#<binding:70045760340680 actionview::template#render="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" template.rb:157="">,
 5=>#<binding:70045760696120 actionview::templaterenderer#render_with_layout="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" renderer="" template_renderer.rb:66="">,
 6=>#<binding:70045761106380 actionview::templaterenderer#render_template="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" renderer="" template_renderer.rb:52="">,
 7=>#<binding:70045761507620 actionview::templaterenderer#render="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" renderer="" template_renderer.rb:14="">,
 8=>#<binding:70045761924980 actionview::renderer#render_template="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" renderer="" renderer.rb:42="">,
 9=>#<binding:70045762326140 actionview::renderer#render="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" renderer="" renderer.rb:23="">,
 10=>#<binding:70045762736120 storecontroller#_render_template="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" rendering.rb:104="">,
 11=>#<binding:70045763147920 storecontroller#_render_template="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" streaming.rb:217="">,
 12=>#<binding:70045763278200 storecontroller#render_to_body="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" rendering.rb:83="">,
 13=>#<binding:70045763450580 storecontroller#render_to_body="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" rendering.rb:52="">,
 14=>#<binding:70045763843520 storecontroller#render_to_body="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" renderers.rb:142="">,
 15=>#<binding:70045764050280 storecontroller#render="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.2="" lib="" abstract_controller="" rendering.rb:26="">,
 16=>#<binding:70045764197340 storecontroller#render="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" rendering.rb:36="">,
 17=>#<binding:70045764518340 storecontroller#block="" (2="" levels)="" in="" render="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" instrumentation.rb:44="">,
 18=>#<binding:70045765647320 benchmark.block="" in="" ms="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" activesupport-5.0.2="" lib="" active_support="" core_ext="" benchmark.rb:12="">,
 19=>#<binding:70045765803580 benchmark.realtime="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" 2.4.0="" benchmark.rb:308="">,
 20=>#<binding:70045765911640 benchmark.ms="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" activesupport-5.0.2="" lib="" active_support="" core_ext="" benchmark.rb:12="">,
 21=>#<binding:70045765993640 storecontroller#block="" in="" render="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" instrumentation.rb:44="">,
 22=>#<binding:70045766068960 storecontroller#cleanup_view_runtime="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" instrumentation.rb:87="">,
 23=>#<binding:70045767595920 storecontroller#cleanup_view_runtime="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" activerecord-5.0.2="" lib="" active_record="" railties="" controller_runtime.rb:25="">,
 24=>#<binding:70045767620700 storecontroller#render="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" instrumentation.rb:43="">,
 25=>#<binding:70045767654740 storecontroller#default_render="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" implicit_render.rb:36="">,
 26=>#<binding:70045765986380 storecontroller#block="" in="" send_action="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" basic_implicit_render.rb:4="">,
 27=>#<binding:70045765964560 storecontroller#send_action="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" basic_implicit_render.rb:4="">,
 28=>#<binding:70045765950940 storecontroller#process_action="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.2="" lib="" abstract_controller="" base.rb:188="">,
 29=>#<binding:70045767747820 storecontroller#process_action="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" rendering.rb:30="">,
 30=>#<binding:70045767783320 storecontroller#block="" in="" process_action="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.2="" lib="" abstract_controller="" callbacks.rb:20="">,</binding:70045767783320></binding:70045767747820></binding:70045765950940></binding:70045765964560></binding:70045765986380></binding:70045767654740></binding:70045767620700></binding:70045767595920></binding:70045766068960></binding:70045765993640></binding:70045765911640></binding:70045765803580></binding:70045765647320></binding:70045764518340></binding:70045764197340></binding:70045764050280></binding:70045763843520></binding:70045763450580></binding:70045763278200></binding:70045763147920></binding:70045762736120></binding:70045762326140></binding:70045761924980></binding:70045761507620></binding:70045761106380></binding:70045760696120></binding:70045760340680></binding:70045759928860></binding:70045759503020></binding:70045759069160></binding:70045758642500>


其上一层,也就是ActionView::Template#block in render,是这样的

def render(view, locals, buffer=nil, &block)
  instrument("!render_template".freeze) do
    compile!(view)
    view.send(method_name, locals, buffer, &block)
  end
rescue => e
  handle_render_error(view, e)
end


如无意外,就跟之前研究过的“sinatra如何利用tilt来render”一样,基本上就是,用erb(如果模板是erb)来将模板转换成一个method,method所绑定的对象(即view),带有controller的实例变量,并且该method可接收额外的局部变量

于是稍微验证一下

首先,compile!所做的就是给view加上method

# actionview-5.0.2/lib/action_view/template.rb
def compile(mod)
  encode!
  method_name = self.method_name
  code = @handler.call(self)

  # Make sure that the resulting String to be eval'd is in the
  # encoding of the code
  source = <<-end_src
    def #{method_name}(local_assigns, output_buffer)
      _old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code}
    ensure
      @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer
    end
  end_src

  # Make sure the source is in the encoding of the returned code
  source.force_encoding(code.encoding)

  # In case we get back a String from a handler that is not in
  # BINARY or the default_internal, encode it to the default_internal
  source.encode!

  # Now, validate that the source we got back from the template
  # handler is valid in the default_internal. This is for handlers
  # that handle encoding but screw up
  unless source.valid_encoding?
    raise WrongEncodingError.new(@source, Encoding.default_internal)
  end

  mod.module_eval(source, identifier, 0)
  ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
end


而这个method中,生成最终html(如果你是返回html)的code,来自@handler.call(self)。于是检查下handler是啥

[10] pry(#<#>)> _bsi_[1].eval("handler.class")
=> ActionView::Template::Handlers::ERB


其源码如下

# actionview-5.0.2/lib/action_view/template/handlers/erb.rb
def call(template)
  # First, convert to BINARY, so in case the encoding is
  # wrong, we can still find an encoding tag
  # (<%# encoding %>) inside the String using a regular
  # expression
  template_source = template.source.dup.force_encoding(Encoding::ASCII_8BIT)

  erb = template_source.gsub(ENCODING_TAG, '')
  encoding = $2

  erb.force_encoding valid_encoding(template.source.dup, encoding)

  # Always make sure we return a String in the default_internal
  erb.encode!

  self.class.erb_implementation.new(
    erb,
    :escape => (self.class.escape_whitelist.include? template.type),
    :trim => (self.class.erb_trim_mode == "-")
  ).src
end


而handler则是,初始化view的时候就赋予的(其实这不重要)

# actionview-5.0.2/lib/action_view/template.rb
attr_reader :source, :identifier, :handler, :original_encoding, :updated_at

def initialize(source, identifier, handler, details)
  format = details[:format] || (handler.default_format if handler.respond_to?(:default_format))

  @source            = source
  @identifier        = identifier
  @handler           = handler
  @compiled          = false
  @original_encoding = nil
  @locals            = details[:locals] || []
  @virtual_path      = details[:virtual_path]
  @updated_at        = details[:updated_at] || Time.now
  @formats           = Array(format).map { |f| f.respond_to?(:ref) ? f.ref : f  }
  @variants          = [details[:variant]]
  @compile_mutex     = Mutex.new
end


那么,回到如何找layout的问题上,从调用栈可见,这个过程应是如下

 5=>#<binding:70045760696120 actionview::templaterenderer#render_with_layout="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" renderer="" template_renderer.rb:66="">,
 6=>#<binding:70045761106380 actionview::templaterenderer#render_template="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" renderer="" template_renderer.rb:52="">,
 7=>#<binding:70045761507620 actionview::templaterenderer#render="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" renderer="" template_renderer.rb:14="">,
 8=>#<binding:70045761924980 actionview::renderer#render_template="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" renderer="" renderer.rb:42="">,
 9=>#<binding:70045762326140 actionview::renderer#render="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" renderer="" renderer.rb:23="">,</binding:70045762326140></binding:70045761924980></binding:70045761507620></binding:70045761106380></binding:70045760696120>


都在TemplateRenderer中

module ActionView
  class TemplateRenderer < AbstractRenderer
    def render(context, options)
      @view    = context
      @details = extract_details(options)
      template = determine_template(options)

      prepend_formats(template.formats)

      @lookup_context.rendered_format ||= (template.formats.first || formats.first)

      render_template(template, options[:layout], options[:locals])
    end

    def render_template(template, layout_name = nil, locals = nil)
      view, locals = @view, locals || {}

      render_with_layout(layout_name, locals) do |layout|
        instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
          template.render(view, locals) { |*name| view._layout_for(*name) }
        end
      end
    end

    def render_with_layout(path, locals)
      layout  = path && find_layout(path, locals.keys, [formats.first])
      content = yield(layout)

      if layout
        view = @view
        view.view_flow.set(:layout, content)
        layout.render(view, locals){ |*name| view._layout_for(*name) }
      else
        content
      end
    end
  end
end


那么这里的options[:layout]是什么呢

[13] pry(#<#>)> _bsi_[7].lv(:options)
=> {:prefixes=>["store", "application"], :template=>"index", :layout=>#<proc:0x007f69cf29fd70@ home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" layouts.rb:386="">}</proc:0x007f69cf29fd70@>


这里好像有点不对劲了,才发现现在断点就是在application.html.erb这个layout上,那就不好分析了,还是断在内容上吧,于是


<%= notice %>

Your Pragmatic Catalog

<% @products.each do |product| %>  
  <%= image_tag(product.image_url) %>  

<%= product.title %>

  <%= sanitize(product.description) %>  
    <%=  number_to_currency product.price %>     <%= button_to 'Add to Cart', line_items_path(product_id: product), remote: true %>  
 
<% end %> <% binding.pry %>


得调用栈如下

[1] pry(#<#>)> _bsi_
=> {0=>#<binding:70045772081600 #<class:0x007f69cf29f9d8="">#_app_views_store_index_html_erb__2763356708539799932_70045772484440 /home/z/test_rails/depot/app/views/store/index.html.erb:17>,
 1=>#<binding:70045772032120 actionview::template#block="" in="" render="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" template.rb:159="">,
 2=>#<binding:70045771966320 activesupport::notifications.instrument="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" activesupport-5.0.2="" lib="" active_support="" notifications.rb:166="">,
 3=>#<binding:70045771908740 actionview::template#instrument="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" template.rb:354="">,
 4=>#<binding:70045771842300 actionview::template#render="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" template.rb:157="">,
 5=>#<binding:70045771792900 actionview::templaterenderer#block="" (2="" levels)="" in="" render_template="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" renderer="" template_renderer.rb:54="">,
 6=>#<binding:70045771759860 actionview::templaterenderer#block="" in="" instrument="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" renderer="" abstract_renderer.rb:42="">,
 7=>#<binding:70045771686380 activesupport::notifications.block="" in="" instrument="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" activesupport-5.0.2="" lib="" active_support="" notifications.rb:164="">,
 8=>#<binding:70045771621060 activesupport::notifications::instrumenter#instrument="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" activesupport-5.0.2="" lib="" active_support="" notifications="" instrumenter.rb:21="">,
 9=>#<binding:70045771555580 activesupport::notifications.instrument="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" activesupport-5.0.2="" lib="" active_support="" notifications.rb:164="">,
 10=>#<binding:70045771490240 actionview::templaterenderer#instrument="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" renderer="" abstract_renderer.rb:41="">,
 11=>#<binding:70045771416660 actionview::templaterenderer#block="" in="" render_template="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" renderer="" template_renderer.rb:53="">,
 12=>#<binding:70045771351340 actionview::templaterenderer#render_with_layout="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" renderer="" template_renderer.rb:61="">,
 13=>#<binding:70045771285860 actionview::templaterenderer#render_template="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" renderer="" template_renderer.rb:52="">,
 14=>#<binding:70045771219460 actionview::templaterenderer#render="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" renderer="" template_renderer.rb:14="">,
 15=>#<binding:70045771143900 actionview::renderer#render_template="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" renderer="" renderer.rb:42="">,
 16=>#<binding:70045771069940 actionview::renderer#render="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" renderer="" renderer.rb:23="">,</binding:70045771069940></binding:70045771143900></binding:70045771219460></binding:70045771285860></binding:70045771351340></binding:70045771416660></binding:70045771490240></binding:70045771555580></binding:70045771621060></binding:70045771686380></binding:70045771759860></binding:70045771792900></binding:70045771842300></binding:70045771908740></binding:70045771966320></binding:70045772032120></binding:70045772081600>


但发现options[:layout]仍然是如此一个Proc

[2] pry(#<#>)> _bsi_[14].lv(:options)
=> {:prefixes=>["store", "application"], :template=>"index", :layout=>#<proc:0x007f699ac616b8@ home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" layouts.rb:386="">}
[3] pry(#<#>)> _bsi_[13].lv(:layout_name)
=> #<proc:0x007f699ac616b8@ home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionview-5.0.2="" lib="" action_view="" layouts.rb:386=""></proc:0x007f699ac616b8@></proc:0x007f699ac616b8@>


但回过头来看,render_template和render_with_layout分别有以下两句

template.render(view, locals) { |*name| view._layout_for(*name) }
layout.render(view, locals){ |*name| view._layout_for(*name) }


于是,大概可以这样猜测:当前的view在render时,会找出layout对象(它也是一个view),然后调用layout的render,并带上一个block,在block中调用当前view的render;当layout所转换成的method中调用yield时,即调用了当前view的render,于是layout便嵌套了当前view。

理论上这是可行的,但真要去验证actionview的具体做法的话……好像太麻烦了,先不干了……