23. 10.

Correva l’inverno scorso, più o meno verso questo periodo dell’anno, quando pix mi ha messo una pulce nell’orecchio chiamata Ruby On Rails, e da quel giorno la mia vita da programmatore cambiò radicalmente, in meglio.

Non tanto perché ora la moda impone di usare Ruby per qualsiasi cosa, ma per avermi aperto gli occhi a stili di programmazione differenti, mentalità più aperte e maggiormente legate all’applicazione da sviluppare piuttosto che a tutto quello che ci sta attorno.

Sono arrivato a non dover scrivere neanche una query SQL in un’intera webapp (sto mentendo, in www.jumptheshark.it una piccola query l’ho scritta, ma era a fin di bene :P), a non preoccuparmi di controllare il risultato di una interrogazione al DB, perché tanto c’è un framework che lo fa per me.

Eh si, quì è la vera rivoluzione, l’uso dei frameworks.

Ho già un lavoro che mi porta via gran parte della giornata, e quelle poche ore libere che ho a disposizione le devo far fruttare al massimo; “scrivere un sito” nel tempo libero (23:00 - 02:00) richiede pazienza, e un aiuto non guasta mai.
Ecco perché usare un framework: ti toglie il lavoro noioso, e ti puoi concentrare sulla tua applicazione, dando quindi maggior risalto alla parte “logica”.

Ruby On Rails è solo un esempio: in un anno ho spulciato diversi frameworks anche per PHP, come CakePHP, CodeIgniter, Symfony, Zend Framework; ma ero stanco di PHP, e ho voluto provare Ruby.
Come linguaggio, Ruby, mi ha lasciato piacevolmente colpito. Ogni cosa è un oggetto e ha i suoi metodi, non abbiamo più una “return” alla fine di una funzione, e non siamo obbligati ad usare parentesi per richiamare funzioni; sembrano stupidate, ma queste insieme ad altre mille cose aiutano ad avere un codice leggibile, e non un’accozzaglia di simboli e assegnazioni.
Scrivere codice Rails mi ha anche aiutato a produrre codice PHP migliore. Ora tendo a scrivere codice con eleganza, cercando di racchiudere nel minor numero di righe quello che facevo prima.

Nel caso specifico, www.jumptheshark.it è stato scritto completamente in Ruby On Rails, il DB è un MySQL 5, mentre per la ricerca delle serie TV ho usato Ferret (implementazione in Ruby del blasonato Lucene).
Il codice è stato scritto in circa una settimana dopo il periodo natalizio 2006, e poi archiviato sul mio SVN personale, per essere poi ripreso (dietro pressioni.. :D) quest’estate, dove ogni singolo minuto era buono per tentare di fare un layout decente (quanto odio IE…); fatto il layout, l’ho inglobato nel progetto, e messo live il sito.
Sembra facile, veloce e divertente, e con Ror lo è stato :D

Grazie a Locomotive ho scritto l’intera applicazione sul mio fido MacBook, dalla testa ai piedi.
Partendo dal basilare

rails jumptheshark

ho implementato la gestione degli utenti usando il plugin acts_as_authenticated, e seguendo il wiki apposito per integrare le funzioni di cambio password, verifica e-mail, ecc…
Nulla di difficile, solo un pò di pazienza per leggere il wiki, ed integrarlo con commenti presenti in ogni pagina.

Sono quindi passato a scrivere le migrations necessarie a creare lo schema del database che volevo, e a creare i relativi modelli, connettendoli tra loro coi vari has_many, has_one, belongs_to.

Al momento, lo schema del DB è il seguente:

borromeo@lussuria:~/sites/jts$ cat db/schema.rb
# This file is autogenerated. Instead of editing this file, please use the
# migrations feature of ActiveRecord to incrementally modify your database, and
# then regenerate this schema definition.

ActiveRecord::Schema.define(:version => 42) do

create_table “activities”, :id => false, :force => true do |t|
t.column “user_id”, :integer, :null => false
t.column “show_id”, :integer, :null => false
end

add_index “activities”, ["user_id", "show_id"], :name => “u_s”, :unique => true

create_table “avatars”, :force => true do |t|
t.column “content_type”, :string
t.column “filename”, :string
t.column “size”, :integer
t.column “db_file_id”, :integer
t.column “user_id”, :integer
t.column “thumbnail”, :string
t.column “width”, :integer
t.column “height”, :integer
t.column “updated_at”, :datetime
t.column “parent_id”, :integer
end

create_table “blogcomments”, :force => true do |t|
t.column “blogpost_id”, :integer
t.column “user_id”, :integer
t.column “body”, :text
t.column “created_at”, :datetime
t.column “updated_at”, :datetime
t.column “ip”, :string
end

create_table “blogposts”, :force => true do |t|
t.column “title”, :string
t.column “slug”, :string
t.column “body”, :text
t.column “user_id”, :integer, :default => 1
t.column “created_at”, :datetime
t.column “updated_at”, :datetime
end

create_table “comments”, :force => true do |t|
t.column “user_id”, :integer
t.column “show_id”, :integer
t.column “body”, :text
t.column “created_at”, :datetime
t.column “ip”, :string
end

create_table “db_files”, :force => true do |t|
t.column “data”, :binary
end

create_table “genres”, :force => true do |t|
t.column “name”, :string
t.column “slug”, :string
end

create_table “open_id_authentication_associations”, :force => true do |t|
t.column “server_url”, :binary
t.column “handle”, :string
t.column “secret”, :binary
t.column “issued”, :integer
t.column “lifetime”, :integer
t.column “assoc_type”, :string
end

create_table “open_id_authentication_nonces”, :force => true do |t|
t.column “nonce”, :string
t.column “created”, :integer
end

create_table “open_id_authentication_settings”, :force => true do |t|
t.column “setting”, :string
t.column “value”, :binary
end

create_table “photos”, :force => true do |t|
t.column “content_type”, :string
t.column “filename”, :string
t.column “size”, :integer
t.column “db_file_id”, :integer
t.column “user_id”, :integer
t.column “thumbnail”, :string
t.column “width”, :integer
t.column “height”, :integer
end

create_table “ratings”, :force => true do |t|
t.column “rating”, :integer, :default => 0
t.column “created_at”, :datetime, :null => false
t.column “rateable_type”, :string, :limit => 15, :default => “”, :null => false
t.column “rateable_id”, :integer, :default => 0, :null => false
t.column “user_id”, :integer, :default => 0, :null => false
end

add_index “ratings”, ["user_id"], :name => “fk_ratings_user”

create_table “reasons”, :force => true do |t|
t.column “user_id”, :integer
t.column “show_id”, :integer
t.column “body”, :string
t.column “active”, :integer
t.column “created_at”, :datetime
t.column “updated_at”, :datetime
t.column “points”, :integer, :default => 0, :null => false
t.column “reasontype_id”, :integer
t.column “ip”, :string
end

create_table “reasontypes”, :force => true do |t|
t.column “name”, :string
t.column “slug”, :string
end

add_index “reasontypes”, ["slug"], :name => “r_slug”, :unique => true

create_table “shows”, :force => true do |t|
t.column “name”, :string
t.column “created_at”, :datetime
t.column “updated_at”, :datetime
t.column “genre_id”, :integer
t.column “slug”, :string
t.column “popularity”, :float
end

add_index “shows”, ["slug"], :name => “s_slug”, :unique => true

create_table “skills”, :force => true do |t|
t.column “user_id”, :integer, :null => false
t.column “show_id”, :integer, :null => false
t.column “points”, :float, :default => 0.0
end

add_index “skills”, ["user_id", "show_id"], :name => “u_s_skills”, :unique => true

create_table “taggings”, :force => true do |t|
t.column “tag_id”, :integer
t.column “taggable_id”, :integer
t.column “taggable_type”, :string
t.column “created_at”, :datetime
end

add_index “taggings”, ["tag_id"], :name => “index_taggings_on_tag_id”
add_index “taggings”, ["taggable_id", "taggable_type"], :name => “index_taggings_on_taggable_id_and_taggable_type”

create_table “tags”, :force => true do |t|
t.column “name”, :string
end

create_table “trends”, :force => true do |t|
t.column “show_id”, :integer
t.column “trend”, :float
t.column “created_at”, :datetime
t.column “updated_at”, :datetime
t.column “woy”, :integer
t.column “year”, :integer
end

create_table “users”, :force => true do |t|
t.column “login”, :string
t.column “email”, :string
t.column “crypted_password”, :string, :limit => 40
t.column “salt”, :string, :limit => 40
t.column “created_at”, :datetime
t.column “updated_at”, :datetime
t.column “remember_token”, :string
t.column “remember_token_expires_at”, :datetime
t.column “activation_code”, :string, :limit => 40
t.column “activated_at”, :datetime
t.column “password_reset_code”, :string, :limit => 40
t.column “new_email”, :string
t.column “email_activation_code”, :string, :limit => 40
t.column “identity_url”, :string
t.column “is_mod”, :integer
t.column “signupip”, :string
t.column “lastlogin”, :datetime
t.column “lastloginip”, :string
t.column “points”, :float
t.column “firstname”, :string
t.column “lastname”, :string
t.column “birthday”, :datetime
t.column “about”, :text
t.column “sex”, :integer
t.column “cached_tag_list”, :text
end

create_table “votes”, :force => true do |t|
t.column “reason_id”, :integer
t.column “user_id”, :integer
t.column “value”, :integer
t.column “created_at”, :datetime
t.column “updated_at”, :datetime
end

end

  1. La tabella “activities” tiene un elenco di “attività” svolte da un utente su un determinato “show”; praticamente viene riempita ogni volta che un utente vota o suggerisce qualcosa su una serie TV. Serve al demone che calcola gli utenti “hot” su ogni show, e permette di calcolare lo “score” di ogni utente che abbia realmente avuto attività, tralasciando gli utenti intattivi.
  2. Le tabelle “avatars“, “photos” e “db_files” (quest’ultima inutilizzata) sono necessarie a acts_as_attachment per gestire l’upload di avatar e fotografie
  3. Le tabelle “blogcomments” e “blogposts” sono necessarie per il blog, la loro struttura è banale, e non credo che abbiano bisogno di spiegazioni
  4. La tabella “comments” contiene i commenti lasciati dagli utenti ad uno show
  5. La tabella “genres” contiene l’elenco del tipo di show (drammatico, avventura, ecc…), attualmente non ancora usata :-(
  6. Le tabelle “open_id_authentication_associations“, “open_id_authentication_nonces” e “open_id_authentication_settings” sono usate da open_id_authentication per gestire l’autenticazione centralizzata di OpenID
  7. La tabella “ratings” è usata da acts_as_rateable per gestire il rating di commenti e show
  8. Le tabelle “reasons” e “reasontypes” contengono le motivazioni suggerite dagli utenti, e i loro “tipo” (morte, nascita, uscita di un attore dal cast, ecc…)
  9. La tabella “shows” contiene l’elenco delle serie TV, ed ogni cambiamento a questa tabella è trasparentemente fatto anche nell’indice di ricerca, grazie ad acts_as_ferret
  10. La tabella “skills” contiene il totale dei punti per ogni utente verso ogni show, per sapere quali sono gli utenti più attivi rispetto ogni serie TV
  11. Le tabelle “taggings” e “tags” sono usate da acts_as_taggable_on_steroids, e gestiscono gli “interessi” nel profilo dell’utente
  12. La tabella “trends” viene riempita da un demone apposito, e contiene un punteggio per data indicante il livello di “popolarità” della serie TV.
  13. La tabella “votes” contiene le valutazioni ai suggerimenti degli utenti
  14. La tabella “users” è la tabella usata da acts_as_authenticated per gestire gli accounts

Una volta che lo schema del DB e i relativi modelli erano pronti, ho iniziato a scrivere la logica dell’applicazione, creando i controllers necessari.

Dopo circa una settimana, sono arrivato ad avere il DB pronto, i Modelli correttamente connessi tra loro, ed i controllers in grado di gestire le richieste; mancava solo la visualizzazione, quindi CSS e XHTML alla mano, tanta tanta tanta pazienza, Parallels per fare le prove anche con IE 6/7, ed ecco sfornate le viste necessarie.

Ovviamente, ho usato anche dei plugins, per velocizzare lo sviluppo, e per risolvere elegantemente alcune problematiche ;-)

  1. acts_as_attachment: Per l’upload di avatars e fotografie
  2. acts_as_authenticated: Gestione degli accounts
  3. acts_as_ferret: Gestione dell’indice per la ricerca delle serie TV
  4. acts_as_rateable: Rating “a stelline” delle serie TV e dei commenti
  5. acts_as_taggable_on_steroids: Gestione gli interessi degli utenti, per poter trovare persone con interessi simili ai nostri.
  6. daemon_generator: Necessario per avere il demone che aggiorna gli utenti “hot” per ogni serie TV, e per calcolare le serie TV più “hot” del momento
  7. l10n_simplified: Non vogliamo un sito dedicato all’italia con gli errori scritti in inglese, vero? ;-)
  8. open_id_authentication: Perché registrarsi ad ogni servizio, quando possiamo avere un account unico condiviso?
  9. painless_png: Ok, era troppo tardi quando ho scoperto che IE6 NON supportava i png trasparenti, ma guarda caso, Rails mi ha aiutato anche su questo punto, con un plugin che applica un hack per questo problema.
  10. simple_captcha: Evitiamo che i bot inseriscano dei suggerimenti in automatico…
  11. will_paginate: Necessario per impaginare i risultati di ricerche o listings…

Una volta che l’applicazione era pronta, ho iniziato ad ottimizzarne le performances, usando del fragment caching (praticamente ogni pagina è un’accozzaglia di caches :D), e tentando di semplificare le richieste ad ActiveRecord.

L’applicazione è servita da un cluster di 3 servers mongrel, “proxate” (scusate il neologismo) da nginx (che si preoccupa anche di servire i files statici come immagini, stylesheets e javascripts); abbiamo quindi nginx che serve i files statici e “rigira” le richieste dinamiche al cluster mongrel.

La “messa in produzione” è fatta tramite SVN, che è così gestito:

  1. /trunk/: contiene l’attuale versione in sviluppo
  2. /live/: contiene l’attuale versione presente sul server live
  3. /tags/: contiene le vecchie versioni “live”, per un veloce rollback nel caso un nuovo delivery fallisca

La struttura SVN è presa dal libro “Scalable Internet Architectures“, di Theo Schlossnagle, da cui ho imparato (sto imparando) parecchie tecniche di scalabilità di applicazioni web.

C’è ancora molto lavoro da fare su questa web app, a partire dal costruire la community! Ma per questo non esiste un framework che tenga… solo il tempo dirà se www.jumptheshark.it piacerà, ma comunque andrà è stato un utilissimo esperimento di sviluppo con Ruby On Rails.

Share and Enjoy: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Digg
  • Reddit
  • del.icio.us
  • Furl
  • co.mments
  • YahooMyWeb
  • Technorati
  • DZone
  • Netscape
  • StumbleUpon

Tags: ,

3 Responses to „Having fun with Ruby On Rails: JumpTheShark.it!“

  1. Federico Feroldi Says:

    Wow che mega-post, sono contento che la mia evangelizzazione di Rails abbia avuto successo! ;)
    Hai fatto proprio un bel lavoro e spero presto di vederne altri… ;)

  2. Federico Feroldi’s blog » Blog Archive » links for 2007-10-23 Says:

    [...] marco@borromeo:~$ _ » Blog Archive » Having fun with Ruby On Rails: JumpTheShark.it! Correva l’inverno scorso, più o meno verso questo periodo dell’anno, quando pix mi ha messo una pulce nell’orecchio chiamata Ruby On Rails, e da quel giorno la mia vita da programmatore cambiò radicalmente, in meglio. (tags: ruby article rails rubyonrails evangelize plugins best-practices) [...]

  3. borrOs Says:

    Ci saranno sicuramente nuovi lavori ;-)

    Uno grosso è in cantiere, e sarà orientato proprio a noi “nerds” del codice sorgente!

    Quindi… occhio al feed di questo blog per altre news! :D

Leave a Reply