2007-12-27 21:53

tutorial, haml, merb, datamapper

merb + datamapper + noob: quick start

Author: Kacper Cieśla (comboy)

Important note: I hope to help somebody setting up a simple merb application, but I’m new to merb too. So I’m sorry if show you something that can be done in some better way. Comments are very appreciated.

Merb is a quite new framework, created by Ezra Zygmuntowicz. In many ways it’s similar to ruby on rails, but here core is very small and you can obtain more functionality (if you want to), by installing plugins (as gems). That (and few different things about which you can read here), makes it faster than rails.

I assume your basic ruby on rails knowledge for this tutorial.

Basic setup

Ok, so first we need merb. You can just:

# gem install merb
But I suggest using the edge version:
# gem install mongrel json json_pure erubis mime-types rspec hpricot mocha rubigen haml markaby mailfactory Ruby2Ruby -y
# svn co http://svn.devjavu.com/merb/trunk merb
# cd merb
# rake install

That’s it. You have merb.

Let’s generate some project:

$ merb almost_blog
      create  
      create  app/controllers
      ...

You may want to take a look at the app skeleton generated in amost_blog directory. It’s pretty similar to this generated by rails.

But..

Merb is (like Ezra say) “ORM agnostic”. So it’s time to make a choice. You can choose between activerecord (one that rails uses), sequel and datamapper. My subjective choice is datamapper.

datamapper

First we need to get it. The easiest way:

$ gem install datamapper merb_datamapper

However I had some problems until I installed it from trunk:

$ svn co http://datamapper.rubyforge.org/svn/trunk/ data_mapper

And for datamapper you need drivers for your database choose a gem to install:

  • do_mysql
  • do_sqlite3
  • do_postgres

I’ve chosen bad old ugly MySQL that I got used to (gem install do_mysql)

It’s time to tell your merb app about your choice. In config/dependencies.rb uncomment the following line:

use_orm :datamapper

save file, and in main app directory (amost_blog in this example) hit:

$ rake
Because now merb know that you use datamapper you will see something like:
No database.yml file found in /lapciak/project/tmp/almost_blog/config.
A sample file was created called database.sample.yml for you to copy and edit.

You know what to do. My version of config/database.yml looks like this:

---
# This is a sample database file for the DataMapper ORM
:development: &defaults
  :adapter: mysql
  :database: almost_blog_dev
  :username: looser
  :password: nopass
  :host: localhost

:test:
  <<: *defaults
  :database: sample_test

:production:
  <<: *defaults
  :database: sample_production

And our basic setup is done.

Models

We’ll try some blog-like app

$  script/generate model note
Started merb_init.rb ...
Connecting to database...
Thu, 27 Dec 2007 21:01:33 GMT: loading gem 'merb_datamapper' from config/dependencies.rb:13 ...
Loading Application...
Compiling routes..
Loaded DEVELOPMENT Environment...
      exists  app/models
      create  app/models/note.rb
  dependency  merb_model_test
      exists    spec/models
      create    spec/models/note_spec.rb

As you can guess, knowing that you use datamapper, merb generated appropriate model files.

When you’re sill in console type:
$  script/generate model comment

Now using your favourite editor (wow, you’re using netbeans too?!) open app/models/note.rb

And here, some basic datamapper knowledge is needed. But I’ll give you some finished model file, and you probably won’t have a problem understaing it.

class Note < DataMapper::Base
  property :title, :string
  property :body, :text
  property :created_at, :datetime

  has_many :comments
  validates_presence_of :title
end

As you can see fields are defined in the model file, and there are rails-like helper methods to create associations and validations. Also, created_at is a magic field that is autoupdated to current time on record creation. You probably can’t wait to see it working so let’s type in console:

$ rake dm:db:automigrate

And take a look how your database schema looks like now.

Let’s finally talk with merb

$ merb -i

As you no doubt have guess it’s something similar to script/console in rails Sample chat session:

$ merb -i
Started merb_init.rb ...
Connecting to database...
Thu, 27 Dec 2007 21:19:57 GMT: loading gem 'merb_datamapper' from config/dependencies.rb:13 ...
Loading Application...
Compiling routes..
Loaded DEVELOPMENT Environment...
Not Using Sessions

irb(main):001:0> Note.find(:all)
=> []
irb(main):002:0> n = Note.new
=> #<Note:0x..fb6bd77f0 @new_record=true, @created_at=nil, @body=nil, @title=nil, @id=nil>
irb(main):003:0> n.body = 'blah blah'
=> "blah blah"
irb(main):004:0> n.save
=> false
irb(main):005:0> n.errors
=> #<Validatable::Errors:0xb6bcc8b4 @errors={:title=>["Title must not be blank"]}>
irb(main):006:0> n.title = 'wasssssup'
=> "wasssssup"
irb(main):007:0> n.save
=> true
irb(main):008:0> Note.find(:all)
=> [#<Note:0x..fb6b9f3a0 @new_record=false, @created_at=#<DateTime: 212065554103/86400,0,2299161>, @body="blah blah", @title="wasssssup", @id=1>]
irb(main):009:0>

Done with talking, time for a comment model now (app/models/comment.rb):

class Comment < DataMapper::Base
  property :name, :string
  property :body, :text
  property :created_at, :datetime

  belongs_to :note

  validates_presence_of :name, :body
end

and automigration:

dm:db:automigrate

and.. oooopss – If you had any notes saved, they are all gone. That’s the one painful thing about datamapper. It’s not able to ALTER your tables. Instead, it recreates them every time you do automigrate. So you better modify your tables manually in the production environment.

Anyway, you have now schema for your models in your database.

controllers

Generation:

script/generate controller notes

And just by the template you can tell the first difference:

class Notes < Application  
  def index
    render
  end
end

Yes, you have to call render manually. Whatever you return from controller method goes to the browser output.

It may be a good moment now to finally see our application in action. To run the server you just type:

$ merb

in the main application directory. It will start on port 4000, so you can check the http://localhost:4000/notes/ to see auto generated index template for notes. If you are a good observer, you will notice that in the page source there is something more than you can find in views/notes/index.html.erb. There is already a default application layout in views/layouts/applicaiton.html.erb

We’ll try to list some notes first (you can create them manually using merb console “merb -i”). Controller part is simple:

class Notes < Application  
  def index
    @notes = Note.find(:all)    
    render
  end
end

but let’s take a look at

view

You’ve already seen the template, It’s a simple erb (note that merb uses erubis for .erb templates by default). Example template for index would look like this:

<h1>Blah blah notes</h1>

<table cellspacing="10" style="border: 1px solid #555">
<% for note in @notes %>
  <tr>
  <td><%= note.title %></td>    
  <td><%= link_to 'show', "notes/#{note.id}"%>
  </td>
  <td><%= link_to 'edit', "notes/#{note.id}/edit"%></td>
</tr>
<% end %>
</table>

Notice that second parameter for link_to method is just an url string. It seems that’s the way it works here, but there is a workaround for it that I’ll show later.

So it works. But I do like HAML.

It’s a little slower, but it looks so much better. To get haml working with your merb application first you need to install it:

# gem install haml

Then tell your merb app that you like haml. In config/dependencies.rb add:

dependency 'haml'

And now you can delete index.html.erb and put so much nicer index.html.haml indstead:

%h1 Blah blah notes

%table{:cellspacing => 10, :style => 'border: 1px solid #555'}
  - for note in @notes
    %tr
      %td= note.title
      %td= link_to 'show', "notes/#{note.id}"
      %td= link_to 'edit', "notes/#{note.id}/edit"

(Don’t forget to restart the server to let merb load dependencies).

Now about this link_to. If you don’t want to put urls manually, well, merb is already pretty RESTful, first go to config/router.rb, you will need something like this:

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

Routes in merb are compiled, and this puts here teached me some useful thing: when you’re in development, routes are being recompiled every time you change something in router.rb. Nice.

Time to use our router now, after modification you’ve just made, you can change your links to:

%td= link_to 'show', url(:note,note)
      %td= link_to 'edit', url(:edit_note,note)

Looks much better. Time to make a page for adding a new note. You may want to modify your application layout to add some links on the top:

<%= link_to 'list', url(:notes) %> |
    <%= link_to 'add new note', url(:new_note) %>     
    <hr />

(yeah, I was too lazy to rewrite it to haml), and now we create a view for new note. There we’ll be some magic there – this is my finished view:

%h1 Adding a new note

= error_messages_for @note

- form_for(@note, :action => url(:note)) do 
  = text_control :title, :label => 'Title'
  %br
  = text_area_control :body, :label => 'Body'
  %br
  = submit_button 'Save'

But it won’t work for you. Even if you use form_tag instead of form_for. As I already mentioned, Ezra wants to keep merb core as small as possible. For form helpers, you need a plugin:

# gem install merb_helpers

and new line in your config/dependencies.rb:

dependency 'merb_helpers'

(restart) And the controller part for this view is:

def new
    @note = Note.new
    render 
  end

(yeah, I know).

Now let me try explain (myself): there is a form_tag and there is a text_field in helpers, but form_for seems to be more elegant. Also, _field methods seems to be adequate to _field_tag methods from rails. So they’re good when you need to get a single value from user, but they’re not so elegant when creating a form for an object that’s defined as a model.

form_for created a block, and inside this block all _control methods add fields inside an object that this form is for Now, I’ve tried something like:

- form_for(:note, :action => url(:note)) do

(just like in documentation) but it does not work. It works when I provide an empty object as a first argument. That’s why I’m creating it in the controller.

(Uh… The worst part over, now let’s just finish this almost_blog)

If you try to add some note now you’ll get an error about “create” method not found.

So in controllers/notes.rb:

def create
    @note = Note.new(params[:note])
    if @note.save
      redirect url(:note,@note)
    else
      render :action =>'new'
    end
  end

  def show
    @note = Note.find(params[:id])
    render
  end

We’ll need a show method too, so here you got some example view for it:

%h1= @note.title
%p= @note.body

And now adding new note should work. Check what happens if you try to add note without a title. To make errors look better you would need to add some css, notice that all fields with errors have element class set to “error”

Editing note is very simple, but we don’t want to duplicate form we created, so let’s try like this:

  1. rename views/notes/new.html.haml to views/notes/form.html.haml
  2. in the view, change the first line to:

    %h1= @heading
    

  3. and make controller actions look like this:

    def new
        @header = "Adding new note"
        @note = Note.new
        render :template => 'notes/form'
      end
    
      def edit
        @header = 'Editing existing note'
        @note = Note.find(params[:id])
        render :template => 'notes/form'
      end
    
      def update
        @note = Note.find(params[:id])    
        if @note.update_attributes(params[:note])
          redirect url(:note,@note)
        else
          render :template => 'notes/note_form'
        end
      end
    

And note editing is done :)

Last part is being able to comment the note. Let’s just implement it without too much talking:

views/notes/show.html.haml:

%h1= @note.title
%p= @note.body

%b Comments:
%br
%br

- if @note.comments.size == 0
  %i No comments yet
- for comment in @note.comments  
  = partial :comment, :with => comment

%br
%br

%b Leave a comment
%br
= error_messages_for @comment if @comment

- form_for((@comment || Comment.new), :action => "add_comment") do
  = hidden_field :name => :note_id, :value => @note.id
  = text_control :name, :label => 'Your name:'
  %br
  = text_area_control :body, :label => 'Comment:'
  %br
  = submit_button 'Add comment'

Because of my solution for new/edit form you might think there are no partials in merb so here is an example of their usage.

views/notes/_comment.html.haml

%b= comment.name
%p= comment.body
%hr

And the controller part:

def add_comment
    @note = Note.find(params[:note_id])
    @comment = Comment.new(params[:comment].merge(:note => @note))
    if @comment.save      
      redirect url(:note,@note)
    else
      render :action => 'show', :id => @note.id
    end
  end

And we’re done. It’s still lacking some basic features like deleting comments and notes, but it’s just an example (and it works)

Complete project source for lazy people ;)

Summary

First of all merb is lacking a good documentation. For example to check how to pass an object to partial, I had to check the source. In fact, most of the things must be checked in the source. That makes it a little bit hard to learn, and that’s why I wrote this crap.

Merb architecture seems to be well thought. There is less magic, that’s why on the beginning you may wonder why form_tag does not work. It may be a good choice if you care about speed, don’t need some fireworks like RJS and lots of helpers. It seems to be perfect for writing a webservice. Well, I think we’ll see in about year how it’s developing and how it’s getting better (and I think it’s not only about what Ezra will do, but mainly about what community will do)

As always, I have to sorry for my enligsh and all mistakes I’ve made. And once again, you’re comment are extremely appreciated.


rate this page:

6.18 (31 votes )
Comments:
Justin Jones (2008-01-02 20:03)

Thanks for providing to this intro to those who need it.

= partial :comment, :with => comment

can be replaced with: = partial :comment, :with => @note.comments

The :with key takes either single objects or a collection of objects. You can also pass :as => ‘something’ if you wish for the local var in the partial to be called something different from the partial name.

The latter is also considerably faster, fyi.

ie. in _comment partial, the default local var for passed in objects is ‘comment’, using :as changes it to whatever you specify.

The lack of documentation (at least regarding this feature) is because it is relatively new. I contributed it a month or so ago, and neglected documentation. Sorry :(



Kacper Cieśla (comboy) (2008-01-02 23:47)

Thanks Justin, I’ll leave the text as it is, but it’s good to know such thing.

Also, thanks for you contribution. Code is much more important than doc :)



md (2008-01-26 22:30)

great article. i probably referred back to it a thousand times when setting merb up.



David Parker (2008-01-28 19:52)

Nice article. I’m just getting started with Merb and giving DataMapper a swing and this has been helpful.



jack dempsey (2008-02-07 18:52)

Thanks for the article. Question question for you regarding this line:

%td= link_to 'edit', url(:edit_note,note)

The url I get generated in my own usage looks like this: http://localhost:4000/questions/#%3CQuestion:0×8a17624%3E/edit When I change it to use question.id i get http://localhost:4000/questions/1/edit

It also works if I explicitly define to_param on my model:

class Question < DataMapper::Base property :title, :string property :type, :string end

def to_param
  id
end

So, any thoughts on this? Are you actually seeing it work without the to_param or .id addition?

thanks!



Mathieu Martin (2008-03-04 04:31)

Thanks for this tutorial :-) I’d like to point out that right now (it might have changed recently, not sure) to generate a merb app, instead of ‘merb almost_blog’ you should use ‘merb-gen almost_blog’



Mathieu Martin (2008-03-04 05:51)

Here are other finds for 0.9 as of March 3rd

use_orm :datamapper is now in config/init.rb

script/generate model note is now merb-gen generate model Note

rake dm:db:automigrate is now rake dm:auto_migrate (but it doesn’t seem to work for me right now. I’m stopping for the night :)



cj (2008-05-18 00:21)

you can use rake -T to display available rake tasks





Leave a comment


CodingBitch.com
ver. 0.1.93 alpha (bugtrack, feature requests)



Author of this page about himself::

Student AGH (4 rok), autor programuj.com oraz skryptu do tego community. Po jakichś 6 latach PHP przerzuciłem się na Ruby i Railsy. Obecnie zajmuje się głównie frameworlami www, wcześniej pomacałem...


Stats (last 14 days):
not generated yet>

Kto linka podrzucał (alpha test shit escape):

Share it:
    del.icio.us Wykop

LOG IN

login:

pass:

remember me




Who's online:

Looks like nobody's here at the moment:/

Lang_pl

  © community.programuj.com - skrypt, pomysł, wykonanie i sprzątanie: Kacper Cieśla (comboy). Niektóre prawa zastrzeżone.
powered by Ruby on Rails, hosted.pl and coffee