Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sinatra helper to fake a request

Tags:

ruby

sinatra

Summary

Within a Sinatra web app, how can I make a virtual request to the application and get the response body back as text? For example, these routes...

get('/foo'){ "foo" }
get('/bar'){ "#{spoof_request '/foo'} - bar" }

...should result in the response "foo - bar" when requesting "/bar" with the web browser.

Motivation

My application has a page representing an bug entry, with lots of details about that bug entry: what version was the bug experienced in, how important is it, what tags are associated with it, to whom is the bug assigned, etc.

The user may edit individual pieces of data on this page interactively. Using my AJAXFetch jQuery plugin, JavaScript uses AJAX to swap out a read-only section of the page (e.g. the name of the person that this bug is assigned to) with an HTML partial form for editing just that section. The user submits the form, and AJAX makes a new request for the static version of that field.

In order to be DRY, I want the Haml view that creates the page to use the exact same request that AJAX makes when creating the individual static pieces. For example:

#notifications.section
  %h2 Email me if someone...
  .section-body= spoof_request "/partial/notifications/#{@bug.id}"

Not-Quite-Working Code

The following helper defining spoof_request worked under Sinatra 1.1.2:

PATH_VARS = %w[ REQUEST_PATH PATH_INFO REQUEST_URI ]
def spoof_request( uri, headers=nil )
  new_env = env.dup 
  PATH_VARS.each{ |k| new_env[k] = uri.to_s } 
  new_env.merge!(headers) if headers
  call( new_env ).last.join 
end

Under Sinatra 1.2.3, however, this no longer works. Despite setting each of the PATH_VARS to the desired URI, the call( new_env ) still causes Sinatra to process the route for the current request, not for the specified path. (This results in infinite recursion until the stack level finally bottoms out.)

This question differs from Calling Sinatra from within Sinatra because the accepted answer to that (old) question does not maintain the session of the user.

like image 489
Phrogz Avatar asked Apr 28 '11 20:04

Phrogz


2 Answers

The following appears to work as needed under Sinatra 1.2.3:

ENV_COPY  = %w[ REQUEST_METHOD HTTP_COOKIE rack.request.cookie_string
                rack.session rack.session.options rack.input]

# Returns the response body after simulating a request to a particular URL
# Maintains the session of the current user.
# Pass custom headers if you want to set or change them, e.g.
#
#  # Spoof a GET request, even if we happen to be inside a POST
#  html = spoof_request "/partial/assignedto/#{@bug.id}", 'REQUEST_METHOD'=>'GET'
def spoof_request( uri, headers=nil )
  new_env = env.slice(*ENV_COPY).merge({
    "PATH_INFO"    => uri.to_s,
    "HTTP_REFERER" => env["REQUEST_URI"]
  })
  new_env.merge!(headers) if headers
  call( new_env ).last.join 
end

where Hash#slice is defined as:

class Hash
  def slice(*keys)
    {}.tap{ |h| keys.each{ |k| h[k] = self[k] } }
  end
end
like image 155
Phrogz Avatar answered Oct 06 '22 18:10

Phrogz


It feels like I'm missing what you are trying to do but why not just call the defined method?

get('/foo'){ "foo" }
get('/bar'){ "#{self.send("GET /foo")} - bar" }

That is one funky method name by the way. Don't ask me why it's even allowed.

PS. This only works pre version 1.2.3. DS.

like image 35
Jonas Elfström Avatar answered Oct 06 '22 18:10

Jonas Elfström