Rack in Rails

If you enjoy this article you may be interested in the book I am working on called Building and Deploying Crypto Trading Bots. You can find more info about it here.

What does it mean for Rails to be be a "rack based web framework?". Let's find out

When a request is first made to a URL, that request's entry-point to a Rails application is through a Ruby webserver interface called rack. Rack is a Ruby gem that provides a minimal interface for connecting web servers (such as Puma, WEBrick, or Unicorn) and web frameworks (such as Rails, Sinatra, Hanami).

Rack in the lifecycle of a request‌‌

Rack's main job is to act as a translator for HTTP requests and responses to and from a webserver  into a standard format for higher level applications to act on. The benefit of doing this is to prevent reimplementation of the same request and response logic processing with each new framework or web application (remember rack is just a Ruby gem and is used outside of Rails!).

Understanding this, the Rack protocol then specifies that:

a Rack application is a Ruby object that responds to the method "call" with exactly one argument, the environment and returns an Array of exactly three values: The status, the headers, and the body.

A minimal rack application implementation is demonstrated below.

class MyRackApp
  def call(env)
   if env['PATH_INFO'] == '/index'
    [200, {'Content-Type => 'text/plain'}, ['Welcome to my rack app']]
  else
    [404, {'Content-Type => 'text/plain'}, ['Page was not found']]
  end
end

run MyRackApp.new

When a request is made to our webserver, the rack gem will call MyRackApp's instance method call with the environment of the request. This environment is a Hash object that includes CGI-like headers (Common Gateway Interface) passed to the application that may freely modify it.

Ruby on Rails is a rack-based web framework which means when you create a Ruby on Rails application you are creating a rack application under the hood! When a request is made to the webserver rack absorbs the request information and invokes the Rails.application base call method to dispatch the request to the Rails application's middleware and eventually up to your application controllers.

When your application controllers respond they eventually cascade their response back down to the call level and with a payload in the format required in the Rack specification. In other words an array containing the Status, Headers, and Body

[ Status, Headers, Body ]

We can see this when the Rack::Response  finish method is called:

# Generate a response array consistent with the requirements of the SPEC.
# @return [Array] a 3-tuple suitable of `[status, headers, body]`
# which is suitable to be returned from the middleware `#call(env)` method.
def finish(&block)
  if STATUS_WITH_NO_ENTITY_BODY[status.to_i]
    delete_header CONTENT_TYPE
    delete_header CONTENT_LENGTH
    close
    return [@status, @headers, []]
  else
    if block_given?
      @block = block
      return [@status, @headers, self]
    else
      return [@status, @headers, @body]
    end
  end
end
Rack Source Code

And we can see that Rails does conform to the Rack response standard by marshalling the response the ActionDispatch::Response module:

# Turns the Response into a Rack-compatible array of the status, headers,
# and body. Allows explicit splatting:
#
#   status, headers, body = *response
def to_a
 commit!
 rack_response @status, @header.to_hash
end

alias prepare! to_a
Rails Source