Puma 6渲染Enumerator时报错undefined method `to_ary'求助
Puma 6升级后流式渲染CSV出现
NoMethodError: undefined method 'to_ary'的解决方案 我们原本通过传递Enumerator给控制器的render_csv方法,实现大型CSV文档的流式输出,代码如下:
def render_csv(body:, filename:) # Tell Rack to stream the content headers.delete("Content-Length") # Don't buffer when going through proxy servers headers["X-Accel-Buffering"] = "no" headers["Cache-Control"] = "no-cache" headers["Last-Modified"] = Time.now.httpdate headers["Content-Type"] = "text/csv; charset=utf-8; header=present" headers["Content-Disposition"] = %(attachment; filename="#{filename}.csv") self.response_body = body end
升级到Puma 6后,触发了以下错误:
#<NoMethodError: undefined method `to_ary' for #<Enumerator: #<Enumerator::Generator:0x000000011e938568>:each>>
问题原因
Puma 6对响应体的处理逻辑做了变更:它会尝试调用to_ary方法来判断响应体是否为数组类型的可枚举对象,但标准的Enumerator并不实现to_ary方法,因此抛出该错误。
解决方法
方法1:自定义响应体类包装Enumerator
创建一个简单的包装类,实现each方法以支持流式输出,同时明确拒绝响应to_ary方法:
class StreamingBody def initialize(enumerator) @enumerator = enumerator end def each(&block) @enumerator.each(&block) end # 阻止Puma尝试调用to_ary def respond_to_missing?(method_name, include_private = false) method_name == :to_ary ? false : super end end
然后修改render_csv方法中的响应体赋值:
self.response_body = StreamingBody.new(body)
方法2:使用Enumerator::Lazy转换
将原Enumerator转换为惰性枚举器,Lazy版本的枚举器在Puma 6中不会触发to_ary检查:
self.response_body = body.lazy
方法3:直接实现Rack兼容的流式响应体
如果需要更贴合Rack规范,也可以将响应体设置为一个调用异步回调的Proc(适用于异步场景):
self.response_body = ->(env) do body.each do |chunk| env['async.callback'].call([200, headers, chunk]) end env['async.callback'].call(nil) # 结束流式传输 end
以上方法都能解决Puma 6中Enumerator触发to_ary错误的问题,同时保留流式输出的特性。
内容的提问来源于stack exchange,提问作者Tom Rossi
相关产品推荐
相关产品推荐

