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

Comments

Leave a response

  1. Dr NicApril 02, 2007 @ 02:15 AM
    Nice overview of Merb + Mongrel vs Rails + Mongrel
  2. Luke FranclApril 02, 2007 @ 10:41 AM
    Thanks, Dr. Nic! See you at RailsConf...
  3. Ezra ZygmuntowiczApril 02, 2007 @ 04:23 PM
    Blech, that post did not respect my line breaks so it's kinda ugly, sorry.
  4. Ezra ZygmuntowiczApril 03, 2007 @ 09:32 AM

    Hey Luke-

    Nice write up, thanks for doing that! I just have a few things to add…

    1. merb now supports multiple template engines erubis, markaby, xml builder and haml templates out of the box and it’s easy to add new template engines.

    2. You are right that Merb does not auto render for you in your controller actions, I consider this a big advantage over the way rails does it for a few reasons. The main reason is that in rails you can only render once per action, so it knows if you haven’t rendered it shoudl auto render. Merb on the other hand, returns to the browser whatever the return value of your controller’s action method is. This opens up more possibilities imho because now you can return any string from your action and that will be sent down the pipe. So Merb’s render method just returns a string and needs to be the last thing you call in your action. You can render multiple times and capture the results into @ivars and then render a master template with many embeded templates. ALso if you return a handle on a FIle or IO object from your action then merb will hand that over to mongrel to be streamed out to the client. And if you return a Proc object from your action, it will be called and the return value sent to the client.

    That last point has some cool connotations if you think about it. Merb does have a mutex lock around the call to your controller’s action anywhere that you can call AR objects. Merb’s lock is way smaller then rails giant lock though and allows for many more concurrent requests to be handled by one process. By returning a Proc object from your action, you allow merb to release the lock and the proc is called in multi threaded way. This allows for all kinds of cool streaming and ‘futures’ where you return the proc and release the mutex. It’s basically like handing over the proc to mongrel and mongrel handles calling it in a thread safe manner.

    I know the docs are lacking and for that I appologize. I will soon have http://merbivore.com live with a blog/wiki/docs for merb. Right now it’s just the rdoc.

    Merb is meant to be a lighter framework with simple non magical code so that people can bend the framework to their own will instead of being bent by the framework to its will ;)

    I do have a merb plugin that has a port of most of the important Rails form helpers though. This can help ease the transition for people moving over, I will post it on the merb trac here shortly.

    Thanks again for the writeup. I’m usually in #merb on irc.freenode.net if anyone has any burnging questions.

  5. Luke FranclApril 03, 2007 @ 09:43 AM

    Hey Ezra, thanks for the comment. I changed the comment format setting to Textile and reposted it.

    Regarding the lack of auto-render, I don’t think it’s bad, I just sometimes forget about it and wonder why nothing is rendering, so I thought it was important to mention.

    The “less magic” is a good point, too. I wish I’d mentioned that in my presentation because that is an important difference. And I think the “magic” in Rails definately gets in the way of new people learning the framework. “Why is that happening? I have no idea. Magic!”—it just isn’t intuitive.

    Good to hear the documentation is coming along!

  6. Joe RubyApril 04, 2007 @ 02:47 AM

    Anybody know if Rails is addressing points 1 and 3? Maybe Ezra’s efforts would be better spent there. :P

  7. bannerApril 04, 2007 @ 04:03 AM

    “Rails isn’t exactly the most upload friendly framework, indeed Rails doesn’t allow multiple concurrent file uploads at once without blocking an entire rails backend for each file upload. So, I’ve got this system where merb handles file uploads, saves them locally, than sends an REST api call to Rails, creating a new asset. Instead of merb posting the file data, Rails looks in the designated merb upload directory and reads the files straight off disk. Merb then returns the result of the Rails request to the user.” - http://www.aireofs.com/

  8. Luke FranclApril 04, 2007 @ 10:06 AM

    Joe—I kind of doubt Rails will ever be thread safe. I think a lot of the “magic” relies on it, and usually it doesn’t matter much since you can run dozens of mongrel processes on a single server. Regarding getting rid of CGI.rb, supposedly Zed Shaw is working on something. That would be a great win for performance I think.

    banner—Can you talk about how you deployed Merb and Rails together? I didn’t get into that because I didn’t need to, but it would be useful to know.