根据Agile Web Development with Rails 5所说:

All instance variables of the controller are also available in the template.

The controller object's  flash ,  headers ,  logger ,  params ,  request ,  response , and  session  are available as accessor methods in the view

下面具体看看它们是怎么来的

view中的flash、session等方法是怎么来的

首先在view中加入<% binding.pry %>断点,可看到flash、params等方法都是被委托到controller上的

[2] pry(#<#>)> method(:flash).source_location
=> ["/home/z/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/actionview-5.0.2/lib/action_view/helpers/controller_helper.rb", 10]


源码如下

module ActionView
  module Helpers
    # This module keeps all methods and behavior in ActionView
    # that simply delegates to the controller.
    module ControllerHelper #:nodoc:
      attr_internal :controller, :request

      delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers,
               :flash, :action_name, :controller_name, :controller_path, :to => :controller

      def assign_controller(controller)
        if @_controller = controller
          @_request = controller.request if controller.respond_to?(:request)
          @_config  = controller.config.inheritable_copy if controller.respond_to?(:config)
          @_default_form_builder = controller.default_form_builder if controller.respond_to?(:default_form_builder)
        end
      end

      def logger
        controller.logger if controller.respond_to?(:logger)
      end
    end
  end
end


view是如何获取controller的

从上面源码可看到assign_controller似乎就类似于一种setter,它是何时会被调用呢?于是,在assign_controller加入断点,然后访问/products

From: /home/z/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/actionview-5.0.2/lib/action_view/helpers/controller_helper.rb @ line 15 ActionView::Helpers::ControllerHelper#assign_controller:

    13: def assign_controller(controller)
    14:   binding.pry
 => 15:   if @_controller = controller
    16:     @_request = controller.request if controller.respond_to?(:request)
    17:     @_config  = controller.config.inheritable_copy if controller.respond_to?(:config)
    18:     @_default_form_builder = controller.default_form_builder if controller.respond_to?(:default_form_builder)
    19:   end
    20: end

[1] pry(#<#>)> _bsi_
=> {0=>#<binding:70250515797060 #<class:0x007fc8f1f069e8="">#assign_controller /home/z/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/actionview-5.0.2/lib/action_view/helpers/controller_helper.rb:14>,
 1=>#<binding:70250516430520 #<class:0x007fc8f1f069e8="">#initialize /home/z/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/actionview-5.0.2/lib/action_view/base.rb:211>,
 2=>#<binding:70250513919800 #<class:0x007fc8f1f069e8="">#initialize /home/z/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/actionpack-5.0.2/lib/action_dispatch/routing/url_for.rb:106>,
 3=>#<binding:70250523128860 productscontroller#view_context="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionview-5.0.2="" lib="" action_view="" rendering.rb:72="">,
 4=>#<binding:70250523311660 productscontroller#_render_template="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionview-5.0.2="" lib="" action_view="" rendering.rb:98="">,
 5=>#<binding:70250523597300 productscontroller#_render_template="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" streaming.rb:217="">,
 6=>#<binding:70250523872240 productscontroller#render_to_body="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionview-5.0.2="" lib="" action_view="" rendering.rb:83="">,
 7=>#<binding:70250524267320 productscontroller#render_to_body="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" rendering.rb:52="">,
 8=>#<binding:70250524386040 productscontroller#render_to_body="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" renderers.rb:142="">,
 9=>#<binding:70250524727040 productscontroller#render="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" abstract_controller="" rendering.rb:26="">,
 10=>#<binding:70250525162960 productscontroller#render="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" rendering.rb:36="">,
 11=>#<binding:70250525316880 productscontroller#block="" (2="" levels)="" in="" render="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" instrumentation.rb:44="">,
 12=>#<binding:70250525733820 benchmark.block="" in="" ms="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" activesupport-5.0.2="" lib="" active_support="" core_ext="" benchmark.rb:12="">,
 13=>#<binding:70250526005360 benchmark.realtime="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" 2.3.0="" benchmark.rb:308="">,
 14=>#<binding:70250526293340 benchmark.ms="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" activesupport-5.0.2="" lib="" active_support="" core_ext="" benchmark.rb:12="">,
 15=>#<binding:70250526546800 productscontroller#block="" in="" render="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" instrumentation.rb:44="">,
 16=>#<binding:70250526978760 productscontroller#cleanup_view_runtime="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" instrumentation.rb:87="">,
 17=>#<binding:70250527139140 productscontroller#cleanup_view_runtime="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" activerecord-5.0.2="" lib="" active_record="" railties="" controller_runtime.rb:25="">,
 18=>#<binding:70250527443460 productscontroller#render="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" instrumentation.rb:43="">,
 19=>#<binding:70250527818180 productscontroller#default_render="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" implicit_render.rb:36="">,
 20=>#<binding:70250528143220 productscontroller#block="" in="" send_action="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" basic_implicit_render.rb:4="">,
 21=>#<binding:70250528465280 productscontroller#send_action="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" basic_implicit_render.rb:4="">,
 22=>#<binding:70250528827020 productscontroller#process_action="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" abstract_controller="" base.rb:188="">,
 23=>#<binding:70250529237440 productscontroller#process_action="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" rendering.rb:30="">,
 24=>#<binding:70250529377280 productscontroller#block="" in="" process_action="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" abstract_controller="" callbacks.rb:20="">,
 25=>#<binding:70250529649300 activesupport::callbacks::filters::end#call="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" activesupport-5.0.2="" lib="" active_support="" callbacks.rb:126="">,
 26=>#<binding:70250530017880 activesupport::callbacks::callbackchain#block="" (2="" levels)="" in="" compile="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" activesupport-5.0.2="" lib="" active_support="" callbacks.rb:506="">,
 27=>#<binding:70250530145300 activesupport::callbacks::callbacksequence#call="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" activesupport-5.0.2="" lib="" active_support="" callbacks.rb:455="">,
 28=>#<binding:70250530486720 productscontroller#__run_callbacks__="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" activesupport-5.0.2="" lib="" active_support="" callbacks.rb:101="">,
 29=>#<binding:70250528846540 productscontroller#_run_process_action_callbacks="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" activesupport-5.0.2="" lib="" active_support="" callbacks.rb:750="">,
 30=>#<binding:70250530706720 productscontroller#run_callbacks="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" activesupport-5.0.2="" lib="" active_support="" callbacks.rb:90="">,
 31=>#<binding:70250530360700 productscontroller#process_action="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" abstract_controller="" callbacks.rb:19="">,
 32=>#<binding:70250530031620 productscontroller#process_action="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" rescue.rb:20="">,
 33=>#<binding:70250529656440 productscontroller#block="" in="" process_action="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" instrumentation.rb:32="">,
 34=>#<binding:70250529412960 activesupport::notifications.block="" in="" instrument="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" activesupport-5.0.2="" lib="" active_support="" notifications.rb:164="">,
 35=>#<binding:70250529196080 activesupport::notifications::instrumenter#instrument="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" activesupport-5.0.2="" lib="" active_support="" notifications="" instrumenter.rb:21="">,
 36=>#<binding:70250528716060 activesupport::notifications.instrument="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" activesupport-5.0.2="" lib="" active_support="" notifications.rb:164="">,
 37=>#<binding:70250528347380 productscontroller#process_action="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" instrumentation.rb:30="">,
 38=>#<binding:70250528010780 productscontroller#process_action="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" params_wrapper.rb:248="">,
 39=>#<binding:70250527626540 productscontroller#process_action="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" activerecord-5.0.2="" lib="" active_record="" railties="" controller_runtime.rb:18="">,
 40=>#<binding:70250527305640 productscontroller#process="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" abstract_controller="" base.rb:126="">,
 41=>#<binding:70250527025880 productscontroller#process="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionview-5.0.2="" lib="" action_view="" rendering.rb:30="">,
 42=>#<binding:70250526688700 productscontroller#dispatch="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal.rb:190="">,
 43=>#<binding:70250526391200 productscontroller.dispatch="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal.rb:262="">,</binding:70250526391200></binding:70250526688700></binding:70250527025880></binding:70250527305640></binding:70250527626540></binding:70250528010780></binding:70250528347380></binding:70250528716060></binding:70250529196080></binding:70250529412960></binding:70250529656440></binding:70250530031620></binding:70250530360700></binding:70250530706720></binding:70250528846540></binding:70250530486720></binding:70250530145300></binding:70250530017880></binding:70250529649300></binding:70250529377280></binding:70250529237440></binding:70250528827020></binding:70250528465280></binding:70250528143220></binding:70250527818180></binding:70250527443460></binding:70250527139140></binding:70250526978760></binding:70250526546800></binding:70250526293340></binding:70250526005360></binding:70250525733820></binding:70250525316880></binding:70250525162960></binding:70250524727040></binding:70250524386040></binding:70250524267320></binding:70250523872240></binding:70250523597300></binding:70250523311660></binding:70250523128860></binding:70250513919800></binding:70250516430520></binding:70250515797060>


可发现有actionpack-5.0.2/lib/action_controller/metal/basic_implicit_render.rb

module ActionController
  module BasicImplicitRender
    def send_action(method, *args)
      super.tap { default_render unless performed? }
    end

    def default_render(*args)
      head :no_content
    end
  end
end


搜索BasicImplicitRender,可见implicit_render.rb中有include它

$ gems git:(master) grep BasicImplicitRender -rn a*
actionpack-5.0.2/lib/action_controller.rb:31:    autoload :BasicImplicitRender
actionpack-5.0.2/lib/action_controller/api.rb:119:      BasicImplicitRender,
actionpack-5.0.2/lib/action_controller/metal/basic_implicit_render.rb:2:  module BasicImplicitRender # :nodoc:
actionpack-5.0.2/lib/action_controller/metal/implicit_render.rb:32:    include BasicImplicitRender


ImplicitRender部分源码如下

module ImplicitRender

  include BasicImplicitRender

  def default_render(*args)
    if template_exists?(action_name.to_s, _prefixes, variants: request.variant)
      render(*args)
    elsif any_templates?(action_name.to_s, _prefixes)
      message = "#{self.class.name}\##{action_name} is missing a template " \
        "for this request format and variant.\n" \
        "\nrequest.formats: #{request.formats.map(&:to_s).inspect}" \
        "\nrequest.variant: #{request.variant.inspect}"

      raise ActionController::UnknownFormat, message
    elsif interactive_browser_request?
      message = "#{self.class.name}\##{action_name} is missing a template " \
        "for this request format and variant.\n\n" \
        "request.formats: #{request.formats.map(&:to_s).inspect}\n" \
        "request.variant: #{request.variant.inspect}\n\n" \
        "NOTE! For XHR/Ajax or API requests, this action would normally " \
        "respond with 204 No Content: an empty white screen. Since you're " \
        "loading it in a web browser, we assume that you expected to " \
        "actually render a template, not nothing, so we're showing an " \
        "error to be extra-clear. If you expect 204 No Content, carry on. " \
        "That's what you'll get from an XHR or API request. Give it a shot."

      raise ActionController::UnknownFormat, message
    else
      logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
      super
    end
  end

  # ...

end


而ImplicitRender则在Base中被include

$ gems git:(master) grep ImplicitRender -rn a*
actionpack-5.0.2/lib/action_controller.rb:31:    autoload :BasicImplicitRender
actionpack-5.0.2/lib/action_controller.rb:32:    autoload :ImplicitRender
actionpack-5.0.2/lib/action_controller/api.rb:119:      BasicImplicitRender,
actionpack-5.0.2/lib/action_controller/base.rb:218:      ImplicitRender,
actionpack-5.0.2/lib/action_controller/metal/basic_implicit_render.rb:2:  module BasicImplicitRender # :nodoc:
actionpack-5.0.2/lib/action_controller/metal/implicit_render.rb:29:  module ImplicitRender
actionpack-5.0.2/lib/action_controller/metal/implicit_render.rb:32:    include BasicImplicitRender


至此,整理一下,send_action是BasicImplicitRender才有的,但default_render是ImplicitRender的优先(只有在“No template found”是才super,去调include的BasicImplicitRender的default_render)

而default_render是只在performed?为false时才会做

这就是Agile Web Development with Rails 5中所谓的:

Because the controller must respond exactly once, it checks to see whether a response has been generated just before it finishes handling a request. If not, the controller looks for a template named after the controller and action and automatically renders it. This is the most common way that rendering takes place. You may have noticed that in most of the actions in our shopping cart tutorial we never explicitly rendered anything. Instead, our action methods set up the context for the view and return. The controller notices that no rendering has taken place and automatically invokes the appropriate template

检查一下哪些module有重载render方法

[2] pry(#<#>)> _bsi_.select{|i, bi| bi.frame_env =~ /render/}
=> {4=>#<binding:70250147973180 productscontroller#_render_template="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionview-5.0.2="" lib="" action_view="" rendering.rb:98="">,
 5=>#<binding:70250147700480 productscontroller#_render_template="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" streaming.rb:217="">,
 6=>#<binding:70250147444120 productscontroller#render_to_body="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionview-5.0.2="" lib="" action_view="" rendering.rb:83="">,
 7=>#<binding:70250147166220 productscontroller#render_to_body="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" rendering.rb:52="">,
 8=>#<binding:70250146902260 productscontroller#render_to_body="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" renderers.rb:142="">,
 9=>#<binding:70250146642820 productscontroller#render="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" abstract_controller="" rendering.rb:26="">,
 10=>#<binding:70250146383760 productscontroller#render="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" rendering.rb:36="">,
 11=>#<binding:70250146058920 productscontroller#block="" (2="" levels)="" in="" render="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" instrumentation.rb:44="">,
 15=>#<binding:70250144916680 productscontroller#block="" in="" render="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" instrumentation.rb:44="">,
 18=>#<binding:70250144030980 productscontroller#render="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" instrumentation.rb:43="">,
 19=>#<binding:70250143729220 productscontroller#default_render="" home="" z="" .rbenv="" versions="" 2.3.3="" lib="" ruby="" gems="" 2.3.0="" gems="" actionpack-5.0.2="" lib="" action_controller="" metal="" implicit_render.rb:36="">}</binding:70250143729220></binding:70250144030980></binding:70250144916680></binding:70250146058920></binding:70250146383760></binding:70250146642820></binding:70250146902260></binding:70250147166220></binding:70250147444120></binding:70250147700480></binding:70250147973180>


大概就是actionpack-5.0.2/lib/action_controller/metal/instrumentation.rb,actionpack-5.0.2/lib/action_controller/metal/rendering.rb,actionpack-5.0.2/lib/abstract_controller/rendering.rb。如无意外就是mixin然后super。

actionpack-5.0.2/lib/action_controller/metal/instrumentation.rb如下,很直接地就用了Benchmark来计时,没有通过ActiveSupport::Notifications.instrument来调什么hook

def render(*args)
  render_output = nil
  self.view_runtime = cleanup_view_runtime do
    Benchmark.ms { render_output = super }
  end
  render_output
end


actionpack-5.0.2/lib/action_controller/metal/rendering.rb如下,用于检查下有否显式地二次调用render

def render(*args) #:nodoc:
  raise ::AbstractController::DoubleRenderError if self.response_body
  super
end


actionpack-5.0.2/lib/abstract_controller/rendering.rb如下,真正地生成响应体,就是在这里了

def render(*args, &block)
  options = _normalize_render(*args, &block)
  rendered_body = render_to_body(options)
  if options[:html]
    _set_html_content_type
  else
    _set_rendered_content_type rendered_format
  end
  self.response_body = rendered_body
end


render干什么

上面的分析仅仅是根据字面编程来猜测,而且似乎跑偏,但可进一步猜测是controller调用render,然后render会生成view,并将当前controller赋给view,然后把view渲染成字符串后,塞到响应体中。

下面来看看具体过程,这次直接跟踪render_to_body

# actionpack-5.0.2/lib/abstract_controller/rendering.rb
def render(*args, &block)
  options = _normalize_render(*args, &block)
  #binding.pry
  rendered_body = binding.trace_tree(html: true, tmp: ['rails', "render_#{options[:template]}.html"]) do
    render_to_body(options)
  end
  if options[:html]
    _set_html_content_type
  else
    _set_rendered_content_type rendered_format
  end
  self.response_body = rendered_body
end


然后访问/products/1。跑的比较慢,生成的文件也很大,完整调用栈如下(注意,rendering.rb因为加入了三行调试语句,所以有关此文件的trace的结果,下半部分会往下偏移三行)

render_show.html

还是从assign_controller查起,可看到controller被赋给了一个匿名类所生成的对象


查源码,可知这里new的就是view对象,它来自动态生成的ActionView::Base子类

module ClassMethods
  def view_context_class
    @view_context_class ||= begin
      supports_path = supports_path?
      routes  = respond_to?(:_routes)  && _routes
      helpers = respond_to?(:_helpers) && _helpers

      Class.new(ActionView::Base) do
        if routes
          include routes.url_helpers(supports_path)
          include routes.mounted_helpers
        end

        if helpers
          include helpers
        end
      end
    end
  end
end

attr_internal_writer :view_context_class

def view_context_class
  @_view_context_class ||= self.class.view_context_class
end

# An instance of a view class. The default view class is ActionView::Base.
#
# The view class must have the following methods:
# View.new[lookup_context, assigns, controller]
#   Create a new ActionView instance for a controller and we can also pass the arguments.
# View#render(option)
#   Returns String with the rendered template
#
# Override this method in a module to change the default behavior.
def view_context
  view_context_class.new(view_renderer, view_assigns, self)
end


而且,从注释也可看出,view接下来会被调用render来渲染出响应体

于是,再往上推导一下,大概流程就是如下(不过这里又说是用renderer来做render)


另外,从view_context方法所使用的view_assigns如下,它其实就是controller的实例变量,只是简单地从controller复制到view,待会应该是会用到instance_variable_set的

# actionpack-5.0.2/lib/abstract_controller/rendering.rb
DEFAULT_PROTECTED_INSTANCE_VARIABLES = Set.new %i(
  @_action_name @_response_body @_formats @_prefixes
)

# This method should return a hash with assigns.
# You can overwrite this configuration per controller.
# :api: public
def view_assigns
  protected_vars = _protected_ivars
  variables      = instance_variables

  variables.reject! { |s| protected_vars.include? s }
  variables.each_with_object({}) { |name, hash|
    hash[name.slice(1, name.length)] = instance_variable_get(name)
  }
end


确实,在assign_controller之前,会assign这些变量


源码如下

# actionview-5.0.2/lib/action_view/base.rb
def initialize(context = nil, assigns = {}, controller = nil, formats = nil) #:nodoc:
  @_config = ActiveSupport::InheritableOptions.new

  if context.is_a?(ActionView::Renderer)
    @view_renderer = context
  else
    lookup_context = context.is_a?(ActionView::LookupContext) ?
      context : ActionView::LookupContext.new(context)
    lookup_context.formats  = formats if formats
    lookup_context.prefixes = controller._prefixes if controller
    @view_renderer = ActionView::Renderer.new(lookup_context)
  end

  assign(assigns)
  assign_controller(controller)
  _prepare_context
end


直接加入断点的话,会看到有product这个变量(当然,set时候会前面带上@)

From: /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionview-5.0.2/lib/action_view/base.rb @ line 199 ActionView::Base#initialize:

    197: def initialize(context = nil, assigns = {}, controller = nil, formats = nil) #:nodoc:
    198:   binding.pry
 => 199:   @_config = ActiveSupport::InheritableOptions.new
    200:
    201:   if context.is_a?(ActionView::Renderer)
    202:     @view_renderer = context
    203:   else
    204:     lookup_context = context.is_a?(ActionView::LookupContext) ?
    205:       context : ActionView::LookupContext.new(context)
    206:     lookup_context.formats  = formats if formats
    207:     lookup_context.prefixes = controller._prefixes if controller
    208:     @view_renderer = ActionView::Renderer.new(lookup_context)
    209:   end
    210:
    211:   assign(assigns)
    212:   assign_controller(controller)
    213:   _prepare_context
    214: end

[1] pry(#<#>)> assigns.keys
=> ["marked_for_same_origin_verification", "product"]


小结

至此,基本可以知道,flash、session等方法,是委托controller而来的,而controller的实例变量,是复制到view的