Taking Merb for a Spin

Looking for an excuse to check out Merb? I was too and finally had a chance to check it out on the plane to RailsConf. The code base is definitely a work in progress but the code is very impressive and approachable. I’d encourage everyone to take a look at it - especially the ultra clean routing code which rocks the house!

Interested in taking a test drive? Good, keep reading.

Install Merb

$ sudo gem install merb

Sample Invoice Tracker app

With your forthcoming Merb hacking skills likely to result in a landslide of consulting gigs, we better build a custom app to track clients and invoices. If you want to skip ahead and grab the code you find it here.

$ merb -g invoice_tracker

The -g flag tells merb to create a skeleton app.

You can checkout all the options with the -h flag.

$ merb --help

Layout

The standard Merb layout is very similar to a Rails app except all code is in a dist subfolder. This makes sense as all you need to deploy is the dist folder when it comes time to put the app into production.

The rest of the layout should be very familiar for a Railer.

Database Config

Database setup currently occurs in conf/merb_init.rb. This is one part of Merb that could use some refactoring. It would be nicer to externalize this specification into a yaml file where multiple environments could be specified.

For now, put in your credentials. Don’t forget to create the database too…

Routing

conf/router.rb is where routes are specified.

Merb::RouteMatcher.prepare do |r|

  # r.default_routes installs stuff like
  #
  # /:controller/:action/:id
  # /:controller/:action
  # /:controller, :action => 'index'
  # ...
  #
  r.default_routes
  
  # just like Rails :)
  r.resources :clients

end

For this simple app all we need is:

conf/router.rb

Merb::Router.prepare do |r|
  r.resources :clients do |client|
    client.resources :invoices
  end
  r.add '', :controller => 'clients', :action => 'index'
end

Migrations

Ok, time to bang out this little app so we can start billing hours!

First let’s add a new migration:

$ ./script/new_migration CreateClients
$ ./script/new_migration CreateInvoices

Now check out the schema/migrations directory. Notice that 001_add_sessions_table.rb is the first migration which was automatically created by the merb generator. We also see our new migrations named 002_create_clients.rb and 003_create_invoices.rb.

Let’s create two simple migrations:

schema/migrations/002_create_clients.rb

class CreateClients < ActiveRecord::Migration

  def self.up
    create_table :clients do |t|
      t.column :name,     :string, :null => false
      t.column :address,  :string
      t.column :city,     :string
      t.column :state,    :string
      t.column :zip_code, :string
    end
  end

  def self.down
    drop_table :clients
  end

end

schema/migrations/003_create_invoices.rb

class CreateInvoices < ActiveRecord::Migration

  def self.up
    create_table :invoices do |t|
      t.column :client_id,    :integer,   :null => false
      t.column :description,  :string
      t.column :invoiced_on,  :datetime,  :null => false
      t.column :rate,         :decimal,   :null => false, :precision => 9, :scale => 2
      t.column :hours,        :decimal,   :null => false, :precision => 9, :scale => 2
    end
  end

  def self.down
    drop_table :invoices
  end

end

Now let’s run those migrations:

$ rake db:migrate

Models

As you may have already guessed ActiveRecord is the standard Merb way here so it’s the same way Rails does it.

app/models/client.rb

class Client < ActiveRecord::Base
  has_many :invoices, :dependent => :destroy, :order => :invoiced_on

  validates_presence_of :name

  def total_invoiced
    @total_invoiced ||= self.invoices.inject(0){ |sum, i| sum += i.total }
  end

end

app/models/invoice.rb

class Invoice < ActiveRecord::Base
  belongs_to :client

  validates_presence_of :rate, :hours, :invoiced_on

  def total
    self.rate * self.hours
  end

end

Controllers

Let’s create our clients controller with one initial index action. Notice that you leave off the _controller suffix preferred by Rails and must explicitly call render at the end of actions.

app/controllers/clients.rb

class Clients < Application

  def index
    @clients = Client.find(:all)
    render
  end

end

Views

Views are again very similar to Rails. You can use the following view suffixes: herb, jerb, erb, and rhtml. Let’s create a placeholder view for initial testing.

app/views/clients/index.erb

<h1>Clients</h1>

Run the App

$ merb

Navigate to http://localhost:4000

Hopefully it’s all working and you are looking at a page that says “Clients”

See, this Merb stuff isn’t tough at all!

Let’s take a look at a few other features…

Filters and Mime type handing

Let’s look at the fleshed out clients controller:

class Clients < Application
  before :find_client, :exclude => [ :new, :create, :index ]

  def new
    @client = Client.new
    render
  end

  def create
    @client = Client.create(params[:client])
    redirect clients_path
  end

  def index
    @clients = Client.find(:all)
    respond_to do |format|
      format.html { render }
      format.xml { render :xml => @clients.to_xml }
    end
  end

  def show
    respond_to do |format|
      format.html { render }
      format.xml { render :xml => @client.to_xml }
    end
  end

  def edit
    render
  end

  def update
    @client.update_attributes(params[:client])
    redirect client_path(@client)
  end

  def destroy
    @client.destroy
    render_js
  end

  private

    def find_client
      @client = Client.find(params[:id])
    end
  
end

Notice that Merb has the before “macro” to implement filters that run before actions. The cool thing about Merb filters is how they signal to halt the action. In Rails the returning of false to halt filters is rather arbitrary. It works but it’s not elegant. A Merb filter thows the symbol :halt. Let’s take a look at how this could work:

Let’s suppose we want to limit visibility of certain actions to admins only. We can use a before filter like this

class Clients < Application
  before :authorize

  ...
	
  private
	
    def authorize
      throw(:halt, 'No way Jose!') if !current_user.admin?
    end

end

This syntax is clean and clearly states the intention of the filter. Use :exclude and :only options to designate to which actions the filter applies.

Mime type handling is done in a very similar way to Rails with respond_to blocks. The few differences are:

  • Requirement of explicitly calling render
  • Calling render_js to render jerb templates (sets content type header to text/javascript)
  • No named urls yet (the redirects above that appear to be named routes are actually helper methods)

Form stuff

Merb has only a few of the niceties provided by ActiveSupport. We can however create form elements that map to ActiveRecord Models using the control_for methods:

<form action="/clients" method="post">
  <%= control_for @client, :name, :text %>
  <input type="submit" value="Create Client" />
</form>

Debugging and Patching

Want to add some logging statements or tweak Merb a bit? No problem. Merb has a rake task that freezes the framework into the dist/ folder.

$ rake merb:freeze

Now you tweak the code and then refreeze to wipe out any unwanted changes. Found a bug and looking to make a patch. Just freeze the framework from subversion.

$ rake merb:freeze_from_svn

Once the framework is frozen, use the following server command to bootstrap Merb with the local version:

$ ./script/merb

Helpers

Again, helpers are similar to those in Rails. Throw all global helper methods in app/helpers/global_helpers.rb. For controller-specific helpers, create a file named after the controller in the app/helpers dir.

app/helpers/clients_helpers.rb

module Merb::ClientsHelper

  def print_stuff
    'stuff'
  end

end

Layouts

Again Merb follows the general semantics of Rails when it comes to layouts. Merb will look for a layout named after the current controller and fallback to application.herb if one does not exist. There are also various helpers in Merb::ViewContextMixin like require_css and js_include_tag that are useful in layouts.

Sample Invoice Tracker app

  1. Grab the code

    $ svn co http://svn.depixelate.com/applications/invoice_tracker
    $ cd invoice_tracker/
  2. Create database named ‘invoice_tracker’

  3. Update username and password in dist/conf/merb_init.rb

  4. Run migrations

    $ rake db:migrate
  5. Start Merb

    $ merb
  6. Play with app at http://localhost:4000

Final thoughts

I hope I’ve wetted your appetite to install Merb and poke around. Ezra has really put together a great little framework and I highly recommend you take closer look. The code is very readable and the small size of the codebase makes it a quick study.

There is also a sample blog app called Mrblog that you can look at to learn a bit more.

Important: Note that Merb changes frequently as Ezra and contributors enhance and patch up the codebase. If you’re looking for help, you can post to the Mailing list or troubleshoot directly from the code.

Update 5/30/07

Ezra just released Merb 0.3.3 which has both a small router specification change and route generation helpers. I updated the sample app to work with the new routing format so if you happen to have Merb already installed, please upgrade to the latest and greatest.

References

Comment or question via
FYI: This post was migrated over from another blogging engine. If you encounter any issues please let me know on . Thanks.