Why Is Rack Response Body an Array Not a String

Why is rack response body an array not a string?

I think rack originated on python's wsgi. Here is the explanation for python:
http://www.python.org/dev/peps/pep-3333/#buffering-and-streaming

Rack Error Rack::Lint::LintError: Response body must respond to each

This is a change in Ruby 1.9.2 and, as has been suggested, if you surround your string with ["brackets"] it will turn "Hello World" into an array with a single value. Sounds silly, but that's the deal :).

It used to be that a String in Ruby would respond to each with an iteration of characters. Evidently there have been changes that way.

render :partial returning an array when defined in ApplicationController but string when in ApplicationHelper

Simply put, don't call the controller's render in helpers. It just does not work that way

render in the controller and render in a helper can't be used interchangeably. This isn't new in Rails 3.1.

When you call render in the controller it eventually does call render on the view, the result of which is stored as its response_body. The response body is eventually returned in the way Rack expects, as a string array (what you see as your output).

These links may shed some more light on how this works:

- The controller's definition of render (metal)

- It's superclass method, where response_body is set (abstract_controller)

Rack::Deflater and Rack::URLMap

It looks like Rack::BodyProxy does provide an #each method as a workaround of issue rack/rack#434. This method returns an enumerator over the elements of the private @body ivar. Adding an .each into the call chain resolves this problem for Rack::BodyProxy objects, and it's a no-op for more ordinary array-based bodies, so for now this gets me where I need to be. However, I would still like to understand why I'm getting these objects and if there's a better way to deal with them.

Here's the modified and working solution:

use Rack::Deflater, :if => lambda { |*, body| 
body.each.map(&:bytesize).reduce(0, :+) > 512
}

UPDATE: Hrrm, sometimes body is a Rack::BodyProxy object, and body.body is a Rack::Response object. This is getting a little out of hand...! Here's the solution I'm using now:

use Rack::Deflater, :if => lambda { |*, body|
body.map(&:bytesize).reduce(0, :+) > 512 \
if body.respond_to?(:map) \
or body.respond_to?(:each) and (body = body.each).respond_to?(:map)
}

Far from elegant...

HelloWorld Rack Program : NoMethodError: undefined method `each'

change:

def response
[200, {}, 'Hello World']
end

to:

def response
[200, {}, ['Hello World']]
end

The following program prints the value of the variable. Why?

In case of ambiguity when compiler finds a variable and method with same name in same scope, it gives precedence to the variable.

To call the method explicitly, send empty parens ()

hello_world = 'Hello Ruby World' 

def hello_world
'Hello World'
end

puts hello_world()

or provider an explicit receiver to the method, in this case, using self

self.hello_world

Edit:
As sepp2k advised in comments below, self.hello_world would not work with a ruby(.rb) file. Just to try, you can dynamically dispatch the method with send:

send(:hello_world) #or
method(:hello_world).call

Alter response.body in Rack Middleware

The problem was that it expects an Array for the 3rd argument in the call method. This pattern got me working again.

# not real code, just a pattern to follow
class MyMiddleware
def initialize(app)
@app = app
end

def call(env)
status, headers, response = @app.call(env)
new_response = make_new_response(response.body)
# also must reset the Content-Length header if changing body
headers['Content-Length'] = new_response.bytesize.to_s
[status, headers, [new_response]]
end
end


Related Topics



Leave a reply



Submit