Saturday, December 27, 2008

Merb blog tutorial

I started using merb a few days ago. After searching through the net, I couldn't find any working blog example for merb 1.0.6.1 (that also uses datamapper) that I'm using. Yes, I know that the new documentation is underway, but I'd rather get this out quickly. (And, yes, I know that merb 2 = rails 3.)

This tutorial just tries to be an shorter updated version of this great tutorial "merb + datamapper + noob: quick start" (from a slightly different viewpoint). It also assumes that you have some knowledge of rails.

First you have to install merb. The easiest way is to just
$ sudo gem install merb

Because we'll use datamapper and sqlite3:
$ sudo gem install datamapper merb_datamapper do_sqlite3

Start by generating merb application:
$ merb-gen app blog

Using this merb-gen app command generates an empty merb application that uses datamapper.

In blog/, you'll find a familiar directory structure. As in rails, configuration files are in config/. The ones that you want to look at are dependencies.rb (that basically lists all the components you're using), init.rb (that specifies your choice of ORM, test framework, and template framework), and database.yml (that, as in rails, provides database config).

Right now, everything merb generates for us looks fine, so we'll stick with it.

Creating the model


It's time to create a model. Issue the command (inside blog/),
$ merb-gen model post

to create model Post. It'll be in app/models/post.rb.

In rails, you'll only have to write migration and model specification (e.g., all the columns) will be automatically read from the database. With DataMapper, we have to specify the model's properties inside its class definition. Out Post model looks like this:

class Post
include DataMapper::Resource

property :id, Serial
property :title, String
property :body, String
end

After that, we have to work with the database to create the table for it. For simple property changes (e.g., adding a new model or adding properties), you don't have to write any migration as merb is clever enough to do that for you: just call
$ rake db:autoupgrade

There's another option for migrating table structures, rake db:automigrate, but this rake task also deletes all your old data (it is destructive), so I prefer db:autoupgrade.

Merb has an interactive console that we can play will models, so let's try to add some post to our blog before moving on.
$ merb -i
irb(main):001:0> p = Post.new :title => 'Hello!', :body => 'This is my first post'
=> #<Post id=nil title="Hello!" body="This is my first post">
irb(main):002:0> p.save
~ INSERT INTO "posts" ("title", "body") VALUES ('Hello!', 'This is my first post')
=> true
irb(main):003:0> p2 = Post.create :title => 'Second post', :body => 'what??'
~ INSERT INTO "posts" ("title", "body") VALUES ('Second post', 'what??')
=> #<Post id=2 title="Second post" body="what??">


Creating the controller


Okay, we have something to display now. Let's create a controller. Call
$ merb-gen controller posts

This command generates app/controllers/posts.rb. Note that there's no Controller suffix to the name. Let's add something to method index.

class Posts < Application
def index
@posts = Post.all
render
end
end

In merb, you have to call render explicitly. This would render a default view for this action. Another point to note is that instead of using Post.find(:all) with Active Record, in Datamapper you only have to call Post.all.

Let's create a view for this action. We can edit views/posts/index.html.erb like this:
<h1>My posts</h1>
<% for post in @posts %>
<h2><%= post.title %></h2>
<p><%= post.body %></p>
<% end %>

It's time to see the posts! Run
$ merb

then point your browser to http://localhost:4000/posts or http://127.0.0.1:4000/posts.

New posts


Let's create a new post! We'll add a link "add new post" at the bottom of the index page.
<%= link_to 'add new post', url(:controller => :posts, :action => :new) %>

Method url, that returns a url for an action, can be really fancy, but for now we go with a simple rails-like version.

Then create methods new in Posts controller that render the view.

class Posts < Application
# ...
def new
@post = Post.new
render
end
end

Also, the view new.html.erb in app/views/posts:
<h1>Creating new post</h1>
<%= form_for @post, :action => 'create' do %>
<%= text_field :title, :label => 'Title' %><br/>
<%= text_area :body, :label => 'Body' %><br/>
<%= submit 'New post' %>
<% end =%>

Note that we use form_for and methods text_field and text_area know that it is inside this form automatically. (In rails you'd need the block parameter.)

Finally, we add method create to Posts controller:

class Posts < Application
# ...
def create
@post = Post.new params[:post]
@post.save
redirect url(:action => 'index')
end
end

Try it to see if you can create new posts.

Comments


What kind of blogs without comments?

So we'll create the comment model. Call
$ merb-gen model comment

and edit app/models/comment.rb:

class Comment
include DataMapper::Resource

property :id, Serial
property :body, String
end


We have to tell a Post that it has many comments. We'll do this by adding something like has_many in Active Record to the model:

class Post
# ...
has n, :comments
end

This is how you specify one-to-many association in Datamapper. If you want one, just use has 1, :comment.

Now let's turn to view and controller. Edit app/views/posts/index.html.erb by adding comment listing and new comment form.
<h1>My posts</h1>
<% for post in @posts %>
<h2><%= post.title %></h2>
<p><%= post.body %></p>
<ul>
<% post.comments.each do |comment| %>
<li><%= comment.body %></li>
<% end %>
<li>
<%= form_for Comment.new, :action => url(:controller => 'posts',
:action => 'create_comment',
:id => post.id) do %>
<%= text_field :body %>
<%= submit 'New comment' %>
<% end =%>
</li>
</ul>
<% end %>

<%= link_to 'add new post', url(:controller => :posts, :action => :new) %>

I believe that with merb router, we can write a better the action url. I'll update this entry after I find out how. Also, note that to get the list display correctly, I have to delete the merb stylesheet link line in app/views/layout/application.html.erb because it hides my list items.

To handle this action, we create method create_comment in Posts controller.

class Posts < Application
# ...
def create_comment
post = Post[params[:id]]
comment = Comment.new params[:comment]
post.comments << comment
post.save
redirect url(:action => 'index')
end
end

Note the way we find a post by id (Post[params[:id]]).

Hope this blog post help getting you started with merb and Datamapper. Any comments are welcome!

ฉบับภาษาไทย