Merb.next

No, ladies and gentlemen, we haven't taken a break after releasing 0.5 (The "Thanks Zed" release). With 0.5 out, stable and fast, the Merb team finally took a hard look at where we're going with the framework. Thanks to Engine Yard's investment in our work, I was able to meet with Ezra in person and spent a large part of last week planning out Merb.next.

First up, we're closing in on Merb 1.0. The next major release will be numbered 0.9, and will be the point release immediately prior to 1.0. In order to help make Merb even more modular and tight, Merb 0.9 will be broken up into a merb-core gem and a package of merb-more gems. The entire package will still install via gem install merb, and will be fully maintained by the core team. However, in Merb.next, it'll be trivial to leave off chunks of functionality that you don't want.

Merb Core, which we're working on right now (you can follow our progress at Git Hub), will be the tight core of the framework, just enough to run basic web services and simple web sites. It'll be possible to use Merb Core for a single-page app (a la Camping or Sinatra) or an app with a full-blown application structure (like Rails or current Merb).

Merb 0.9 will also tie up a bunch of loose ends, not shying away from breaking things in order to give Merb 1.0's API as much longevity as possible. At the same time, we're trying to make the upgrade path from Merb.current to Merb.next as painless as possible.

In an effort to keep Merb core really tight, we're also adopting some more formal programming and documentation techniques that will make working on and using Merb much easier:

  • All methods (public and private) are required to provide a clear method signature, including the types for any parameters, and the possible values for any options hashes, as well as return types and other information (see below for an example).
  • Methods can be tagged @public, which means that changing them will break the public API. Methods can also be tagged @semipublic, which means that they're used in other parts of the framework and possibly the test harness. This'll aid future refactoring, because methods that are not public or semipublic can be reimplemented at will as long as the test suite still passes.
  • We're removing all hardcoded paths from the framework. This means that Merb will scale from a single-page app to any application structure you want. You can even specify how to name template files (you can go from the traditional controller/view.mime.type or controller.view.mime.type or even view.mime.type for a single controller app)
  • We're splitting our test suite into public and private tests. Public tests test the public API, while private tests test things like the Request object. Public tests are required to use only methods in the public or semipublic API, so they should still pass after major refactors.

Merb Core is also based on a Rack adapter now, so it'll work out of the box with Mongrel, Evented Mongrel, FCGI, regular CGI, thin, webrick, and Fuzed, as well as any server you can write a rack adapter for. And fear not, you'll still have access to the raw mongrel request if you want to do fancy stuff like streaming or deferred rendering outside the mutex.

Here's an example of the new documentation standards:

  # Render the specified item, with the specified options.
  #
  # ==== Parameters
  # thing<String, Symbol, nil>:: 
  #   The thing to render. This will default to the current action
  # opts<Hash>:: An options hash (see below)
  #
  # ==== Options (opts)
  # :format<Symbol>:: A registered mime-type format
  # :template<String>:: 
  #   The path to the template relative to the template root
  # :status<~to_i>:: 
  #   The status to send to the client. Typically, this would
  #   be an integer (200), or a Merb status code (Accepted)
  # :layout<~to_s>::
  #   A layout to use instead of the default. This should be
  #   relative to the layout root. By default, the layout will
  #   be either the controller_name or application. If you
  #   want to use an alternative content-type than the one
  #   that the base template was rendered as, you will need
  #   to do :layout => "foo.#{content_type}" (i.e. "foo.json")
  #
  # ==== Returns
  # String:: The rendered template, including layout, if appropriate.
  #
  # ==== Raises
  # TemplateNotFound::
  #   There is no template for the specified location.
  # 
  # ==== Alternatives
  # If you pass a Hash as the first parameter, it will be moved to
  # opts and "thing" will be the current action
  #
  #---
  # @public
  def render(thing = nil, opts = {})
    &lt;snip&gt;
  end

Some description: The render method takes two parameters, a "thing" to render and an options Hash. The "thing" can be a String, Symbol, or nil. The options hash takes :format, :template, :status, and :layout options, which are described under Options. Th :status option must respondto? toi and the :layout options must respondto? tos. It returns a String (the rendered layout), and raises a TemplateNotFound if the template specified is not found. It is part of the @public API.

The documentation format will be itself documented as part of the Merb Core release, and we hope to write a parser that uses RDoc to spit out just the public API (leaving out public methods that are not marked @public).

The public test suite itself is also designed to provide a useful starting point for Merb plugin authors; here's an example:

  # @public
  it "should accept template-type registrations via #register_extensions" do
    Merb::Template.register_extensions(Merb::Test::Fixtures::MyTemplateEngine, %w[myt])
    Merb::Template.engine_for("foo.myt").should == Merb::Test::Fixtures::MyTemplateEngine
  end

All in all, it's going to be damn tight, formal, and grokkable. I plan to release some code traces once 0.9 is done that trace the path of a request through the Merb framework, so you can grok exactly how requests go through the pipeline and are eventually rendered.

Good Coding and Good Luck!