RSS
 

Archive for March 20th, 2010

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!