Merb (and why you potentially should care)

Posted by Luke Francl
on Sunday, April 01

Update: We’ve posted a new and updated Merb article in the form of an interview with Ezra. This original article was presented at the March 27, 2007 Ruby Users of Minnesota meeting. You may also download the presentation (PDF, 100K).

Merb is “Mongrel + ERB,” written by Ezra Zygmuntowicz of Engine Yard.

I must admit that when I first heard of Merb, I thought, “Ah, PHP is making a comeback.” But Merb is actually a small Model-View-Controller framework similar to Rails in many ways, but with some features that make it superior for some tasks, like handling file uploads.

Key features in Merb:

1. Merb is thread-safe

Merb does not use ActionPack which is the main offender of thread unsafety in Rails. ActionPack is the Rails controller and view system. Merb implements its own controllers, and doesn’t have a whole lot in terms of view helpers.

Note: Ruby uses “green” threads, which are implemented inside the Ruby interpreter, not at the operating system level. To take advantage of multi-CPU machines, you will need to run (at least) one ruby process per CPU.

2. Merb uses Erubis for embedded Ruby

Erubis is a re-implementation of ERB that is significatnly faster. Erubis is 3 times faster than ERB, and even 10% faster than eRuby, which is a C implementation of ERB!

Note: Erubis is close to being fully supported in Rails. Perhaps in the next release…?

3. Merb does not use CGI.rb

CGI.rb is quite slow and very processor intensive, especially for parsing multipart form uploads. Merb doesn’t use it. However, Merb does use regular expression matching to parse requests, so it’s possible it could suffer from the same problems; I haven’t investigated this myself, though.

This last feature is critical. You may have heard that Rails blocks while handling file uploads. When you’re using Mongrel, this is not strictly true. Yes, Mongrel blocks while running a Rails request. But Mongrel pre-buffers file uploads, and hands the entire file to Rails.

[File] upload doesn’t block Rails actions going on, when you finally pass this to Rails you’ll block that Mongrel process while cgi.rb is going.

This is why you should make a separate Mongrel handler to do all of your upload processing and file preparation before you pass the fully cooked stuff to Rails. Mongrel running cgi.rb in a thread is much more efficient than Rails running cgi.rb inside a lock.

Zed Shaw (emphasis mine)

Merb is essentially an easier way to write a custom Mongrel upload handler. And as a bonus, it does not use CGI.rb at all.

Do you need Merb?

The short answer is “no.” The longer answer is “not yet.”

We planned on using Merb in a recent project at Slantwise. You know the drill: the customer says the site’s going to be huge, it needs to scale, yadda yadda. We don’t believe in pre-optimizing, but we thought we’d take a look at Merb and see how hard it would be to integrate with a Rails app.

After talking to Ezra about it, we decided not to use Merb—until we need to.

We had fallen under the widely held misconception that Mongrel blocks during file uploads. It doesn’t. So we will load test the application and decide after that if we need more uploads per second.

Installing Merb

Merb is available as a gem. However, development moves fast and you will run into bugs that get fixed in the trunk that aren’t available in the gem. If you decide you need to use Merb, I recommend running off the trunk.

sudo gem install mongrel json erubis archive-tar-minitar rspec -y

svn co http://svn.devjavu.com/merb/trunk merb && cd merb && rake install

(This last command will create a gem from the trunk.)

Then you can generate a new merb app:

merb -g myapp

Merb versus Rails

Merb uses ActiveRecord, so all your favorite features are there. It’s Model-View-Controller so development is similar.

In general, Merb is rougher around the edges than Rails, and documentation is harder to come by and less expansive. This is to be expected, as the framework is a lot newer than Rails with fewer users. But development is ongoing and the mailing list is friendly. There’s also a sample application called MrBlog you can learn from.

Here’s some of what you can expect:

  • Generator scripts are not generally available (there is one for creating a migration).
  • Routing is different (simple but functional, and RESTful routes are available).
  • There are not many view helpers—you write straight ERB most of the time.
  • Don’t forget to call render in your controller actions—Merb doesn’t do it for you.
  • Most configuration is in Ruby rather than YAML.
  • You may run into bugs (file them in Trac).

Merb + Rails

Perhaps more interesting is getting Merb working with your existing Rails app, for example, to handle file uploads and processing.

  • Merb uses ActiveRecord, so you can use your existing data model.
  • If you store your sessions in the database, Merb can piggy-back on the Rails session ID, so you can use your existing authorization mechanism.

In about half a days work, I was able to get Merb loading up my ActiveRecord objects from my Rails project (to avoid code duplication), and get the attachment_fu plugin working.

Use your Rails models in Merb

You can include your Rails models in Merb.

Here’s an example. DIST_ROOT is the Merb root directory. In this example, I’ve installed the Merb app inside the Rails root, so I go down to the Rails root and back up to the model I want.

1
2
3
4

class Item < ActiveRecord::Base
end
require DIST_ROOT + '/../../app/models/item.rb'

Getting ActiveRecord plugins working

Obviously, not all Rails plugins will work with Merb. But a lot of Rails plugins are really just ActiveRecord plugins. Attachment_fu is an example. If attachment_fu worked with Merb, you’d have a simple way to process uploads for your Rails application.

I got attachment_fu working with Merb in an afternoon with a few tweaks to the code. The code’s too ugly to share here, but I can tell you how I did it.

Mostly I had to change how files were required, because Rails does a lot of magic to load plugins. I had to require each file individually. Attachment_fu also relies on the RAILS_ROOT constant. I defined it equal to Merb’s DIST_ROOT.

One change in attachment_fu itself was required.

1
2
3
4
5
6
7
8
9
10
11
12
13

# TODO: Allow it to work with Merb tempfiles too.
def uploaded_data=(file_data)
  return nil if file_data.nil? || file_data.size == 0 
  self.content_type = file_data.content_type
  self.filename     = file_data.original_filename if respond_to?(:filename)
  if file_data.is_a?(StringIO)
    file_data.rewind
    self.temp_data = file_data.read
  else
    self.temp_path    = file_data.path
  end
end

See that TODO? Because Merb doesn’t use CGI.rb, the file_data object is actually a File, and it lacks some methods.

This is pretty easy to work around:

1
2
3
4
5
6
7
8
9
10
11
12
13

def uploaded_data=(file_data)
  return nil if file_data.nil? || file_data.size == 0 
  self.content_type = file_data.content_type if file_data.respond_to?(:content_type)
  self.filename     = file_data.original_filename if respond_to?(:filename) && file_data.respond_to?(:original_filename)
  if file_data.is_a?(StringIO)
    file_data.rewind
    self.temp_data = file_data.read
  else
    self.temp_path    = file_data.path
  end
end

This isn’t ideal (after all, it’d be nice to know the content type and the original file name) but it worked for my demo.

Deploying Merb with Rails

I have not done this myself, but I believe the correct way to do it is to point your load balancer to your Merb app for certain requests—like POSTs to /upload.

The nice thing about this is that you can develop your Rails app as normal. If you find Rails cannot handle the load, you could drop in Merb to handle file uploads and processing at the same URL. The Merb app would handle the upload, then redirect back to your Rails app.

Resources