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:
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:
Se ora cerchiamo di inviare il form (che punta a /posts/
) riceviamo un errore poiché il metodo create
ancora non esiste: è il paradigma REST!
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:
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.