sviluppo web con ruby on rails - tanasi · vede persone e fa cose ... ruby on rails - fu? “ruby...
TRANSCRIPT
Chi vi parlaChi vi parla● Alessandro `jekil` Tanasi
● Vede persone e fa cose
● Non sviluppa siti web ma ha delle web-esigenze da soddisfare (chi non le ha?)
● Senza perdere tempo e nel modo più efficace possibile
Ruby on Rails - fu?Ruby on Rails - fu?
“Ruby on Rails is astounding. Using it is like watching a kung-fu movie,where a dozen bad-ass frameworks prepare to beat up the little newcomer only to be handed their asses in a variety of imaginative ways.”(Nathan Torkington, O'Reilly Program Chair for OSCON)
Ruby on RailsRuby on RailsRuby on Rails è un framework per lo sviluppo
di applicativi web
● Basato su Ruby
● Open source
● Pensato per massimizzare la produttività
● Progettato pensando alla felicità del programmatore
● http://rubyonrails.org/screencasts
La forza di RailsLa forza di Rails● Ruby
● Convention over configuration
● Best practices: MVC, DRY, Testing
● Astrazione (SQL, Javascript, ..)
● Integrazione con AJAX e REST
● Metodologie “agile”
InstallazioneInstallazione● Interprete Ruby
– apt-get install ruby– Compilazione dei sorgenti– IstantRails su Windows– Locomotive su OSX
● Ruby on Rails
● DBMS
– SQLite, MySQL, Postgres e altri● Editor di testo
– TextMate, jEdit, Scite, NetBeans, Aptana, vim, emacs
Struttura directoryStruttura directoryapp controllers helpers models views layoutsconfig environment.rb routes.rbdb database.yml migrationsliblogpublicscripttestvendor plugins rails
MigrationsMigrations● Evoluzione del database schema nel tempo
● Definite indipendentemente dal DBMS sottostante
● script/generate migration
● Ogni migrazione è numerata e applicata sequenzialmente
● La migrazione può essere reversibile
● I dati evolvono con la migrazione
● rake db:migrate VERSION=X
EsempioEsempioclass CreateUsers < ActiveRecord::Migration def self.up create_table "users", :force => true do |t| t.string :login, :null => false t.string :email, :null => false t.string :salt, :null => false, :limit => 40 t.string :remember_token t.datetime :remember_token_expires_at t.string :password_reset_code, :limit => 40 t.timestamps end add_index :users, :login add_index :users, :enabled end
def self.down drop_table "users" endend
RakeRakeUtility per lo svolgimento di task
● db:migrate
● db:sessions:create
● doc:app
● doc:rails
● log:clear
● rails:freeze:gems
● rails:freeze:edge
● rails:update
● :test (default task)
● :stats
ScaffoldScaffold● Creazione automatizzata dell'interfaccia
web di un dato modello
● Utile per strumenti che devono essere usabili da subito e con il minimo sforzo
● Possibilità di personalizzazioni
● Rapidità di sviluppo
● ruby script/generate scaffold antani
ActiveRecordActiveRecord● Ogni tabella del database è mappata con
una classe
● I nomi delle tabelle sono al plurale mentre le classi dei modelli sono al singolare
● Ogni tabella ha un campo primary key chiamato id
● La mappatura permette di eseguire operazioni trattando i dati come oggetti
● Permette di definire costrutti e condizioni sui dati
FindersFinders● User.find(:all)
● User.find(:all, :limit => 10)
● Dynamic finders
● User.find_all_by_last_name “Hanson”
● User.find_by_age “20”
● User.find_by_first_name_and_last_name “Andreas”, “Kviby”
AssociazioniAssociazionihas_one :credit_card, :dependent => :destroy
has_many :comments, :include => :authorhas_many :people, :class_name => "Person", :conditions => "deleted = 0", :order => "name"has_many :tracks, :order => "position", :dependent => :destroyhas_many :comments, :dependent => :nullifyhas_many :tags, :as => :taggablehas_many :subscribers, :through => :subscriptions, :source => :userhas_many :subscribers, :class_name => "Person", :finder_sql => 'SELECT DISTINCT people.* ' + 'FROM people p, post_subscriptions ps ' + 'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' + 'ORDER BY p.first_name'
ValidazioniValidazioni● Regole di validazione del modello che
proteggono l'integrità dei dati
● La validazione viene eseguita al salvattaggio e modifica di dati
● E' fondamentale che ogni dato sia sempre validato
● Es. nomi utente univoci
● Es. campi che devono essere solo numerici
ValidatoriValidatori● validates_acceptance_of
● validate_associated
● validates_confirmation_of
● validates_each
● validates_exclusion_of
● validates_format_of
● validates_inclusion_of
● validates_length_of
● validates_numericality_of
● validates_presence_of
● validates_size_of
● validates_uniqueness_of
EsempioEsempioclass User < ActiveRecord::Base
validates_presence_of :login, :email validates_presence_of :password, :if => :password_required? validates_presence_of :password_confirmation, :if => :password_required?
validates_length_of :login, :within => 3..40 validates_length_of :email, :within => 3..100 validates_uniqueness_of :login, :email, :case_sensitive => false validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :message => "Indirizzo email non valido" validates_format_of :login, :with => /^[\w\d\-\_]+$/, :message => "Sono permessi solo lettere" before_save :encrypt_password has_one :country has_many :photos has_one :profile, :dependent => :nullify
ActiveControllerActiveController● I controller sono classi Ruby in
app/controllers
● Ad ogni suo metodo pubblico corrisponde una vista
● Gestiscono le azioni che si compiono sui dati
● Gestiscono tutte le interazioni logiche
● Es. un utente compra un oggetto
● Es. un utente si iscrive al sito
Flash messagesFlash messages● Permettono di impostare un messaggio da
mostrare all'utente nella gestione di una richiesta e visualizzarlo nella successiva
● Varie priorità flash[:notice], flash[:error]
● Utilizzati per azioni che richiedono un feedback dopo un submit
● Es. Compilazione di un ordine online
● Es. Login di un utente
EsempioEsempioclass LinksController < ApplicationController def index @user = User.find_by_id(params[:user_id]) @links = @user.links end def latest n = 10 @links = Link.find(:all, :order => 'created_at ASC', :limit => n) endend
ActionViewActionView● Si occupa del rendering della risposta al
client
● Rendering basato su template
● Permesso l'uso di codice all'interno dei template
● Il controller sceglie quale template visualizzare
● Le view hanno accesso alle variabili d'istanza (ad es. @pluto)
Tipi di templateTipi di template● Rxml – genera output XML. Tipicamente
usato per generare feed RSS/Atom
● Rhtml – genera output HTML
● Rjs – genera codice Javascript. Utilizzato per AJAX
HTML templateHTML template● <%= ruby code %> valuta l'espressione e
stampa l'output
● <%= ruby code -%> valuta l'espressione e stampa l'output senza un newline a fine riga
● <% ruby code %> valuta l'espressione e non stampa l'output
● <%= h comment.body %> escaping dell'output
PartialsPartials● Parti di una pagina (ad es. footer)
● Possono essere inclusi in una o più pagine
● Funzionano come i template
● Il loro nome inizia con un underscore
● Vengono utilizzate per le parti di codice condivise che devono essere riciclate nell'applicazione
● Es. search box
● Es. header e footer
LayoutLayout● I template del layout si trovano in
/app/views/layout
● Il posizionamento di tag permette la visualizzazione delle parti di codice generate
● <%= yield %> visualizza l'output di una view
● Permettono di uniformare il layout del sito e se desiderato di personalizzarlo in alcune sue zone
EsempioEsempio<div class="titlesection"> <%= link_to '<img src="/images/rss_rotated.jpg" />', :controller => 'rss', :action => 'category', :id => 1 %> Papers</div><%= render :partial => 'table' %><p class="readmore"><%= link_to 'more papers', :controller => 'documents', :action => 'category', :id => 1 %></p>
<div class="titlesection"> <%= link_to '<img src="/images/rss_rotated.jpg" />', :controller => 'rss', :action => 'category', :id => 2 %> Slides</div>
<%= render :partial => 'table' %><p class="readmore"><%= link_to 'more papers', :controller => 'documents', :action => 'category', :id => 2 %></p>
<div class="titlesection"> <%= link_to '<img src="/images/rss_rotated.jpg" />', :controller => 'rss', :action => 'category', :id => 3 %> Videos</div>
HelpersHelpers● Moduli Ruby che definiscono metodi
disponibili nei template
● Evitano duplicazione di codice
● Riducono il codice nei template
● Per default ogni controller ha un helper corrispondente
● Usati per definire le funzionalità comuni
● Es. è loggato un utente?
● Es. pagina successiva
EsempioEsempiomodule ApplicationHelper
# Check if a parameter is nil, if it's print - else print the value. NilPrintCheck def npc(var) if var.nil? or var.empty? return '-' else return var end end
TestingTesting● Test::Unit è una libreria Ruby per unit
testing
● Rails integra tre tipi di tests:
– Uni tests (test sul modello)– Integration tests (test di integrazione)– Functional tests (test sul controller)
● Ogni test deve iniziare per test_
● Prima dell'esecuzione di ogni test viene chiamato il medoto setup e alla sua conclusione il metodo teardown
● Ogni test contiene una o piu assert
Unit TestingUnit Testing● Ogni modello ha il suo unit test in
test/units/test_my_model.rb generato automaticamente alla creazione del modello
● Utilizzato per verifcare coerenza di
– Modello– Validatori– Funzioni del modello
● E' buona norma testare ogni funzione del modello in particolare quelle custom
EsempioEsempiorequire File.dirname(__FILE__) + '/../test_helper'
class UserTest < Test::Unit::TestCase fixtures :users
def test_password_chars assert_difference 'User.count' do u = create_user(:login => '0aaaaaaaA-_') end assert_no_difference 'User.count' do u = create_user(:login => 'aaaaaaaaaaa+') assert u.errors.on(:login) end end def test_should_create_user assert_difference 'User.count' do user = create_user assert !user.new_record?, "#{user.errors.full_messages.to_sentence}" end endend
Helpers & FixturesHelpers & Fixtures● Ogni unit test ha il suo helper
● E' possibile utilizzare una fixture che contiene i dati di test che vengono caricati nel database
● Dati memorizzati in sintassi YAML
● Permette di avere uno storage di dati d'esempio
EsempioEsempioquentin: id: 1 login: quentin email: [email protected] created_at: <%= 5.days.ago.to_s :db %>aaron: id: 2 login: aaron email: [email protected] bio: Aaron is a weird guy created_at: <%= 1.days.ago.to_s :db %>
AssertionsAssertions● assert(actual, comment) # Asserts truth
● assert_equal(expected, actual, comment)
● assert_in_delta(expected_float, actual_float, delta,
● message)
● assert_match(pattern, string, message)
● assert_nil(object, message)/assert_not_nil
● assert_raise(Exception, ..., message) { block ... }
● assert_difference(expressions, difference = 1, &block)
Functional testsFunctional tests● Verificano un'istanza di controller
● Simula una richiesta HTTP e controlla la risposta
● Fatti per verificare il funzionamento della logica applicativa
EsempioEsempiorequire File.dirname(__FILE__) + '/../test_helper'require 'comments_controller'class CommentsController; def rescue_action(e) raise e end; endclass CommentsControllerTest < Test::Unit::TestCase fixtures :users, :comments def setup @controller = CommentsController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @request.env['HTTP_HOST'] = "localhost" @request.session[:user] = users(:aaron) end def test_rss get :rss, :id => users(:quentin) assert_response :success assert_select "rss > channel" do assert_select "title", /Recent comments/ assert_select "item", 1 assert_select "item > title", Regexp.new(users(:aaron).login) users(:quentin).comments.first.body end end
AssertionsAssertions● assert_response
:success|:redirect|:missing|:error
● assert_redirected_to(:controller => ‘blog’, :action => ‘list’)
● assert_template ‘store/index’
● assert_not_nil assigns(:items)
● assert session[:user]
● assert_not_nil flash[:notice]
Integration testsIntegration tests● Test di integrazione al Rails dispatcher e
tutti i controller
● Simula scenari d'uso reali
● Inludono le stesse funzionalità dei functional tests
● Simulano l'utilizzo dei componenti nella loro globalità
EsempioEsempioclass TracerBulletTest < ActionController::IntegrationTest def test_tracer_bullet get("/mcm/user/login") assert_response :success post("/mcm/user/login", :email => self.mail, :password => self.password) assert_redirected_to :controller => 'mcm/general' follow_redirect! assert_response :success expect_count = contacts(:adam_sandler).jobs.size post("/mcm/contacts/search", :q => 'sandler new york') assert_response :success assert_n_search_results(expect_count) get "/mcm/lists/show/#{list.id}" assert_response :success assert_template 'mcm/lists/show' endend
Running testsRunning tests● rake - runs all tests
● rake test:units
● rake test:functionals
● rake test:integration
● ruby test/unit/user_test.rb
RCOVRCOV● Rcov e` una libreria Ruby per misurare la
copertura data dalle unit tests
● Utilizzata per trovare parti rimaste scoperte dai test
# Installation of rcov:gem install rcovruby script/plugin install http://svn.codahale.com/rails_rcovrake test:test:rcov
RoutingRouting● Insime di regole che mappano URL e
parametri in componenti Rails
● Le rotte sono definite in config/routes.rb
● Se un URL non trova una rotts corrispondente si ottiene un 404
● Gli oggetti e i controller possono essere mappati sugli URL per creare ad es. /users/photos/1
MVC Request CycleMVC Request Cycle1.Richiesta http://localhost:3000/users/new/1
2.Il server Rails:
1.Invoca il dispatcher
2.Cerca la rotta in routes.rb
3.La rotta di default :controller/:action/:id viene usata se non ne vengono trovate altre
4.Il metodo new è chiamato all'interno del controller users che prendo il dato ad id 1 dal modello
3.Viene generato codice HTML dalla vista new.html.erb
4.Rails invia tutto il codice HTML generato al browser
DeploymentDeployment● Utilizzo di normali web server e CGI
(fastCGI)
● Utilizzo di cluster (mongrel) e web server di front end in modalita` proxy (apache)
● Utilizzo di tool per il deploy automatico (Capistrano)
● Il deploy e il mantenimento potrebbe risultare un tallone d'Achille se non vengono svolti utilizzando procedure corrette e lungimiranti
Best praticesBest pratices● Usare SQL solo se strettamente necessario
● Mettere meno codice possibile nel controller e cercare di tenere logica nel modello
● Accedere ai dati utilizzando l'utente corrente se possibile (ad es. current_user.visits.recent)
● Dipendere il meno possibile da librerie e plugin esterni (inluderle nell'applicazione)
● Utilizzare ampiamente le unit test
● Automatizzare ogni task possibile
Libri utiliLibri utili● Agile Web Development with Rails
● Professional Ruby on Rails – Wrox
● Rails Cookbook – O'Reilly
● Deploying Rails Applications - Pragmatic Bookshelf
● Beginning Ruby on Rails - Wrox
Slides e contattiSlides e contattiQueste slides sono disponibili su:
http://www.lonerunners.net
Scrivetemi pure!