Rails 看我如何解决 ActionView::MissingTemplate 的问题

ane · May 21, 2014 · Last by scott1743 replied at July 16, 2014 · 8743 hits
Topic has been selected as the excellent topic by the admin.

今天遇到 ActionView::MissingTemplate 一个问题,大多数情况下遇到这样的问题,通常都伴随着 HTTP_ACCEPT "/的情况。网上已经有很多文章解答如何 fix 这个问题,大多是指明具体的类型。但是很明显,这样的解决方案不是我目前想要的。

在练习《craft rails 4 applications》的第一个创建 pdf 的 lib 例子中就出现了Missing template home/index, application/index with {:locale=>[:en], :formats=>[:pdf], :handlers=>[:erb, :builder, :raw, :ruby]}. Searched in: * "/Users/ane/projects/plugin/pdf_renderer/test/dummy/app/views"这样讨厌的错误。

说说我的诊错流程:

  1. 第一步首先就是 googleActionView::MissingTemplate错误的前因后果。 2 .第二步 利用 debugger, ``` module PdfRenderer require "prawn"

ActionController::Renderers.add :pdf do |filename,options| debugger pdf = Prawn::Document.new debugger pdf.text render_to_string(options) debugger send_data(pdf.render,filename: "#{filename}.pdf", disposition: "attachment")
end end

最终定位到`pdf.text render_to_string(options)`的错误。于是简单修改为`pdf.text "render_to_string(options)"`,测试通过。所以必然是 `render_to_string(options)`的问题。

3.第三步:追踪发现 `options=={:prefixes=>["home", "application"], :template=>"index", :layout=>nil}`

4.第四步:先吃饭,晚上继续搞。

5.第五步:继续追踪 render_to_string

module ActionController module Rendering extend ActiveSupport::Concern

include AbstractController::Rendering .... def render_to_string(*) if self.response_body = super string = "" response_body.each { |r| string << r } string end ensure self.response_body = nil end ....

这里调用了super也就是`AbstractController::Rendering`中的`render_to_string`

111 def render_to_string(*args, &block) 112 options = _normalize_render(*args, &block) => 113 render_to_body(options) 114 end

到了这里,笔者天真的使用了*args来看第一个参数是什么,结果发现原来是用args

(rdb:2) args ** SyntaxError Exception: /Users/ane/.rvm/gems/ruby-2.0.0-p451@rails400/gems/actionpack-4.0.5/lib/abstract_controller/rendering.rb:112: syntax error, unexpected end-of-input, expecting '=' *args ^

(rdb:2) args {:prefixes=>["home", "application"], :template=>"index", :layout=>nil} (rdb:2) block nil

进入`_normalize_render`

151 def _normalize_render(*args, &block) => 152 options = _normalize_args(*args, &block) 153 _normalize_options(options) 154 options 155 end

#返回一个 Hash def _normalize_args(action=nil, options={}) case action when NilClass when Hash options = action when String, Symbol action = action.to_s key = action.include?(?/) ? :file : :action options[key] = action else options[:partial] = action end

options end

因为本来就是hash,所以152直接返回。(表示对文档的缓存功能大赞)

进入153

def _normalize_options(options) #:nodoc: if options.key?(:text) && options[:text].respond_to?(:to_text) options[:text] = options[:text].to_text end

if options.delete(:nothing) || (options.key?(:text) && options[:text].nil?) options[:text] = " " end

if options[:status] options[:status] = Rack::Utils.status_code(options[:status]) end

super end

因为if条件都不满足,直接进入super,这里我原本以为进入`AbstractController::Rendering`.可实际进入的的是`AbstractController`里,一开始还觉得奇怪,也发现了原来super的确是这样工作的。
先进入大模块AbstractController中的方法,如果不存在,就进入include的子module

#AbstractController def _normalize_options(options) # :nodoc: super

if _include_layout?(options) layout = options.delete(:layout) { :default } options[:layout] = _layout_for_option(layout) end end

def _include_layout?(options) (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout) end


#AbstractController::Rendering def _normalize_options(options) if options[:partial] == true options[:partial] = action_name end

if (options.keys & [:partial, :file, :template]).empty? options[:prefixes] ||= _prefixes end

options[:template] ||= (options[:action] || action_name).to_s options end

可以看出在`AbstractController::Rendering`中options依旧没有变化。

跟踪进入_layout_for_option(layout)

def _layout_for_option(name) case name when String then _normalize_layout(name) when Proc then name when true then Proc.new { _default_layout(true) } when :default then Proc.new { _default_layout(false) } when false, nil then nil else raise ArgumentError, "String, Proc, :default, true, or false, expected for `layout'; you passed #{name.inspect}" end end

原来给 options[:layout]赋上了一个proc

(rdb:2) Proc.new { _default_layout(false) } #Proc:0x00000101a64418@/Users/ane/.rvm/gems/ruby-2.0.0-p451@rails400/gems/actionpack-4.0.5/lib/abstract_controller/layouts.rb:379 (rdb:2)

通过对proc的注释`# Optionally raises an exception if the layout could not be found.`,初步怀疑问题处在这个proc中

anyway,先进入原先的154返回我们的options

151 def _normalize_render(*args, &block) 152 options = _normalize_args(*args, &block) 153 _normalize_options(options) => 154 options 155 end 156 157 # Normalize args by converting render "foo" to render :action => "foo" and 158 # render "foo/bar" to render :file => "foo/bar". (rdb:2) options {:prefixes=>["home", "application"], :template=>"index", :layout=>#Proc:0x0000010129b088@/Users/ane/.rvm/gems/ruby-2.0.0-p451@rails400/gems/actionpack-4.0.5/lib/abstract_controller/layouts.rb:383} (rdb:2)


6: 第六步:进入最上面的113`render_to_body(options)`

25 def render_to_body(options) 26 debugger => 27 _handle_render_options(options) || super 28 end


先看看   _handle_render_options(options)

(rdb:2) _handle_render_options(options) nil (rdb:2)

再看看super

(rdb:2) super *** NoMethodError Exception: super: no superclass method `render_to_body' for #HomeController:0x000001090092b8

(rdb:2)

呃,难道发现bug了?

好吧,还记的最初的   ` pdf.text "render_to_string(options)" `吗?看看它什么情况

(rdb:2) _handle_render_options(options) || super *** NoMethodError Exception: super: no superclass method `render_to_body' for #HomeController:0x00000104120908

原来一模一样。但是确实成功下载了。

7: 第七步,记得一定要先发帖子,再更新,这样就不怕放弃了,免得丢人。

8: 第八步, 倒回去,重lol。发现 controller中的`format.pdf { render pdf: "contents", :layout => false }
    end`之后就先进入一次

def render_to_body(options) 26 debugger => 27 _handle_render_options(options) || super 28 end

然后才进入我们定义的lib的

ActionController::Renderers.add :pdf do |filename,options| debugger pdf = Prawn::Document.new debugger pdf.text render_to_string(options) debugger send_data(pdf.render,filename: "#{filename}.pdf", disposition: "attachment")
end

。发现`pdf.text render_to_string(options)`的时候,_handle_render_options(options)是

(rdb:2) _handle_render_options(options) *** ActionView::MissingTemplate Exception: Missing template home/index, application/index with {:locale=>[:en], :formats=>[:pdf], :handlers=>[:erb, :builder, :raw, :ruby]}. Searched in:

  • "/Users/ane/projects/plugin/pdf_renderer/test/dummy/app/views"

(rdb:2)

`pdf.text "render_to_string(options)"`的时候,_handle_render_options(options)是

rdb:2) _handle_render_options(options) Rendered text template (0.0ms) Sent data contents.pdf (4.3ms) "%PDF-1.3\n%\xFF\xFF\xFF\xFF\n1 0 obj\n<< /Creator \n/Producer \n>>\nendobj\n2 0 obj\n<< /Type /Catalog\n/Pages 3 0 R\n>>\nendobj\n3 0 obj\n<< /Type /Pages\n/Count 1\n/Kids [5 0 R]\n>>\nendobj\n4 0 obj\n<< /Length 103\n>>\nstream\nq\n\nBT\n36 747.384 Td\n/F1.0 12 Tf\n[<72656e6465725f746f5f737472> -15 <696e67286f7074696f6e7329>] TJ\nET\n\nQ\n\nendstream\nendobj\n5 0 obj\n<< /Type /Page\n/Parent 3 0 R\n/MediaBox [0 0 612.0 792.0]\n/Contents 4 0 R\n/Resources << /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]\n/Font << /F1.0 6 0 R\n>>\n>>\n>>\nendobj\n6 0 obj\n<< /Type /Font\n/Subtype /Type1\n/BaseFont /Helvetica\n/Encoding /WinAnsiEncoding\n>>\nendobj\nxref\n0 7\n0000000000 65535 f \n0000000015 00000 n \n0000000109 00000 n \n0000000158 00000 n \n0000000215 00000 n \n0000000369 00000 n \n0000000547 00000 n \ntrailer\n<< /Size 7\n/Root 2 0 R\n/Info 1 0 R\n>>\nstartxref\n644\n%%EOF\n"


很明显两者的区别太大了。

9:第9步,停下来,重新lol.刨根问底有点深了,把自己给埋了。砰砰砰,请看下集

初步怀疑

render_to_body(options) _handle_render_options(options) || super end

中的super是不是个bug? format.pdf一定有故事。







  1. 你都没说你想要什么样的结果?
  2. 你只说你发现了,还没来的及处理,你这事看来还得追踪报导呀!

各个 Rails App 都有这个问题,关注楼主分析,得出解决方案

...那么……如何重现这个问题呢。。。

这就精了?管理是不是把加精当成私人收藏了?

5 Floor has deleted

#3 楼 @Kabie 可以参考《craft rails 4 applications》的第一个 pdf 的 gem 例子。

#6 楼 @ane 所以说。。。我重现不出来啊。。。

#4 楼 @kikyous 重点是楼主的过程

9 Floor has deleted
10 Floor has deleted

#8 楼 @huacnlee 楼上发广告的来了,看来论坛越来越有名了,怎么建立一个有效的防水墙呢?关键字过滤不是很有效果。

12 Floor has deleted

#7 楼 @Kabie 我找到原因后,重新发一个。

#7 楼 @Kabie 我找到原因后,重新发一个。

#11 楼 @Peter 我是不是也是小广告队的?(⊙o⊙)…

楼主码字辛苦了,我不能赞同的更多了。

#16 楼 @agilejzl 我最近重新试了一边,发现之前有错误认知,等我稍后更新

这是预告片

You need to Sign in before reply, if you don't have an account, please Sign up first.