You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 18:42:41