Jesse Newland


Rails Metal: a micro-framework with the power of Rails: \m/


Josh Peek committed a new feature to Edge Rails today: Rails Metal. After the recent work to replace Rails’ crufty request processing code with Rack and integrate its middleware support, Rails Metal is a logical progression that allows Rails apps to use the power of Rack middleware to create super-fast actions.

For example, here’s a sample “Hello World” Metal:

  class Poller < Rails::Rack::Metal
    def call(env)
      if env["PATH_INFO"] =~ /^\/poller/
        [200, {"Content-Type" => "text/html"}, "Hello, World!"]
        [404, {"Content-Type" => "text/html"}, "Not Found"]

And for comparison, a “Hello World” controller:

    class OldPollerController < ApplicationController
      def poller
        render :text => "Hello World!"

So, let’s fire up ruby script/server and see what this gives us:

  # traditional Controller
  $ curl
    Hello World!
  # the new Metal
  $ curl
    Hello World!

So, the point of all of these other “micro-frameworks” is that they’re faster than Rails, right? Let’s benchmark this new “Hello World” Metal:

  # first, let's benchmark the traditional controller
  $ ab -n 1000
  ... snip ...
  Requests per second:    408.45 [#/sec] (mean)
  Time per request:       2.448 [ms] (mean)

  # now for the Metal middleware
  $ ab -n 1000
  ... snip ...
  Requests per second:    1154.66 [#/sec] (mean)
  Time per request:       0.866 [ms] (mean)

For this trivial “Hello World” benchmark, Rails Metal is 2.8x faster than a Controller. Awesome. Have a couple actions of your app you need to optimize? Instead of breaking them out into a separate application using a micro-framework, add a Metal inside your existing app. You get the performance benefits of processing requests outside of ActionPack, and it’s all integrated as a part of your Rails app. Easy!

Sinatra Metal

You can now also use Sinatra to create Metal end points:

  Sinatra::Application.default_options.merge!(:run => false, :env => 
  Api = Sinatra.application unless defined? Api

  get '/interesting/new/ideas' do
    'Hello Sinatra!'

First person to show the use of a Merb app as a Metal end point wins a prize.

Standalone Execution

Additionaly, Rails Metal are able to be executed in a separate process from your Rails application using rackup:

  rackup -s mongrel app/metal/poller.rb

This runs the Poller Metal separeately from Rails, on it’s own port (rackup defaults to 9292). This is perfect if you have an action that’s taking a very long time (for example a file upload) that you’d like to split out from the normal Rails request processing queue.

Testing Metal

Update: After several people commented asking how to test metal, DHH chimed in and recommend Integration Testing for Metal end points, as they hit the whole stack, and I submitted a patch cleaning up the Integration Testing behavior of Metal. Testing Metal end points now works just like any other Integration test:

      class PollerTest < ActionController::IntegrationTest
        test "poller returns hello world" do
          get "/poller"
          assert_response 200
          assert_response :success
          assert_response :ok
          assert_equal "Hello World!", response.body

Fun With Middleware

So, essentially, Rails Metal is a thin wrapper around Rails’ new Rack middleware support. Rack middleware is pretty powerful stuff: framework-independent components that process requests independently or in concert with other middleware. For example, here’s a simple piece of Rack middleware that runs a regex on responses:

class RegexMiddleware
  def initialize(app)
    @app = app

  def call(env)
    status, headers, response =
    new_response = []
    response.each do |part|
      new_response << part.gsub(/World/, 'Middleware')
    [status, headers, new_response]

To use this rack middleware in Rails, add this line to your environment.rb do |config|
  config.middleware.use RegexMiddleware

Restart your server, and check out what happens:

  $ curl
    Hello Middleware!

The Rack middleware filtered the output of the Metal we created before. This works with output generated by normal controllers and everything too. The possible uses of this pattern are endless:

rack-contrib is a nice collection of Rack middleware if you’re interested in more examples.

Rails Metal is a simple wrapper around the existing (yet undocumented) Rack middleware support in Edge Rails that attempts to DRY the process of using middleware to create endpoints (like a poller) as opposed to filters (which are better implemented as traditional middleware, like the examples above). For example, Rails Metal might be used:

We don’t need no stinking micro-frameworks

With the additional of Metal and Rack middleware support, Rails effectively includes a micro-framework of its own; one that either tighly integrates with Rails or is executed separately – whichever the need dictates.

This is a great response by the Rails team to all of the buzz surrouding micro-frameworks: a micro-framework with the power of Rails. I’m definitely going to try this approach to squeeze a couple extra requests per second out of a heavily trafficked API call – let me know in the comments if you find a use for it.

Read up a bit more on Rack and then take a look at Josh’s commit Introducing Rails Metal (and the ensuing comments) if you’re interested for more information.

Fork me on GitHub