RSS
 

Archive for the ‘Ruby on Rails’ Category

Leonardo: un generatore di applicazioni Rails 3.1

22 Aug

L’aggiornamento alla versione più recente lo trovi sul mio nuovo blog. Click here for the english version.

Ho creato un nuovo generatore per creare applicazioni rails 3.1 (che attualmente si trova in rc6) ed automatizzare le operazioni che risultano ripetitive.

Il nome della gemma è leonardo ed è suddivisa in due generatori:

  1. leolay: per creare il layout
  2. leosca: per creare la risorsa (sostituisce lo scaffold)

Perchè creare un nuovo generatore se esiste già lo scaffold di rails?

Lo scaffold è un comodo generatore e si può anche personalizzare con facilità ma è adatto per scopi didattici e la personalizzazione è limitata alle sole viste. In produzione mi serviva qualcosa di più completo e personalizzabile.

Preparazione dell’ambiente ed installazione della gemma

Creiamo un nuovo ambiente ruby ed installiamo rails 3.1:
gem install rails –pre
usiamo il –pre perchè in questo momento si trova in release candidate, toglietelo se invece è stata rilasciata

Possiamo installare la gemma semplicemente con:
gem install leonardo

…ma non è necessario e consiglio di usare il procedimento che sto per descrivere:
creiamo una nuova applicazione utilizzando il template che ho preparato, raccomandato per agevolare l’avvio e per meglio sfruttare la gemma:

rails new NewApp -m http://cloud.github.com/downloads/marcomd/Leonardo/template.rb

Se non dovesse funzionare il link potete comunque trovare il template nella root della gemma

Il template chiederà se installare determinate gemme esterne, per una prova consiglio di installarle tutte rispondendo y o premendo invio quando propone un default (es. devise)

Verrà poi eseguito un bundle install per verificare la presenza delle gemme ed in seguito verranno eseguite le varie generazioni, il tutto durerà qualche minuto circa.
La generazione del layout chiederà una conferma per sostituire il file en.yml, rispondete y. Preferisco non forzare la sostituzione per permettere altre ed eventuali esecuzioni del generatore leolay nel caso si volesse aggiornare il layout.

Al termine otterrete un applicazione “pronta per partire” potendo così orientare lo sviluppo sulla parte applicativa.

Spostiamoci nella cartella dell’applicazione ed avviamo il server
cd NewApp
rails s
indirizziamo il browser all’indirizzo http://localhost:3000 per accedere alla home

Se clicchiamo su “Sign in” verrà richiesta l’autenticazione (se l’avete inclusa), per un rapido accesso potete inserire:
email: admin@newapp.com
password: abcd1234

Signed in

Vengono create tre utenze con tre ruoli diversi (nel caso avete incluso le autorizzazioni):

  1. admin@newapp.com
  2. manager@newapp.com
  3. user@newapp.com

Naturalmente si tratta di utenze con finalità legate allo sviluppo per cui ricordate di eliminarle prima di qualsiasi rilascio. Possiamo consultare e modificare i ruoli accedendo al file app/models/ability.rb in quanto la gestione è affidata alla gemma cancan.

Per cambiare la lingua è sufficiente inviare la nuova come valore del parametro lang, esempio:

http://localhost:3000/?lang=it

il generatore attualmente gestisce :en e :it ma aggiungere il supporto ad un’altra lingua è semplice, basta aggiungere il file yml nella cartella dove si trovano i primi due, naturalmente deve avere gli stessi tags.

Generare le risorse

Ora generiamo qualche risorsa utilizzando il generatore leosca, una sorta di scaffold personalizzato:

rails g leosca category name:string active:boolean

rails g leosca product category:references name:string description:text price:decimal

ora creiamo le due nuove tabelle anche sul database:
rake db:migrate

ed eseguiamo anche il popolamento con qualche dato che leosca ha preparato per noi:
rake db:seed

Listing Categories

Alcune note:

  • L’esportazione dei dati in csv attualmente non funziona se attiva la paginazione ajax, sto cercando un buon metodo per risolvere.
  • Nelle liste vengono inseriti in automatico tutti i campi per poter applicare dei filtri, eliminiamo quelli che non ci interessano. I filtri su campi boolean non funzionano con sqlite3 a causa di un bug nel driver che non genera un sql corretto. Con SQLServer funziona alla perfezione.
  • Gli oggetti relazionati mostrano un link col nome e cliccando si effettua una :show. Nel caso la tabella non abbia il campo :name viene utilizzato l’id che è possibile sostituire con il dato che più lo rappresenta.
  • Solo l’operazione destroy è gestita tramite ajax. Show, edit e create attualmente sono gestite tramite http.
  • Riguardo le utenze create in automatico: con i ruoli di default admin può fare tutto, manager tutte le operazioni crud, user tutte tranne la delete.
  • Le etichette dei campi vengono inserite in automatico ma la traduzione è necessario farsela da se all’interno dei files config/locales/*.yml

Personalizzare il generatore

E’ sufficiente installarlo nel progetto e personalizzarlo secondo le proprie esigenze:
rails g leosca:install
si trova sotto lib ed eventuali modifiche avranno la precedenza sulla gemma

Conclusioni

Per ulteriori approfondimenti o aggiornamenti sugli sviluppi futuri è possibile consultare la homepage del progetto:
https://github.com/marcomd/Leonardo

Altri riferimenti:
http://edgeguides.rubyonrails.org/generators.html
http://railscasts.com/episodes/216-generators-in-rails-3
http://railscasts.com/episodes/218-making-generators-in-rails-3
http://railscasts.com/episodes/242-thor
http://rdoc.info/github/wycats/thor/master/Thor
http://textmate.rubyforge.org/thor/Thor/Actions.html
http://railscasts.com/episodes/265-rails-3-1-overview

 
 

Pluralizzare in tutte le lingue

08 Sep

In questo articolo mostrerò come utilizzare quel comodo helper di rails, pluralize, con tutte le lingue mediante l’utilizzo di I18n.

Diamo per scontato che l’applicazione sia internazionalizzata, quindi modelli e campi presenti nei file yaml di configurazione.
Ora dobbiamo aggiungere i termini per i quali vogliamo internazionalizzare la pluralità: modelli, attributi ecc.

activerecord: &activerecord
    models: &models
      user: "Utente"
      users: "Utenti"
      activity: "Impiego"
      activities: "Impieghi"
      task: "Incarico"
      tasks: "Incarichi"
      project: "Progetto"
      projects: "Progetti"
 
    attributes: &attributes
      activity:
        task: "Incarico"
        day: "Giorno"
        hours: "Ore"
        hour: "Ora"
        description: "Descrizione"

Ipotizziamo che il nostro modello “Incarico” sia relazionato col modello “Impiego” e che questo abbia un campo di nome “Ore”. Vogliamo creare una frase che descriva quanti impieghi sono presenti per tale incarico ed il totale delle ore.

In base ai dati nel database, vogliamo ottenere frasi del tipo:
Questo incarico ha 1 impiego per un totale di 1 ora.
Questo incarico ha 13 impieghi per un totale di 0 ore.
Questo incarico ha 1 impiego per un totale di 15 ore.

…e in tutte le lingue:

This task has 1 activity with an amount of 15 hours.
Diese Aufgabe haben 1-Aktivität für eine Gesamtmenge von 15 Stunden.
Cette tâche a 1 activité pour un total de 15 heures.
Esta tarea tiene 1 actividad para un total de 15 horas.
このタスクは、 15時間の量1活性を持つ

Vogliamo evitare:
Questo incarico ha 1 impieghi per un totale di 1 ore
This task has 1 activities with an amount of 1 hours

Aggiungiamo la frase in ogni file yaml I18n:

  #it.yml
  task_activities: "Questo incarico ha <strong>{{activities}}</strong> per un totale di <strong>{{hours}}</strong>."
  #en.yml
  task_activities: "This task has <strong>{{activities}}</strong> with an amount of <strong>{{hours}}</strong>."
  #etc.

Per fare ciò ci avvaliamo del comodo helper di rails, pluralize, opportunamente modificato per eliminare il numero dal risultato. Pluralizziamo il termine inglese da usare come chiave per I18n.

Creiamo nell’helper dell’applicazione:

module ApplicationHelper
   #Pluralize without the number
   def I18n_pluralize(count, singular, plural = nil)
     ((count == 1 || count =~ /^1(\.0+)?$/) ? singular : (plural || singular.pluralize))
   end
end

Nella view richiamiamo la frase I18n passando il numero di impieghi ed il totale di ore (per motivi descrittivi ho inserito nella view operazioni che però riterrei più opportuno posizionare nel controller):

  <% tot_hours = @activities.map{|a| a.hours}.inject{|tot,h| tot+h}  || 0 %>
  <% str_hour = t("attributes.activity.#{I18n_pluralize(tot_hours,'hour')}") %>
  <% str_activity = t("models.#{I18n_pluralize(@activities.size,'activity')}") %>
  <%= t :task_activities, :activities => "#{@activities.size} #{str_activity}", :hours => "#{@tot_hours} #{str_hour}" %>

Osserviamo questa riga alla moviola:

t("attributes.activity.#{I18n_pluralize(tot_hours,'hour')}")

In base al numero di ore otteniamo la chiave I18n: hour o hours

chiave = I18n_pluralize(tot_hours,'hour')

equivale

t("attributes.activity.#{chiave}")

se tot_hours = 1 avremo:

t("attributes.activity.hour")

altrimenti:

t("attributes.activity.hours")

Questo è il risultato:
Esempio Pluralize I18n

 
Comments Off

Posted in Ruby on Rails

 

Creare scheletri di applicazioni con generatori e template

20 Mar

Ultima modifica 01/04/2010

Rails è un meraviglioso strumento che permette di realizzare agevolmente, un’applicazione secondo gli standard moderni. Tuttavia, quando se ne crea una nuova, è necessario eseguire diverse operazioni alquanto noiose: aggiungere plugins o gemme, il recupero di un layout di partenza e tutto ciò che di solito usate nelle vostre applicazioni.
Per risolvere questo problema, dalla versione 2.3 è possibile utilizzare i templates, modelli con cui è possibile diversificare la creazione di scheletri di applicazioni. Si trovano molti esempi su internet, io ne presento uno che mostra i vantaggi in combinazione con un generatore personalizzato per le proprie esigenze. Questa guida spiega come creare un generatore. Se siete impazienti potete partire da qualcosa di già fatto come ho fatto io. Ho personalizzato il nifty-generators di Ryan Bates aggiungendo alcune nuove caratteristiche:

  • Layout: ora supporta stili multipli: classic, cloudy and blackwhite. Niente di che, solo una base da sviluppare
  • Layout: ho inserito il file di configurazione config.rb, contenitore delle costanti applicative come il nome dell’applicazione, lo stile di default ecc.
  • Ho introdotto la localizzazione: il nifty_layout aggiunge i files yaml per la lingua en e it. Nell’application controller viene inserito del codice per la gestione, con il parametro lang si cambia l’impostazione della lingua (esempio ?lang=it) che verrà mantenuta per tutta la sessione. Il nifty_scaffold aggiunge le risorse e i suoi attributi, pronti per essere tradotti.
  • nifty_layout: la div per i messaggi è stata spostata dentro un partial nella cartella shared. Ho preferito fare questo per poterla utilizzare anche tramite ajax
  • nifty_scaffold: le views new e edit, ora utilizzano due partial: render @model che a sua volta richiama il secondo con all’interno i campi. Come la versione originale, supporta haml e sass ed ho aggiunto il supporto a formtastic potendo quindi scegliere la combinazione preferita!

La nuova versione del nifty-generators è scaricata dal template che vi mostrerò, comunque, questi sono i sorgenti, qua invece, potete scaricare l’ultima versione della gemma e del template.

Cos’è che dovrebbe fare il generatore e cosa il template?
Beh, i templates dovrebbero essere un contenitore di operazioni da compiere, eseguiti nella fase di creazione di un nuovo progetto. Con i generatori invece, è possibile fare quasi tutto. In genere, si aggiungono nuove funzionalità o si personalizza qualcosa che già esiste. Può essere eseguito ogni volta che si vuole da riga di comando e naturalmente, richiamato all’interno di un template.

Chi non ha mai usato un template, potrebbe iniziare con un semplice esempio o meglio, con un tutorial:
questo screencast mostra come destreggiarsi e questo invece, è un articolo in html.

I molti esempi che ho visto su internet sono spesso pieni di parametri di configurazione che mi fan venire dei grossi mal di testa. Io invece ho preferito che mi venisse chiesto cosa voglio. Sicuramente, non è ancora perfetto, ma penso che sia una buona base :

#This is a Rails 2.3 template
#Written by Marco Mastrodonato, last update on 01/04/2010 
#
# USAGE: rails yourapp -m template.rb
 
#This is the unique parameter, after first execution use FALSE to avoid installation
install_gems = false
git_http = true
 
WINDOWS = (RUBY_PLATFORM =~ /dos|win32|cygwin/i) || (RUBY_PLATFORM =~ /(:?mswin|mingw)/) 
 
#plugin/install doesn't work on my windows systems
def myplugin(name, url)
  url.sub! 'git://', 'http://' if git_http
  if WINDOWS
    #run "ruby script/plugin install #{url}"
    run "git clone #{url} vendor/plugins/#{name}"
  else
    plugin name, :git => url
  end
end
 
puts '*' * 40
puts "* Processing template#{WINDOWS ? ' on windows system': ''}..."
puts '*' * 40
 
use_git = yes?("Do you think to use git ?")
if use_git
  git :init
  file ".gitignore", <<-EOS.gsub(/^    /, '')
    .DS_Store
    log/*.log
    tmp/**/*
    config/database.yml
    db/*.sqlite3
    nbproject/*
  EOS
end
 
#Everyone must to have it!
plugin 'will_paginate',
        :git => "git://github.com/mislav/will_paginate.git" if yes?("Will Paginate ?")
 
# Attachments with no extra database tables, only one library to install for image processing
plugin 'paperclip',
       :git => "git://github.com/thoughtbot/paperclip.git" if yes?("Paperclip ?")
 
formtastic = yes?("Formtastic ?")
if formtastic
  #gem 'justinfrench-formtastic', :lib => 'formtastic', :source => 'http://gems.github.com'
  #rake "gems:install" if install_gem
 
  #A Rails FormBuilder DSL (with some other goodies) to make it far easier to create beautiful, semantically rich, syntactically awesome, readily stylable and wonderfully accessible HTML forms in your Rails applications.
  plugin 'formtastic',
        :git => "git://github.com/justinfrench/formtastic.git"
  #Adds reflective access to validations
  plugin 'validation_reflection',
        :git => "git://github.com/redinger/validation_reflection.git"
  generate "formtastic"
end
 
if yes?("Add testing framework ?")
  # RSpec's official Ruby on Rails plugin  
  plugin 'rspec', 
    :git => 'git://github.com/dchelimsky/rspec.git'
 
  plugin 'rspec-rails',
         :git => "git://github.com/dchelimsky/rspec-rails.git"
  generate "rspec"
 
  # BDD that talks to domain experts first and code 2nd
  plugin 'cucumber',
         :git => "git://github.com/aslakhellesoy/cucumber.git"
end
 
if yes?("Add authentication ?")
  puts "0. None"
  puts "1. Devise/warden"
  puts "2. Authlogic"
  puts "3. Restful authentication"
  choose = ask("Choose one:")
  case choose
    when "1"
      gem "warden", :version => "0.9.6"
      gem "devise", :version => "1.0.4"
      rake "gems:install" if install_gems
      generate "devise_install"
      generate "devise", "User"
      generate "devise_views"
      #User.create!(:email => 'admin@administrator.com', :password => 'secret')
    when "2"
      gem "authlogic"
      rake "gems:install" if install_gems
      generate "session", "user_session"
      generate "model", "user"
      generate "controller", "user_sessions"
      route "map.resource :user_session"
      create_users_file = Dir['db/migrate/*_create_users.rb'].first
      file create_users_file, <<-EOS.gsub(/^        /, '')
        class CreateUsers < ActiveRecord::Migration
          def self.up
            create_table :users do |t|
              t.string    :login,               :null => false                # optional, you can use email instead, or both
              t.string    :email,               :null => false                # optional, you can use login instead, or both
              t.string    :crypted_password,    :null => false                # optional, see below
              t.string    :password_salt,       :null => false                # optional, but highly recommended
              t.string    :persistence_token,   :null => false                # required
              t.string    :single_access_token, :null => false                # optional, see Authlogic::Session::Params
              t.string    :perishable_token,    :null => false                # optional, see Authlogic::Session::Perishability
 
              # Magic columns, just like ActiveRecord's created_at and updated_at. These are automatically maintained by Authlogic if they are present.
              t.integer   :login_count,         :null => false, :default => 0 # optional, see Authlogic::Session::MagicColumns
              t.integer   :failed_login_count,  :null => false, :default => 0 # optional, see Authlogic::Session::MagicColumns
              t.datetime  :last_request_at                                    # optional, see Authlogic::Session::MagicColumns
              t.datetime  :current_login_at                                   # optional, see Authlogic::Session::MagicColumns
              t.datetime  :last_login_at                                      # optional, see Authlogic::Session::MagicColumns
              t.string    :current_login_ip                                   # optional, see Authlogic::Session::MagicColumns
              t.string    :last_login_ip                                      # optional, see Authlogic::Session::MagicColumns
 
              t.timestamps
            end
 
            add_index :users, :email
            add_index :users, :login, :unique => true
          end
 
          def self.down
            drop_table :users
          end
        end
      EOS
      file 'app/models/user.rb', <<-EOS.gsub(/^        /, '')
        class User < ActiveRecord::Base
          acts_as_authentic
        end
      EOS
      file 'spec/models/user_spec.rb', <<-EOS.gsub(/^        /, '')
        require 'spec_helper'
 
        describe User do
          before(:each) do
            @valid_attributes = {
              :login                 => "login",
              :email                 => "some@thing.com",
              :password              => "password",
              :password_confirmation => "password"
            }
          end
 
          it "should create a new instance given valid attributes" do
            User.create!(@valid_attributes)
          end
        end
      EOS
    when "3"
      plugin 'restful-authentication-i18n',
          :git => "git://github.com/dcrec1/restful-authentication-i18n.git"
      #—include-activation \ —stateful \ —rspec \ —skip-migration \ —skip-routes \ —old-passwords 
      generate "authenticated", "user", "sessions", "—include-activation"
  end
 
  if yes?("Authorization ?")
    gem "cancan"
    rake "gems:install" if install_gems
    #plugin 'cancan', :git => "git://github.com/ryanb/cancan.git" 
    file 'spec/models/ability.rb', <<-EOS.gsub(/^      /, '')
      class Ability  
        include CanCan::Ability  
 
        def initialize(user)  
          user ||= User.new # Guest user  
          if user.role? :admin  
            can :manage, :all  
          else  
            can :read, :all  
          end  
        end  
      end  
    EOS
    generate "migration", "add_roles_mask_to_users roles_mask:integer"
    puts "Add these lines to user model:"
    puts <<-EOS.gsub(/^      /, '')
      named_scope :with_role, lambda { |role| {:conditions => "roles_mask & \#{2**ROLES.index(role.to_s)} > 0 "} }
      def roles=(roles)
        self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r) }.sum
      end
      def roles
        ROLES.reject { |r| ((roles_mask || 0) & 2**ROLES.index(r)).zero? }
      end
      def role_symbols
        roles.map(&:to_sym)
      end
    EOS
 
  end
 
end          
 
haml = yes?("Haml?")
if haml
  # The world's greatest templating system
  #plugin 'haml', :git => "git://github.com/nex3/haml.git"
  gem "haml", :lib => "haml"
  rake "gems:install" if install_gems
  run "haml --rails ."
end
 
if yes?("Layout ?")
  plugin "marcomd-nifty-generators", 
    :lib => "marcomd-nifty-generators",
    :git => "git://github.com/marcomd/nifty-generators.git"
  puts " 1. classic"
  puts " 2. cloudy"
  puts " 3. blackwhite"
  puts "or. write names separed by space"
  style = ask("Choose style:")
  style = case style
    when "1" then "classic"
    when "2" then "cloudy"
    when "3" then "blackwhite"
    else style
  end
  generate "nifty_layout", "application #{style} #{haml ? '--haml' : ''} #{formtastic ? '--formtastic' : ''}"
end
 
# Link to local copy of edge rails
#inside('vendor') { run 'ln -s ~/dev/rails/rails rails' } if yes?("Rails ?")
rake("rails:freeze:gems") if yes?("Freeze rails gems ?")
 
# Mailer dummy config
initializer "mailer.rb", <<-EOS.gsub(/^  /, '')
  mailer_options = YAML.load_file("\#{Rails.root}/config/mailer.yml")
  ActionMailer::Base.smtp_settings = mailer_options
EOS
file "config/mailer.yml", <<-EOS.gsub(/^  /, '')
  :address: mail.authsmtp.com
  :domain: yourdomain.com
  :authentication: :login
  :user_name: USERNAME
  :password: PASSWORD
EOS
 
if yes?("Generate controller home ? (suggested)")
  generate "controller", "home", "index"
  route "map.root :controller => :home"
else
 
end
 
rake "gems:install" if install_gems
# Unpack all gems to vendor/gems
rake "gems:unpack" if yes?("Unpack to vendor/gems ?")
rake "db:create:all"
rake "db:migrate"
File.unlink "public/index.html"
 
git :add => ".", :commit => "-m 'initial commit'" if use_git
 
puts "ENJOY!"

Questo è uno dei possibili risultati:


marco@d9400:~/Rails$ rails Blog -m marcomd.rb
create bla bla bla
applying template: marcomd.rb
Processing template...
plugin will_paginate
Unpacking objects: 100% (57/57), done.
From git://github.com/mislav/will_paginate
* branch HEAD -> FETCH_HEAD
Paperclip ?
y
plugin paperclip
Unpacking objects: 100% (78/78), done.
From git://github.com/thoughtbot/paperclip
* branch HEAD -> FETCH_HEAD
Formtastic ?
y
gem justinfrench-formtastic
generating formtastic_stylesheets
Add testing framework ?
y
plugin rspec
From git://github.com/dchelimsky/rspec
* branch HEAD -> FETCH_HEAD
plugin rspec-rails
From git://github.com/dchelimsky/rspec-rails
* branch HEAD -> FETCH_HEAD
plugin cucumber
From git://github.com/aslakhellesoy/cucumber
* branch HEAD -> FETCH_HEAD
Add authentication ?
y
0. None
1. Devise/warden
2. Authlogic
3. Restful authentication
Choose one:
1
gem warden
gem devise
generating devise_install
Authorization ?
n
Haml?
n
Layout ?
y
plugin marcomd-nifty-generators
From git://github.com/marcomd/nifty-generators
* branch HEAD -> FETCH_HEAD
1. classic
2. cloudy
3. blackwhite
or. write names separed by space
Choose style:
2
generating nifty_layout
Freeze rails gems ?
n
initializer mailer.rb
file config/mailer.yml
Generate controller home ? (suggested)
y
generating controller
route map.root :controller => :home
Unpack to vendor/gems ?
n
rake db:create:all
rake db:migrate
ENJOY!
applied marcomd.rb

Ecco fatto, ora avviamo mongrel


marco@d9400:~/Rails$ cd Blog/
marco@d9400:~/Rails/Blog$ script/server

=> Booting Mongrel
=> Rails 2.3.5 application starting on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server

http://localhost:3000 e controlliamo il nostro nuovo blog

This is only the beginning

Ora aggiungiamo una risorsa e lanciamo la migrate per allineare il db:


marco@d9400:~/Rails$ cd Blog/
marco@d9400:~/Rails/Blog$ script/generate nifty_scaffold post title:string body:text

exists app/models
create app/models/post.rb
exists db/migrate
create db/migrate/20100319175545_create_posts.rb
exists test/unit
create test/unit/post_test.rb
exists test/fixtures
create test/fixtures/posts.yml
exists app/controllers
create app/controllers/posts_controller.rb
exists app/helpers
create app/helpers/posts_helper.rb
create app/views/posts
create app/views/posts/index.html.erb
create app/views/posts/show.html.erb
create app/views/posts/new.html.erb
create app/views/posts/edit.html.erb
create app/views/posts/_post.html.erb
create app/views/posts/_fields.html.erb
route map.resources :posts
exists test/functional
create test/functional/posts_controller_test.rb

marco@d9400:~/Rails/Blog$ rake db:migrate

(in /home/marco/Rails/Blog)
== CreatePosts: migrating ====================================================
-- create_table(:posts)
-> 0.0016s
== CreatePosts: migrated (0.0017s) ===========================================

Add post resource

I controllers e le viste usano messaggi localizzati, per questo motivo i nomi delle risorse devono essere aggiunti all’interno degli yaml. Dalla versione 0.3.2.3 nifty_scafold lo fa per voi, dovete pensare solo alla traduzione. Tenete presente inoltre, che se per esempio distruggete una risorsa deve essere rimossa dagli yaml manualmente.

#config/locales/en.yml
en:
  activerecord:
    models: &models
      post: "Post"
      posts: "Posts"
    #DO NOT REMOVE MODELS
    attributes: &attributes
      title: "Title"
      body: "Body"
    #DO NOT REMOVE ATTRIBUTES
 
  formtastic:
    titles:
    labels:
    hints:
      post:
        title: "Choose a good title for your article"
        body: "Choose a good body for your article"
    #DO NOT REMOVE HINTS
    actions: &actions
      create: "Create my {{model}}"
      update: "Save changes"
  <<: *models
  <<: *attributes
  <<: *actions
 
#config/locales/it.yml
it:
  activerecord:
    models: &models
      post: "Messaggio"
      posts: "Messaggi"
    #DO NOT REMOVE MODELS
    attributes: &attributes
      title: "Titolo"
      body: "Corpo"
    #DO NOT REMOVE ATTRIBUTES
 
  formtastic:
    titles:
    labels:
    hints:
      post:
        title: "Choose a good title for your article"
        body: "Choose a good body for your article"
    #DO NOT REMOVE HINTS
    actions: &actions
      create: "Crea {{model}}"
      update: "Salva le modifiche"
  #DO NOT REMOVE FORMTASTIC
  <<: *models
  <<: *attributes
  <<: *actions

Ho usato dei commenti del tipo: #DO NOT REMOVE ecc. per posizionare i nomi delle risorse o degli attributi nelle posizioni corrette, per cui è necessario non rimuoverli.

Messaggio creato con successo

Ho da poco iniziato a testare la versione haml e sass per cui potrebbero esserci ancora dei bachi.
Spero che questo articolo possa essere stato utile, buon divertimento!

 
Comments Off

Posted in Ruby on Rails

 

Gestire instradamenti errati

20 Nov

La nostra applicazione può rispondere solo a determinate richieste, naturalmente a quelle che abbiamo previsto. Per tutte le altre viene generato un errore in base al tipo:

  • Errori di instradamento
  • Errori nel controller

Errori di instradamento

Piccola premessa: In un’applicazione tradizionale, equivaleva a richiamare una pagina inesistente ricevendo dal web server un codice 404. In un framework con un pattern mvc, le richieste transitano attraverso un controller e questo ci permette di definirne il comportamento. Le richieste vengono smistate in base alle regole di instradamento ed in Rails, per convenzione, tutti i controllers possono essere indirizzati, in fondo al file routes.rb scopriamo il perchè:

#config\routes.rb
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'

Per fare in modo che solo determinati controllers possano essere instradati, basta eliminare quelle due righe in fondo al file che definiscono solo la forma della richiesta, lasciando solo gli instradamenti espliciti. Fine della premessa.

Se non esiste una rotta, viene generato un errore di instradamento (routing error) poichè Rails non sa cosa intendiamo richiamare. Per gestire questa evenienza, basta semplicemente aggiungere un’ultima strada prima di generare l’errore che percorrerà solo per informare che non ha trovato altre strade valide:

4
5
6
7
#config\routes.rb
 
#aggiungiamo in ultima riga, in coda a tutto
map.catch_all "*anything" , :controller => "home" , :action => "unknown_request"

Quest’ultima rotta cattura gli instradamenti non gestiti e li indirizza nel controller dove vogliamo gestirli, nell’esempio indirizza nel controller home col metodo unknown_request:

#app\controllers\home_controller.rb
def unknown_request
  respond_to do |format|
    format.html do
      flash[:error] = I18n.t(:unknown_request)
      redirect_to root_path
    end
    format.xml  {  render :xml => {:root => I18n.t(:unknown_request) }, :status => :unprocessable_entity }
  end
end

Errori nel controller

Se il controller è stato indirizzato non è detto che la richiesta sia comunque corretta, ognuno infatti ha una serie di operazioni che devono essere previste e devono poter operare sulla risorsa quindi gestire le eccezioni sollevate dal database.

Dalla versione 2.0 di rails, possiamo utilizzare rescue_from nel controller:

#app\controllers\my_controller.rb
 
class MyController < ApplicationController
  rescue_from ActionController::UnknownAction, :with => :action_not_found
  rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found
 
  def action_not_found
    render 'shared/action_not_found', :layout => true, :status => 404
  end
  def record_not_found
    render 'shared/record_not_found', :layout => true, :status => 404
  end
end

Queste sono le viste:

#app\views\sharedaction _not_found.html.erb
<h2>Sorry, the action doesn't exists!</h2>
 
#app\views\sharedrecord_not_found.html.erb
<h2>Sorry, the record doesn't exists!</h2>

Usare una proc come parametro a rescue_from potrebbe essere un’alternativa più concisa:

#app\controllers\my_controller.rb
rescue_from(ActiveRecord::RecordNotFound) { |e| render 'shared/record_not_found', :layout => true, :status => 404 }
 
Comments Off

Posted in Ruby on Rails

 

FreeTTS, una libreria java in jRuby on Rails

03 Aug

In questo articolo vedremo come utilizzare le classi java contenute in un file JAR
Per questo scopo andremo ad utilizzare una simpatica libreria opensource sviluppata dalla Carnegie Mellon University: FreeTTS.
L’acronimo TTS significa Text To speech tradotto: “da testo a voce”, permette infatti di trasformare un testo in formato audio. Noi la utilizzeremo in un progetto jRuby on Rails per farci leggere il testo che inseriremo nel db.

Nel precedente articolo, abbiamo visto come configurare l’ambiente, partiamo quindi creando la nuova applicazione:


C:>rails ProvaFreeTTS

Nella cartella lib (dalla root) creiamo una sotto cartella freetts, scarichiamo il file freetts-1.2.2-bin.zip, scompattiamo il contenuto in una cartella temporanea, copiamo solamente il contenuto della cartella lib (files jar e jsapi) nella cartella appena creata: tua_applicazionelibfreetts.

Ora creiamo l’interfaccia per il jar:

#libfreetts.rb
 
require 'freetts/freetts.jar'
 
import com.sun.speech.freetts.Voice
import com.sun.speech.freetts.VoiceManager
import com.sun.speech.freetts.util.Utilities
 
class FreeTTS
  def initialize
    @voice = VoiceManager.getInstance.getVoice(Utilities.getProperty("voice16kName","kevin16"))
    @voice.allocate
  end
 
  def speak(txt=nil)
    return nil unless txt
    @voice.speak txt
  end
end

Creiamo una semplicissima risorsa “sentence” con un solo campo “body


C:ProvaFreeTTS>jruby script/generate scaffold sentence body:text

Ora creiamo due nuove operazioni, come ho spiegato approfonditamente in un precedente articolo.

Iniziamo dal controller aggiungendo in coda:

  #appcontrollerssentences_controller.rb
 
  def speak
    @sentence = Sentence.new(params[:sentence])
    require 'freetts'
    tts = FreeTTS.new
    tts.speak @sentence.body
    render (@sentence.new_record? ? :new : :edit)
  end
 
  def read
    @sentence = Sentence.find(params[:id])
    require 'freetts'
    tts = FreeTTS.new
    tts.speak @sentence.body
    redirect_to :back
  end

Ora andiamo a modificare le viste.
Creiamo un nuovo file, più precisamente un partial dove inseriremo il form dati per la nostra risorsa, in questo modo utilizzeremo lo stesso codice per tutte le operazioni:

#appviewssentence_sentence.html.erb
 
<% form_for(@sentence) do |f| %>
  <%= f.error_messages %>
 
  <p>
    <%= f.label :body %><br />
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit 'Update' %>
  </p>
<% end %>
 
<h2>Preview</h2>
<% form_for @sentence, :url => speak_sentences_path, :method => :put do |f| %>
  <%= f.error_messages %>
 
  <p>
    <%= f.label :body %><br />
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit 'Speak' %>
  </p>
<% end %>

Aggiungiamo un form clone di quello creato dallo scaffold ma con l’action diversa, per richiamare l’operazione speak.

Ora modifichiamo le viste create in automatico:

#appviewssentencenew.html.erb
 
<h1>New sentence</h1>
 
<%= render @sentence %>
 
<%= link_to 'Back', sentences_path %>
 
#appviewssentenceedit.html.erb
 
<h1>Editing sentence</h1>
 
<%= render @sentence %>
 
<%= link_to 'Show', @sentence %> |
<%= link_to 'Back', sentences_path %>

Infine modifichiamo la lista per richiamare la seconda delle operazioni implementate, read. Sostanzialmente cliccando sulla riga corrispondente leggerà il testo precedentemente memorizzato nel db.

Dobbiamo solamente aggiungere una riga ottenendo questa view:

#appviewssentenceindex.html.erb
<h1>Listing sentences</h1>
 
<table>
  <tr>
    <th>Body</th>
  </tr>
 
<% @sentences.each do |sentence| %>
  <tr>
    <td><%=h sentence.body %></td>
    <td><%= link_to 'Read', read_sentence_path(sentence) %></td>
    <td><%= link_to 'Show', sentence %></td>
    <td><%= link_to 'Edit', edit_sentence_path(sentence) %></td>
    <td><%= link_to 'Destroy', sentence, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
<% end %>
</table>
 
<br />
 
<%= link_to 'New sentence', new_sentence_path %>

Infine andiamo a modificare il routing:

#configroutes.rb
 
#sostituiamo map.resources :sentences con
map.resources :sentences, :member => { :read => :get }, :collection => { :speak => :put }
 
#aggiungiamo
map.root :controller => "sentences"

Configuriamo il file database.yml, creiamo il db e le tabelle con rake e facciamo partire glassfish come spiegato qua (cerca database.yml)

Come abbiamo visto, utilizzare librerie java con jruby on rails è semplicissimo, siamo pronti per far pronunciare al nostro server tutte le frasi più sporcaccione!

Il progetto completo potete scaricarlo qua.

Per ulteriori approfondimenti c’è anche questo articolo, mentre questo tratta la libreria FreeTTS in un progetto Java.

Buona continuazione.

 
Comments Off

Posted in JRuby, Ruby on Rails

 

Configurare un ambiente per jRuby on Rails

29 Jul

Nei prossimi articoli utilizzerò jruby 1.3.1 con Rails 2.3.3 e ne approfitto per scrivere due righe su come configurare il sistema.
A proposito, il sistema utilizzato è un windows xp virtualizzato, java sdk 6, glassfish come application server e javadb.

Scarichiamo e installiamo l’sdk java, siamo alla versione 6 update 14.

C’è anche il download dell’sdk con netbeans, un ottimo IDE per gestire progetti ruby on rails. Potrebbe essere un occasione per provarlo. Il pacchetto comprende anche l’application server glassfish V3.

Ora scarichiamo e installiamo jRuby, siamo alla versione 1.3.1.

L’installazione è semplicissima: scompattiamo lo zip in un percorso del tipo C:\ruby\jruby-131

Andiamo ad impostare le variabili d’ambiente, tasto destro su “Risorse del computer” sul desktop -> “Proprietà” -> seleziona il tab “Avanzate” -> click su “variabili d’ambiente”.

  • Aggiungiamo alla variabile PATH il percorso della cartella bin dove abbiamo installato jruby, per esempio C:\ruby\jruby-131\bin.
  • Impostiamo la variabile d’ambiente JAVA_HOME. Nella zona “variabili di sistema” -> “nuovo” -> come nome “JAVA_HOME” come valore il percorso di installazione dell’sdk per esempio C:\Programmi\Java\jdk1.6.0_14

Non è necessario riavviare il sistema operativo, testiamo il risultato:

C:\>jruby -v
jruby 1.3.1 (ruby 1.8.6p287) (2009-06-15 2fd6c3d) (Java HotSpot(TM) Client VM 1.6.0_14) [x86-java]

Eureka! Ora abbiamo jruby, jirb ed il solito gem. Se nel sistema è presente anche ruby classico è necessario specificare jruby -S per richiamare gem, utilizziamolo subito per installare rails 2.3.3:

C:\>jruby -S gem install rails
Successfully installed activesupport-2.3.3
Successfully installed activerecord-2.3.3
Successfully installed actionpack-2.3.3
Successfully installed actionmailer-2.3.3
Successfully installed activeresource-2.3.3
Successfully installed rails-2.3.3
6 gems installed
Installing ri documentation for activesupport-2.3.3...
Installing ri documentation for activerecord-2.3.3...
Installing ri documentation for actionpack-2.3.3...
Installing ri documentation for actionmailer-2.3.3...
Installing ri documentation for activeresource-2.3.3...
Installing ri documentation for rails-2.3.3...
Installing RDoc documentation for activesupport-2.3.3...
Installing RDoc documentation for activerecord-2.3.3...
Installing RDoc documentation for actionpack-2.3.3...
Installing RDoc documentation for actionmailer-2.3.3...
Installing RDoc documentation for activeresource-2.3.3...
Installing RDoc documentation for rails-2.3.3...

Installiamo l’adapter jdbc per collegarci a java DB (da ora ometto jruby -S, nel mio sistema non è necessario):

C:\>gem install activerecord-jdbcderby-adapter
Successfully installed activerecord-jdbc-adapter-0.9.1
Successfully installed jdbc-derby-10.4.2.0
Successfully installed activerecord-jdbcderby-adapter-0.9.1
3 gems installed
Installing ri documentation for activerecord-jdbc-adapter-0.9.1...
Installing ri documentation for jdbc-derby-10.4.2.0...
Installing ri documentation for activerecord-jdbcderby-adapter-0.9.1...
Installing RDoc documentation for activerecord-jdbc-adapter-0.9.1...
Installing RDoc documentation for jdbc-derby-10.4.2.0...
Installing RDoc documentation for activerecord-jdbcderby-adapter-0.9.1...

Poi installiamo la gemma per glassfish:

C:\>gem install glassfish
Successfully installed rack-1.0.0
Successfully installed glassfish-0.9.5-universal-java
2 gems installed
Installing ri documentation for rack-1.0.0...
Installing ri documentation for glassfish-0.9.5-universal-java...
Installing RDoc documentation for rack-1.0.0...
Installing RDoc documentation for glassfish-0.9.5-universal-java...

Siamo pronti, creiamo un nuovo progetto:

C:\>rails ProvaArticolo

…nel file di configurazione dei db utilizziamo:

# config/database.yml
 
# JavaDB Setup
#
# You may need to copy derby.jar into
#  TODO: location C:\ruby\jruby-131\lib
# With Java SE 6 and later this is not necessary.
development:
  adapter: derby
  database: db/development.db
 
# Warning: The database defined as 'test' will be erased and
# re-generated from your development database when you run 'rake'.
# Do not set this db to the same as development or production.
test:
  adapter: derby
  database: db/test.db
 
# Warning: The database defined as 'test' will be erased and
# re-generated from your development database when you run 'rake'.
# Do not set this db to the same as development or production.
production:
  adapter: derby
  database: db/production.db
 
# Warning: The database defined as 'test' will be erased and
# re-generated from your development database when you run 'rake'.
# Do not set this db to the same as development or production.

Facciamo partire il server, dal prompt dentro la nostra applicazione:

C:\ProvaArticolo>glassfish
Starting GlassFish server at: 127.0.0.1:3000 in development environment...
Writing log messages to: C:/ProvaArticolo/log/development.log.
Press Ctrl+C to stop.

All’indirizzo http://localhost:3000/ dovremmo vedere la pagina di benvenuto.
Ora fermiamo il server con Ctrl+C e creiamo il db in ambiente sviluppo:

C:\ProvaArticolo>rake db:create
(in C:/ProvaArticolo)
db/development.db already exists

Creiamo qualcosa all’interno del database, iniziamo con la risorsa di rails:

C:\ProvaArticolo>jruby script/generate scaffold article name:string body:text
create app/models/
exists app/controllers/
exists app/helpers/
exists app/views/articles
create app/views/layouts/
create test/functional/
create test/unit/
create test/unit/helpers/
create public/stylesheets/
create app/views/articles/index.html.erb
create app/views/articles/show.html.erb
create app/views/articles/new.html.erb
create app/views/articles/edit.html.erb
create app/views/layouts/articles.html.erb
create public/stylesheets/scaffold.css
create app/controllers/articles_controller.rb
create test/functional/articles_controller_test.rb
create app/helpers/articles_helper.rb
create test/unit/helpers/articles_helper_test.rb
route map.resources :articles
dependency model
exists app/models/
exists test/unit/
create test/fixtures/
create app/models/article.rb
create test/unit/article_test.rb
create test/fixtures/articles.yml
exists db/migrate
create db/migrate/20090729171105_create_articles.rb

e creiamo la tabella nel db con la migrate:

C:\ProvaArticolo>rake db:migrate
(in C:/ProvaArticolo)
== CreateArticles: migrating =================================================
-- create_table(:articles)
-> 0.0700s
-> 0 rows
== CreateArticles: migrated (0.0700s) ========================================

Infine eliminiamo il file index.html da dentro la cartella public e creiamo la route iniziale:

#config\routes.rb
map.root :controller => "articles"

Riavviamo il server e sempre all’indirizzo http://localhost:3000/ questa volta dovremmo vedere la lista degli articoli.

Se non mi sono dimenticato qualcosa, jruby on rails è pronto, buon divertimento!

 
 

Aggiungere un operazione ad una risorsa in un sistema RESTful

22 Jun

Creare un applicazione RESTful con rails è molto semplice:

C:>rails MyApp

In un sistema REST, l’applicazione è composta da risorse web aventi un insieme vincolato di operazioni. Con lo scaffold generiamo una risorsa e le quattro basilari funzioni per gestirla, denominate CRUD (Create, Read, Update, Delete)

C:>script/generate scaffold assets name:string

Abbiamo creato la base da cui partire per testare questo breve articolo.

Supponiamo di voler aggiungere una nuova operazione alla nostra risorsa risorsa Asset, per esempio vogliamo gestire la copia. Iniziamo creando il nuovo indirizzamento modificando quanto creato dallo scaffold:

#config/routes.rb
map.resources :assets, :member => { :copy => :get }

Testiamo l’esito:
C:>rake routes

verificando che ci sia il nuovo percorso:

copy_asset GET /assets/:id/copy(.:format) {:controller=>"assets", :action=>"copy"}

La copia è un’operazione utile in molti contesti in quanto permette di effettuare l’inserimento di una nuova risorsa partendo da una già esistente.
Traduciamo in codice e andiamo a creare la nuova operazione nel controller:

#app/controllers/assets_controller.rb
 
# GET /assets/copy
# GET /assets/copy.xml
def copy
  @asset = asset.new
  @asset.attributes = Asset.find(params[:id]).attributes
 
  respond_to do |format|
    format.html { render :new }
    format.xml  { render :xml => @asset }
  end
end

Eventualmente si può creare la view, dipende dall’operazione che si deve gestire. In questo esempio si sfrutta la view dell’operazione new in quanto sono molto simili e richiamano entrambe la :create.

Creare o non creare una nuova azione sulla risorsa è pura filosofia, tralasciando la questione vediamo come indirizzare una nuova create, ci riferiamo ad una collezione di risorse:

#config/routes.rb
map.resources :assets, :member => { :copy => :get }, :collection => { :createcp => :put }

ho utilizzato una collezione perchè in questo caso non serve un riferimento ad una risorsa esistente, se avessi usato :member il path per la generazione dell’uri lo avrebbe richiesto.

se testiamo nuovamente le routes dovrebbe aggiungersi questa nuova:

createcp_asset PUT /assets/createcp(.:format) {:controller=>"assets", :action=>"createcp"}

creiamo quindi la nuova view e il form con la uri della nuova azione specificando il metodo (per precisione ho indicato put ma equivale ad utilizzare un post) :

#app/views/assets/copy.html.erb
 
<% form_for(@asset, :url => createcp_assets_path, :method=>:put do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label t(:name) %><br />
    <%= f.text_field :name %>
  </p>
  <p>
    <%= f.submit t(:create) %>
  </p>
<% end %>

e la nuova azione nel controller:

#app/controllers/assets_controller.rb
 
# POST /assets/createcp
# POST /assets/createcp.xml
def createcp
  @asset = Asset.new(params[:asset])
 
  #do something
 
  respond_to do |format|
    if @asset.save
      flash[:notice] = I18n.t(:created, :model => I18n.t('model.asset'))
      format.html { redirect_to assets_path }
      format.xml  { render :xml => @asset, :status => :created, :location => @asset }
    else
      format.html { render :action => "copy" }
      format.xml  { render :xml => @asset.errors, :status => :unprocessable_entity }
    end
  end
end

Per richiamare la nuova operazione dalla index:

#app/views/assets/index.html.erb
<%= link_to t(:copy), copy_asset_path(asset) %>

Infine ed eventualmente, queste sono le parole utilizzate dall’esempio per l’internazionalizzazione:

create: "Crea"
copy: "Copia"
created: "L'oggetto {{model}} è stato creato correttamente."
name: "Nome"
model:
  asset: "Allegato"
 
Comments Off

Posted in Ruby on Rails