Merb logo

I’m curious. I’ve always been curious in my life, so now I’d like to play a little with Merb, a project by Ezra Zygmuntowicz of EngineYard.

After all I’m starting to think that Rails (to be clear: more and more I find it really nice and impressive under different standpoints) is a little over-hyped. It’s a nice framework, but sometimes I find it a little “fat”: a lot of extension, gem, module are preloaded.

So I’ve “met” Merb: claimed as “lightweight” I was curious to play with it a little. One of he most nice thing I have learnt was the “other ORM: DataMapper. It has a really clean and short syntax, though it seems to be enough compatible with AtiveRecord showing a lot of method names in common.

Other thing I liked a lot to find in Merb was the RSpec approach to testing, though “classical” Test::Unit is still present.

Installing all the required components

Ok, first of all you’ve to install the required gems:

$ sudo gem install merb
$ sudo gem install mongrel json json_pure erubis mime-types rspec
       hpricot mocha rubigen haml markaby mailfactory Ruby2Ruby

Merb can use the well know ActiveRecord ORM (like Rails) to manage object persistence, but now I’d like to show you an interesting alternative: DataMapper. So install it:

$ sudo gem install datamapper merb\_datamapper merb\_helpers

And now we need a specific driver: do\_mysql, do\_sqlite3, do\_postgres. I’d like to use mysql, so:

$ sudo gem install do_mysql

..and a little problem appear, but don’t worry: let fix it!

Reach the do\_mysql gem directory (on my system is: /usr/local/lib/ruby/gems/1.8/gems/do\_mysql-0.2.3/), open the file ext/mysql\_c.i and change line #41 to include the correct header file:

%include "/usr/local/mysql-5.0.41-osx10.4-i686/include/mysql.h"

Then edit the file ext/mysql\_c.c and comment the line #16079:

/*  rb\_define\_const(mMysql\_c, "MYSQL\_OPT\_SSL\_VERIFY\_SERVER\_CERT",
    SWIG\_From\_int((int)(MYSQL\_OPT\_SSL\_VERIFY\_SERVER\_CERT))); */

Now we’re ready to recompile and reinstall the gem package:

$ sudo make clean
$ cd ..
$ rake repackage
$ cd pkg
$ sudo gem install ./do_mysql-0.2.3.gem

Environment setup

After installed all the required components (gem) the first step is to create a brand new application:

$ merb blog

As you can see merb command create the structure of our blog application, and if you are a rails developer there isn’t nothing to worry too much about here: skeleton and directory are almost the same, with little variations.

Then we need some databases: for development environment, for testing and (hopely) for production. So let’s create them (here I’m using MySQL but it’s not mandatory):

$ echo "create database blog\_dev" | mysql -u root
$ echo "create database blog\_test" | mysql -u root
$ echo "create database blog\_prod" | mysql -u root

Now the real dive into Merb begin: configuration!

In the file config/dependencies.rb (approximatively around line 13) we need to uncomment the following line in order to enable the +DataMapper+ ORM usage:

use\_orm :datamapper

The smart reader have noticed (didn’t you?) the absence of a database configuration file, in fact it still doesn’t exists. Lets create it issuing a:

$ rake -T
No database.yml file found in ~/blog/config.
A sample file was created called database.sample.yml for you to copy and edit.

After having enabled the usage of an ORM, Merb noticed the absence of such file and created a stub for us in config/database.sample.yaml.

We have to rename it in config/database.yml and fill in the correct values:

  # This is a sample database file for the DataMapper ORM
  :development: &defaults
  :adapter: mysql
  :database: blog_dev
  :username: root
  :password:
  :host: localhost

  :test:
    <<: \*defaults
    :database: blog_test

  :production:
    <<: \*defaults
    :database: blog_prod

Now issuing again a rake -T command we obtain the task list:

  rake aok                   # Run all tests, specs and finish with rcov
  rake clobber_rcov          # Remove rcov products for rcov
  rake controller_specs      # Run all controller specs
  rake dm:db:automigrate     # Perform automigration
  rake dm:sessions:clear     # Clears sessions
  rake dm:sessions:create    # Creates session migration
  rake haml:compile_sass     # Compiles all sass files into CSS
  rake merb:freeze           # freeze the merb framework into merb for portab...
  rake merb:freeze_from_svn  # freeze the merb framework from svn, use REVISI...
  rake merb:unfreeze         # unfreeze this app from the framework and use s...
  rake merb_env              # Setup the Merb Environment by requiring merb a...
  rake merb_init             # load merb_init.rb
  rake model_specs           # Run all model specs
  rake rcov                  # RCov
  rake spec                  # Run a specific spec with TASK=xxxx
  rake specs                 # Run all specs
  rake specs_html            # Run all specs output html
  rake svn_add               # Add new files to subversion
  rake test                  # Run tests for test
  rake test_functional       # Run tests for test_functional
  rake test_unit             # Run tests for test_unit

Ok, now we are ready to start building our blog application.

Model creation

Every blog has a series of post, so the first model we need is a “post” model. As in rails we have generators here:

$ script/generate model Post title:string body:text created\_at:datetime
Connecting to database...
      exists  app/models
      create  app/models/post.rb
  dependency  merb_model_test
      exists    spec/models
      create    spec/models/post\_spec.rb

Without any surprise the file app/models/post.rb was generated by merb:

class Post &lt; DataMapper::Base
  property :title, :string
  property :body, :text
  property :created\_at, :datetime
end

The first thing to note is that DataMapper describe model in unique file, that is: you don’t need to jump your eyes from a migration file to a model file to understand (or simply remember) all the fields and validations and associations you made on your models.

I really like this approach: *it’s really DRY*.

So we’ve defined three properties (fields) in the ruby class, now we’ve to tell merb to generate the corresponding table in databases:

   $ rake dm:db:automigrate
        Connecting to database...
        Started merb\_init.rb ...
        Loading Application...
        Compiling routes..
        Loaded DEVELOPMENT Environment...
 
        $ echo "desc posts" | mysql -u root --database blog_dev -t
        +------------+-------------+------+-----+---------+----------------+
        | Field      | Type        | Null | Key | Default | Extra          |
        +------------+-------------+------+-----+---------+----------------+
        | id         | int(11)     | NO   | PRI | NULL    | auto_increment |
        | title      | varchar(50) | YES  |     | NULL    |                |
        | body       | text        | YES  |     | NULL    |                |
        | created_at | datetime    | YES  |     | NULL    |                |
        +------------+-------------+------+-----+---------+----------------+

Umm… DataMapper created a primary key column for us, and inflected correctly the table name (posts). Let’s play a little with it starting an interactive merb session (console in rails world):

# hit that command in blog directory, not elsewhere!
$ merb -i
Started merb_init.rb ...
Connecting to database...
Loading Application...
Compiling routes..
Loaded DEVELOPMENT Environment...
Not Using Sessions
      irb(main):002:0&gt; p = Post.new
        =&gt; #
&lt;post @new_record="true," @title="nil," @created_at="nil," @body="nil," @id="nil"%gt;
        irb(main):003:0&gt; p.comments &lt;&lt; Comment.new
        =&gt; [#&lt;comment @new_record="true," @content="nil," @post_id="nil,"
              @created_at="nil," @id="nil"&gt;]
        irb(main):004:0&gt; p.comments.first.content = 'really nice your post!'
        =&gt; "really nice your post!"
        irb(main):005:0&gt; p.valid?
        =&gt; false
        irb(main):006:0&gt; p.title = 'my first post (again)'
        =&gt; "my first post (again)"
        irb(main):007:0&gt; p.body = '....'
        =&gt; "...."
        irb(main):008:0&gt; p.valid?
        =&gt; true
        irb(main):009:0&gt; p.save
        =&gt; true
        irb(main):010:0&gt; Post.count
        =&gt; 1
        irb(main):011:0&gt; Comment.count
        =&gt; 1
        irb(main):012:0&gt;

As you can see everything is usual.

Ok, now we need a comment to attach on post (otherwise what kind of blog it will be?). So generate another model:

$ script/generate model Comment
Connecting to database...
      exists  app/models
      create  app/models/comment.rb
  dependency  merb_model_test
      exists    spec/models
      create    spec/models/comment\_spec.rb

and see the content of the generated app/model/comment.rb file:

class Comment &lt; DataMapper::Base
end

As we have not declared any fields, the model is empty. It’s up to us now to insert appropriate property declarations:

class Comment &lt; DataMapper::Base
  property :content, :text
  property :created\_at, :datetime
 
  belongs\_to :post
  validates\_presence\_of :content
end

Also we need to say that a post has many comments. No migrations involved (for now): simply modify the post model file (app/model/post.rb):

class Post &lt; DataMapper::Base
  property :title, :string
  property :body, :text
  property :created_at, :datetime
 
  has_many :comments
  validates_presence_of :title
  validates_presence_of :body
end

and try to issue again the “automigrate” task:

$ rake dm:db:automigrate

and see directly what happened in data base:

$ echo "show tables" | mysql -u root --database blog_dev -t
+------------+----------+------+-----+---------+----------------+
| Field      | Type     | Null | Key | Default | Extra          |
+------------+----------+------+-----+---------+----------------+
| id         | int(11)  | NO   | PRI | NULL    | auto_increment |
| content    | text     | YES  |     | NULL    |                |
| created_at | datetime | YES  |     | NULL    |                |
| post_id    | int(11)  | YES  |     | NULL    |                |
+------------+----------+------+-----+---------+----------------+

And play directly with the interactive session:

$ merb -i
      irb(main):001:0&gt; Post.all
        =&gt; []

As we can notice each time we issue the rake “dm:db:automigrate” task it erases the database content. That is normal because, actually, DataMapper doesn’t use migrations: it simply destroy and recreate the tables structure.

Let’s continue to play:

      irb(main):002:0&gt; p = Post.new
        =&gt; #
&lt;post @new_record="true," @title="nil," @created_at="nil," @body="nil," @id="nil"&gt;
        irb(main):003:0&gt; p.comments &lt; &lt; Comment.new
        =&gt; [#&lt;comment @new_record="true," @content="nil," @post_id="nil," @created_at="nil," @id="nil"&gt;]
        irb(main):004:0&gt; p.comments.first.content = 'really nice your post!'
        =&gt; "really nice your post!"
        irb(main):005:0&gt; p.valid?
        =&gt; false
        irb(main):006:0&gt; p.title = 'my first post (again)'
        =&gt; "my first post (again)"
        irb(main):007:0&gt; p.body = '....'
        =&gt; "...."
        irb(main):008:0&gt; p.valid?
        =&gt; true
        irb(main):009:0&gt; p.save
        =&gt; true
        irb(main):010:0&gt; Post.count
        =&gt; 1
        irb(main):011:0&gt; Comment.count
        =&gt; 1
        irb(main):012:0&gt;

The only thing to note is DataMapper has (little) different methods from ActiveRecord to fetch things (method all rather than find(:all)), more in this on a future post ;-).

Controllers

Now it’s time to add some “logic” in our application, lets generate a controller:

$ script/generate controller posts
Connecting to database...
      exists  app/controllers
      create  app/controllers/posts.rb
      create  app/views/posts
      create  app/views/posts/index.html.erb
      exists  app/helpers/
      create  app/helpers/posts\_helper.rb
  dependency  merb\_controller\_test
      exists    spec/controllers
      create    spec/controllers/posts\_spec.rb
      create    spec/views/posts
      create    spec/views/posts/index\_html\_spec.rb
      create    spec/helpers
      create    spec/helpers/posts\_helper\_spec.rb

that generated the file app/controllers/posts.rb

class Posts &lt; Application
  def index
    render
  end
end

Despite what rails do in controllers, we have to explicitly call the render method.

Well, we want that our index action shows all post, in termporal reverse order:

class Posts &lt; Application
  def index
    @posts = Post.all(:order =&gt; 'created_at DESC')
    render
  end
end

Views

It’s time to create and fill some views in our application, we find them in app/views/posts/ directory. Let’s modify the stub created in index.html.erb:

&lt;h1&gt;All posts&lt;/h1&gt;
&lt;% for post in @posts %&gt;
&lt;p class="post"&gt;
&lt;h2&gt; &lt;%= post.title %&gt; &lt;/h2&gt;
    &lt;span class="time" &gt; &lt;%= post.created_at %&gt; &lt;/span&gt;
&lt;p class="body"&gt;
    &lt;%= post.body %&gt;
&lt;% end %&gt;
 
&lt;%= link_to 'add new post', "posts/new" %&gt;

If we’d like to spice up the views through CSS the file is public/stylesheets/master.css (but maybe is better to add another one). As you can see merb provides us a layout for the application (app/views/layout/application.html.erb).

Finally it’s time to make our eyes happy admiring what we’ve built, lets start the merb server and point our browser to http://localhost:4000.

merb-tut-post_new.jpg

Managing forms and routes

Ok, now let’s add a methods and a view to insert a new post.

In controller we put:

class Posts &lt; Application
  def index
    @posts = Post.all(:order =&gt; 'created_at DESC')
    render
  end
 
  def new
    @post = Post.new
    render
  end
end

And the fresh new view (app/views/posts/new.html.erb):

&lt;h1&gt;New post&lt;/h1&gt;
  &lt;%= error_messages_for @post %&gt;
 
  &lt;% form_for @post, :action =&gt; url(:post) do %&gt;
    &lt;%= text_control :title, :label =&gt; 'Title' %&gt;
 
    &lt;%= text_area_control :body, :label =&gt; 'Body' %&gt;
 
    &lt;%= submit_button 'save' %&gt;
&lt;% end %&gt;

Now pointing our browser on http://localhost:4000/posts/new we obtain:

merb-tut-helper_error.jpg

Ouch! What happen? Simple answer: we used some helper in recently added view _without_ tell merb to load it before. In fact merb aims to be as thin as possible, so every piece of functionality we need have to be explicitly included. Open and modify the file app/config/dependencies.rb adding the following line:

dependency 'merb_helpers'

The clever reader probably noticed that we used the mehod url(:post) that is supposed to build a url given some model….or _resource_. In fact Merb is restful too, we simply have to declare which resources we want in the file config/router.rb:

puts "Compiling routes.."
Merb::Router.prepare do |r|
  r.resources :posts
  r.default_routes
end

(notice that the order matters).

Now restart the merb server and reload the previous (error) page:

merb-tut-404.jpg

If we try to submit the form, that points to /posts/ action (so post controller), we receive an error because the method create doesn’t exist: it’s the REST tongue!

So add some methods to post controller:

def create
  @post = Post.new(params[:post])
  if @post.save
        # it's a GET request on post resource (=&gt; show action)
    redirect url(:post, @post)
  else
    render :action =&gt;'new'
  end
end
 
def show
  @post = Post.find(params[:id])
  render
end

when we are simply say “after a successful creation, show the content of new post”.

The last piece we need is the “show view” (appt/views/posts/show.html.erb):

&lt;h1&gt;Post&lt;/h1&gt;
&lt;p class="post"&gt;
&lt;h2&gt; &lt;%= @post.title %&gt;&lt;/h2&gt;
  &lt;span class="time"&gt; &lt;%= @post.created_at %&gt; &lt;/span&gt;
&lt;p class="body"&gt;
  &lt;%= @post.body %&gt;
 
&lt;%= link_to 'full post list', url(:posts) %&gt;

and enjoy the result:

merb-tut-post_show.jpg

I leave to readers the joy to finish inserting all other methods to implement the REST verbs (that is: index, show, new, create, edit, update, destroy). But before terminating this introductory article, let me show you how use partial in Merb.

Every partial is stored in a file with leading underscore, app/view/posts/\_comment.html.erb is the partial for comment:

 &lt;span class="time"&gt; &lt;%= comment.created_at %&gt; %lt;/span&gt;
 &lt;%= comment.content %&gt;

and we can use it in other views with:

&lt;%= partial :comment, :with =&gt; @a_comment %&gt;

So, what to say now..? Enjoy with merb! :)

One Response to “Playing around with Merb”

  1. A “sort of” italian version of this article can be found on HMTL.it ruby section: http://ruby.html.it/articoli/l.....-con-merb/

Leave a Reply

You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">

Spam protection by WP Captcha-Free