Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Non solo Rails: costruire un blog con Merb

Un'introduzione dal taglio molto pratico al framework Merb, che rappresenta una valida alternativa a Rails
Un'introduzione dal taglio molto pratico al framework Merb, che rappresenta una valida alternativa a Rails
Link copiato negli appunti

Molti sviluppatori hanno avuto modo di apprezzare Rails e le features che introduce per migliorare l'esperienza di chi deve sviluppare un'applicazione Web. Tuttavia esistono valide alternative, che vale la pena conoscere. Questo articolo è rivolto principalmente agli sviluppatori che abbiamo già una conoscenza di Rails, seppur sommaria.

Merb è un progetto di Ezra Zygmuntowicz di EngineYard. È nato principalmente con l'obbiettivo di essere un framework "leggero": niente estensioni forzate, poche "magie sintattiche", ma con il vantaggio di una notevole velocità di esecuzione.

Una delle prime cose che si apprezza approcciando Rails è senza dubbio l'uso di ActiveRecord per la gestione dei modelli (o della relativa persistenza), ma esistono anche altri Object-Relational Mapper (ORM). DataMapper è uno di questi. Benché Merb non imponga l'uso di un ORM specifico (può infatti utilizzare benissimo anche ActiveRecord, o non utilizzare affatto un ORM) in questo articolo presenteremo l'uso di DataMapper.

Un'altra peculiarità di Merb è l'uso di RSpec per il testing (o meglio "comportamento") dell'applicazione che si va a costruire, nonostante il classico Test::Unit sia ancora presente.

Installazione

Essendo un progetto ancora giovane non potevamo aspettarci una installazione "semplicissima", abbiamo riscontrato qualche problemino, ma nulla di insormontabile. Anzitutto occorre installare le gem.

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

Fatto ciò passiamo allo strato ORM. Come detto precedentemente vogliamo utilizzare DataMapper.

$ sudo gem install datamapper merb_datamapper merb_helpers

Ora abbiamo bisogno dei driver specifici: do_mysql, do_sqlite3, do_postgres. Scegliamo di utilizzare MySQL.

$ sudo gem install do_mysql

Qui si presenta un piccolo problema: sistemiamolo! Bisogna entrare nella directory della gem do_mysql (nel nostro caso è /usr/local/lib/ruby/gems/1.8/gems/do_mysql-0.2.3/), aprire il file ext/mysql_c.i e madificare la linea #41 affinché includa il corretto header file.

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

Quindi editare il file ext/mysql_c.c e commentare la linea #16079:

/*  rb_define_const(mMysql_c, "MYSQL_OPT_SSL_VERIFY_SERVER_CERT", SWIG_From_int((int)(MYSQL_OPT_SSL_VERIFY_SERVER_CERT))); */

Adesso possiamo ricompilare ed installare la gemma:

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

Setup dell'ambiente

Dopo aver installato le gemme, creiamo una nuova applicazione (con poca fantasia: un blog!):

$ merb blog

Come è possibile osservare Merb crea la struttura delle directory per la nuova applicazione. Tale struttura sarà molto familiare a chi ha utilizzato Rails almeno una volta.

A questo punto abbiamo bisogno di "qualche" database: uno per lo sviluppo, uno per il testing ed infine uno per l'ambiente in produzione.

$ echo "create database blog_dev" | mysql -u root
$ echo "create database blog_test" | mysql -u root
$ echo "create database blog_prod" | mysql -u root

Fatto ciò andiamo a configurare Merb. Nel file config/dependencies.rb (intorno alla linea 13) decommentiamo la seguente linea per abilitare l'uso di DataMapper:

use_orm :datamapper

È utile notare l'assenza di un file di configurazione per l'accesso ai database, a differenza di quanto avviene in Rails. Infatti tale file ancora non esiste. Creiamolo dando il comando:

$ 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.

Dopo aver abilitato l'uso di un ORM, Merb nota l'assenza dell'opportuno file di configurazione, e lo crea per noi uno stub in config/database.sample.yaml. Rinominiamolo in config/database.yml e modifichiamolo inserendo gli opportuni valori.

	# 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

Ora dando nuovamente il comando rake -T otteniamo la 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

Ora siamo pronti per iniziare a costruire il nostro blog, cosa che faremo nella seconda parte dell'aricolo

Nella prima parte dell'articolo abbiamo installato Merb e ci siamo occupati delle configurazione iniziale del blog. In questa parte lavoreremo sulla costruzione di modelli, controller e viste.

Creazione dei modelli

Ogni blog ha dei post, pertanto abbiamo bisogno di un modello per l'entità "post". Come in Rails, anche in Merb abbiamo di generatori di codice:

$ 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

Senza grandi sorprese è stato generato il file app/models/post.rb:

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

La prima "sorpresa" è che DataMapper descrive il modello facendo uso di un unico file, ovvero non vengono create migration. Questo approccio è da apprezzare, in quanto ci evita di dover saltare continuamente dal modello alle migration per ricordare i vari attributi, riferimenti, constraint sull'entità in questione. È un approccio veramente DRY.

Abbiamo definito le proprietà dell'entità "Post" all'interno della classe Ruby, adesso dobbiamo fare in modo che Merb generi la corrispondente tabella nel database:

$ 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    |                |
+------------+-------------+------+-----+---------+----------------+

Notiamo subito che DataMapper ha aggiunto una chiave primaria ed ha assegnato opportunamente il nome alla tabella (il plurale del nome della classe: posts).

Ogni post avrà una serie di commenti, pertanto abbiamo bisogno di un'altra entità.

$ 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

Viene generato il file app/model/comment.rb:

class Comment < DataMapper::Base
end

Non avendo indicato nessun attributo il modello è completamente vuoto. Spetta a noi popolarlo opportunamente:

class Comment < DataMapper::Base
  property :content, :text
  property :created_at, :datetime

  belongs_to :post
  validates_presence_of :content
end

Dobbiamo anche indicare che un post può avere più commenti:

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

  has_many :comments
  validat s_presence_of :title
  validates_presence_of :body
end

...e invocare nuovamente il task "automigrate":

$ rake dm:db:automigrate

Vediamo cosa è accaduto nel DB:

$ 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    |                |
+------------+----------+------+-----+---------+----------------+

È stata aggiunta l'apposita foreign key (post_id). Bisogna porre attenzione, inoltre, al fatto che il task "dm:db:automigrate" elimina le tabelle e le ricostruiscce, quindi ogni dato presente nel database viene perso (e in un ambiente di sviluppo questo è del tutto ragionevole...). Prima di andare avanti vediamo, in una console interattiva, come trattare gli oggetti DataMapper.

$ merb -i
irb(main):001:0> Post.all
=> []
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 post!’
=> "really nice post!"
irb(main):005:0> p.valid?
=> false
irb(main):006:0> p.title = ‘My First Post’
=> "My First Post"
irb(main):007:0> p.body = ‘bla bla bla’
=> "bla bla bla"
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>

I controllers

È giunto il momento di aggiungere della "logica" alla nostra applicazione, generiamo il controller posts:

$ 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

È stato generato il file app/controllers/posts.rb:

class Posts < Application
  def index
    render
  end
end

A differenza di quanto venga fatto in Rails, in Merb all'interno dei controller dobbiamo esplicitamente invocare il metodo render.

Adesso vogliamo che la nostra index mostri tutti i post, dal più recente al meno recente:

class Posts < Application
  def index
    @posts = Post.all(:order => 'created_at DESC')
    render
  end
end

Le viste

Abbiamo fornito la nostra applicazione di modelli e controller, adesso dobbiamo scrivere le "viste" che permettono il collegamento tra i controller e gli utenti.Le viste relative al controller posts si trovano nella directory app/views/posts/. Modifichiamo lo stub generato in index.html.erb:

<h1>All posts</h1>

<% for post in @posts %>
  <h2><%= post.title %></h2>
  <span class="time" ><%= post.created_at %></span>
  <div class="body">
    <%= post.body %>
  </div>
<% end %>

<%= link_to 'add new post', "posts/new" %>

Volendo "abbellire" la nostra vista, possiamo utilizzare un CSS come quello che si trova in public/stylesheets/master.css. Come possiamo vedere, Merb (analogamente a Rails) propone un layout a livello di applicazione (app/views/layout/application.html.erb).

Gestire i form e il routing

Scriviamo metodi e viste affinché sia possibile inserire nuovi post all'interno del nostro blog. Nel controller abbiamo:

class Posts < Application
  def index
    @posts = Post.all(:order => 'created_at DESC')
    render
  end

  def new
    @post = Post.new
    render
  end
end

E una nuova vista in (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 %>

Puntando il nostro browser su http://localhost:4000/posts/new otteniamo:

merb-tut-helper_error.jpg

Che succede?! Semplice: abbiamo utilizzato alcuni helper nella precedente view, senza indicare a Merb di caricarli (ricordate: in Merb non vi è nulla di superfluo, tutto quello che occorre va dichiarato anticipatamente).

Apriamo il file app/config/dependencies.rb e aggiugiamo la seguente linea:

dependency 'merb_helpers'

Il lettore più attento avrà notato che all'interno della precedente vista abbiamo utilizzato il metodo url(:post), che (intuitivamente) dovrebbe costruire un url a partire da un modello... ovvero una "risorsa".

In effetti Merb è RESTful. Dobbiamo semplicemente dichiarare quali risorse vogliamo come tali all'interno del file config/router.rb (notiamo che l'ordine è importante):

puts "Compiling routes.."

Merb::Router.prepare do |r|
  r.resources :posts
  r.default_routes
end

Facciamo ripartire l'istanza del server merb e carichiamo nuovamente la precedente pagina nel browser:

merb-tut-post_new.jpg

Se ora cerchiamo di inviare il form (che punta a /posts/) riceviamo un errore poiché il metodo create ancora non esiste: è il paradigma REST!

merb-tut-404.jpg

Aggiungiamo tale metodo al controller dei post:

  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

Abbiamo semplicemente indicato di mostrare il contenuto del nuovo post dopo la sua creazione. L'ultima parte mancante consiste nel fornire una view per il metodo "show" (nel file app/views/posts/show.html.erb):

<h1>Post</h1>

<div class="post">
  <h2><%= @post.title %></h2>
  <span class="time"><%= @post.created_at %></span>
  
  <div class="body">
    <%= @post.body %>
  </div>
  
  <%= link_to 'full post list', url(:posts) %>
</div>

Vediamo il risultato nel browser:

merb-tut-post_show.jpg

Lasciamo a ciascuno l'onere (e la gioia...) di terminare l'applicazione di esempio scrivendo gli altri metodi, e relative viste, per implementare il paradigma REST sui post (ovvero: index, show, new, create, edit, update, destroy).

Prima di terminare con questo articolo puramente introduttivo, vediamo come sia possibile in Merb (al pari di Rails) utilizzare i partials.

Ogni partial risiede in un proprio file, il cui nome inizia con un underscore ("_"), ad esempio app/view/posts/_comment.html.erb è il partial per i commenti:

<span class="time"> <%= comment.created_at %> %lt;/span>
<%= comment.content %>

e possiamo usarlo nelle view semplicemente con:

<%= partial :comment, :with => @a_comment %>

Conclusioni

Questo articolo, dal taglio volutamente introduttivo, ha avuto lo scopo di stimolare la curiosità verso framework "alternativi". Rails è, e rimane, senza dubbio un ottimo framework, ma è importante sapere che esistono delle alternative e valutarne i pro ed i contro. C'è da considerare infine, che Merb nasce dall'esigenza di avere un framework per ruby che sia realmente scalabile e che possa offrire delle performance adeguate.


Ti consigliamo anche