官方文档里有两个地方提到 render 的用法 在 Controller 里,http://guides.rubyonrails.org/layouts_and_rendering.html#using-render,例如
render :edit
render plain: "OK"
render json: @product
在 View 里,http://guides.rubyonrails.org/layouts_and_rendering.html#using-partials,例如
<%= render "shared/menu" %>
<%= render partial: "form", locals: {zone: @zone} %>
看到这里,render 的日常使用就差不多了。
但我们还想找找 render 更多细致的用法,于是去查 API,找到很多 render 的定义。
有点混乱,也没有一个说明了render json: @product
这样的用法。
于是有了些疑问:
render json: @product
这样的用法在哪儿实现的?带着这样疑问,通过代码调试,结合源码,找到了些答案。
Controller 里的 render 定义应该在 AbstractController::Rendering#render。 这里"定义"有点不准确,因为往往有多次重载,姑且理解为"主要方法"的意思。
这里的 render 其实是通过 render_to_body 这个方式实现的,render_to_body 是个 API 方法,主要实现在 ActionController::Renderers。 在 ActionController::Renderers 里有 add 的类方法,可以添加 Mine 类型的支持,文档里的例子是定制"render csv: @csvable"。 ActionController::Renders 源码里就有 json, js, xml 的 Mine 类型的支持的实现。这里摘抄一段 js 的:
add :js do |js, options|
self.content_type ||= Mime::JS
js.respond_to?(:to_js) ? js.to_js(options) : js
end
这样就回答问题 2:"render json: @product" 是在 ActionController::Renderers 里实现的。
但 ActionController::Renderers 里并没有render "index"
这样的用法实现。
跟踪发现,ActionCtroller::Base included ActionView::Layouts which included ActionView::Rendering。
ActionView::Rendering#render_to_body uses ActionView::Renderer#render.
ActionView::Renderer#render的文档里有说明,它会根据条件判断使用TemplateRender或者PartialRenderer渲染。
PartialRenderer 类有比较详细的文档,TemplateRender 的类没什么文档,它的说明主要在下面提到的 ActionView::Helpers::RenderingHelper#render。
这里是很奇妙的一点,Controller 的模版渲染实现其实是通过 View 里面的 Renderer 实现的。
不过想来也可以理解,View 层本来就负责对模版文件做渲染。
那么我们可以推测,在 controller 里我们其实也可以这样使用render partial: 'shared/menu'
。我测试了下,的确可以。
至于问题3,相对简单些:在ActionView::Helpers::RenderingHelper#render定义的。 这个 Helper 方法还顺便提供了两个很有意思的功能。
一是 partial 可以当作 layout 来使用,
<%= render layout: 'shared/menu' do %>
<p>Content</p>
<% end %>
二是在 View 层可以简化 partial 的用法
<%= render partial: "form", locals: {zone: @zone} %>
<%# 可简化为 %>
<%= render "form", zone: @zone %>
那么简单总结下: