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 < 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> p = Post.new => # <post @new_record="true," @title="nil," @created_at="nil," @body="nil," @id="nil"%gt; irb(main):003:0> p.comments << Comment.new => [#<comment @new_record="true," @content="nil," @post_id="nil," @created_at="nil," @id="nil">] irb(main):004:0> p.comments.first.content = 'really nice your post!' => "really nice your post!" irb(main):005:0> p.valid? => false irb(main):006:0> p.title = 'my first post (again)' => "my first post (again)" irb(main):007:0> p.body = '....' => "...." irb(main):008:0> p.valid? => true irb(main):009:0> p.save => true irb(main):010:0> Post.count => 1 irb(main):011:0> Comment.count => 1 irb(main):012:0>
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 < 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 < 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 < 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> Post.all => []
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> p = Post.new => # <post @new_record="true," @title="nil," @created_at="nil," @body="nil," @id="nil"> irb(main):003:0> p.comments < < Comment.new => [#<comment @new_record="true," @content="nil," @post_id="nil," @created_at="nil," @id="nil">] irb(main):004:0> p.comments.first.content = 'really nice your post!' => "really nice your post!" irb(main):005:0> p.valid? => false irb(main):006:0> p.title = 'my first post (again)' => "my first post (again)" irb(main):007:0> p.body = '....' => "...." irb(main):008:0> p.valid? => true irb(main):009:0> p.save => true irb(main):010:0> Post.count => 1 irb(main):011:0> Comment.count => 1 irb(main):012:0>
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 < 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 < Application def index @posts = Post.all(:order => '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:
<h1>All posts</h1> <% for post in @posts %> <p class="post"> <h2> <%= post.title %> </h2> <span class="time" > <%= post.created_at %> </span> <p class="body"> <%= post.body %> <% end %> <%= link_to 'add new post', "posts/new" %>
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.
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 < Application def index @posts = Post.all(:order => 'created_at DESC') render end def new @post = Post.new render end end
And the fresh new view (app/views/posts/new.html.erb):
<h1>New post</h1> <%= error_messages_for @post %> <% form_for @post, :action => url(:post) do %> <%= text_control :title, :label => 'Title' %> <%= text_area_control :body, :label => 'Body' %> <%= submit_button 'save' %> <% end %>
Now pointing our browser on http://localhost:4000/posts/new we obtain:
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:
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 (=> show action) redirect url(:post, @post) else render :action =>'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):
<h1>Post</h1> <p class="post"> <h2> <%= @post.title %></h2> <span class="time"> <%= @post.created_at %> </span> <p class="body"> <%= @post.body %> <%= link_to 'full post list', url(:posts) %>
and enjoy the result:
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:
<span class="time"> <%= comment.created_at %> %lt;/span> <%= comment.content %>
and we can use it in other views with:
<%= partial :comment, :with => @a_comment %>
So, what to say now..? Enjoy with merb! :)
07.07.2008 at 12:36
A “sort of” italian version of this article can be found on HMTL.it ruby section: http://ruby.html.it/articoli/l.....-con-merb/