Your SlideShare is downloading. ×
Show Day
                Test Drive Ruby on Rails
                    com Fabio Akita




       AkitaOnRails.com
•   Mais...
Introdução




                Ruby
• Criado por Yukihiro Matsumoto (Matz)
• Desde 1993
• “Ruby” inspirado por “Perl”, Pyt...
Ruby
• Linguagem “Humana”
• Linguagem Dinâmica
• Princípio da Menor Surpresa
• Quase totalmente orientada a objetos
• Mult...
Mac OS X Leopard
•   Atualizar para versões mais recentes:

    •   sudo gem update --system

    •   sudo gem update

•  ...
Ubuntu 8.04

sudo apt-get install ruby irb ri rdoc ruby1.8-dev libzlib-ruby libyaml-ruby libreadline-ruby
libncurses-ruby ...
Windows
• gem install RubyInline
• FreeImage
 • freeimage.sourceforge.net/download.html
   • copiar FreeImage.dll no c:win...
Ferramentas
• Subversion
 •   Ubuntu - apt-get install subversion

 •   Mac - port install subversion

 •   Windows - http...
Ferramentas


• ImageMagick (processador de imagens)
 •   Ubuntu - apt-get install libmagick9-dev

 •   Mac - port install...
IRB
• Interpreted Ruby
• Shell interativo que executa qualquer
  comando Ruby
[20:42][~]$ irb
>> 1 + 2
=> 3
>> class Foo; ...
“é fácil escrever Fortran
em qualquer linguagem”
        Jim Weinrich




“uma linguagem que não
    afeta seu jeito de
  ...
Convenções
   • NomesDeClasse
   • nomes_de_metodos e nomes_de_variaveis
   • metodos_fazendo_pergunta?
   • metodos_perig...
Estruturas
class MinhaClasse < ClassePai
  def self.metodo_de_classe
    quot;nao é a mesma coisa que estáticoquot;
  end
...
Arrays e Strings
>> lista_longa = [<<FOO, <<BAR, <<BLATZ]
teste1
teste2
FOO
foo1
foo2
BAR
alo1
alo2
BLATZ
=> [quot;teste1n...
Rubismos
     • Parênteses não obrigatórios
     • Argumentos se comportam como Arrays
     • não precisa de “return”
    ...
Strings e Symbols
>>   quot;testequot;.object_id
=>   9417020
>>   quot;testequot;.object_id
=>   9413000
                ...
Tudo é Objeto
>>   1.class            >>    true.class            >>   Class.class
=>   Fixnum             =>    TrueClass...
Tudo é Objeto
                                  Não há “primitivas”
def fabrica(classe)
  classe.new                 >>   ...
Quase tudo são
       Mensagens
• Toda a computação de Ruby acontece
  através de:
 • Ligação de nomes a objetos (a = b)
 ...
Mensagens
      >> 1 + 2
      => 3
                            Envio da mensagem “+”
                                 ao ...
Mensagens
      >> pilha = Pilha.new
      => #<Pilha:0x1028978 @buffer=[]>

      >> pilha.blabla
      => [quot;blablaqu...
Classes Abertas
 class Fixnum
   def par?
     (self % 2) == 0           abrindo a classe padrão
                         ...
Mixins
class Pessoa                     >>   carlos = Pessoa.new(quot;Carlosquot;, 20)
  include Comparable             =>...
Geração de Código
  class Module
    def trace_attr(sym)
      self.module_eval %{                         class Cachorro
...
Geração de Código
     def MyStruct(*keys)
       Class.new do
         attr_accessor *keys
         def initialize(hash)
...
Dynamic Typing

     Static        Dynamic



     Weak           Strong




    Dynamic Typing

  Static/Strong       Jav...
“Strong” Typing
             class   Parent
               def   hello; puts quot;In parentquot;; end
             end
   ...
Duck Typing
• Se anda como um pato
• Se fala como um pato
• Então deve ser um pato
• Compilação com tipos estáticos NÃO
  ...
Chicken Typing
       class Gato;     def fala; quot;miauquot;; end; end
       class Cachorro; def fala; quot;au auquot;;...
Blocos e Fechamentos
      # jeito quot;antigoquot;
      f = nil
      begin
        f = File.open(quot;teste.txtquot;, q...
Construções Funcionais
>> (1..10).class
=> Range

>> (1..10).map { |numero| numero * 2 }
=> [2, 4, 6, 8, 10, 12, 14, 16, 1...
Construções Funcionais
>> (1..50).select { |n| n % 2 == 0 }.map { |n| n * 2 }.inject(0) { |n, t| t += n }
=> 1300




    ...
Tudo Junto
>> tag :div
<div>
</div>

>> tag :img, :src => quot;logo.gifquot;, :alt => quot;imagemquot;
<img alt='imagem' s...
Ruby on Rails
• Criado em 2004 por David Heinemeir
  Hansson
• Extraído da aplicação Basecamp, da 37signals
• “Convention ...
DRY
• A resposta de “por que Ruby?”
• Meta-programação é a chave
• Novamente: fazer o computador trabalhar
  para nós
• DS...
Metodologias Ágeis
• Martin Fowler: metodologias monumentais
  não resolvem o problema
• Metodologias Ágeis: pragmatismo e...
Complementos
• BDD: Behaviour Driven Development
  (RSpec)
• Automatização: Rake, Capistrano,Vlad
• Application Servers: M...
[20:57][~/rails/sandbox/impacta]$ rails _2.1.0_ tarefas
      create
      create app/controllers
      create app/helpers...
Pacotes
                          ActiveResource                     Rails

Aplicação Rails           ActiveSupport       ...
Configurações
                                    •   database.yml

                                        •   configuração...
Ambientes Separados
•   Development

    •   Tudo que é modificado precisa recarregar imediatamente, cache tem que
        ...
Plugins: aceleradores
>> ./script/plugin install git://github.com/technoweenie/restful-authentication.git
removing: /Users...
Rake (Ruby Make)
>> rake db:create:all
db/development.sqlite3 already exists
db/production.sqlite3 already exists
db/test....
Migrations
     >> rake db:migrate

     == 20080629001252 CreateUsuarios: migrating ===================================
 ...
Migrations
         •   Versionamento do Schema do Banco de Dados

         •   O Schema completo fica em db/schema.rb

   ...
Servidor




não esquecer de apagar public/index.html




             M.V.C.
  Model-View-Controller feito direito
requisição HTTP !    Mongrel
   Mongrel     !    routes.rb
   routes.rb   !    Controller
  Controller   !     Action
    ...
Controllers
     # app/controllers/application.rb
     class ApplicationController < ActionController::Base
       helper ...
Views - Templates
       •   app/views/layouts

           •   application.html.erb

           •   controller_name.html.e...
Rotas
# config/routes.rb
ActionController::Routing::Routes.draw do |map|
  map.resources :tarefas

 map.logout '/logout', ...
Active Record
•   “Patterns of Enterprise Application Architecture”, Martin
      Fowler

  •   Uma classe “Model” mapeia para uma tabel...
Interatividade - Console
  >> Usuario.count
  => 0
  >> Tarefa.count
  => 0
  >> admin = Usuario.create(:login => 'admin',...
Interatividade - Console
>> Tarefa.count
=> 1
>> tarefa = Tarefa.first
=> #<Tarefa id: 1, usuario_id: 1, duracao: 2, descr...
Validações
validates_presence_of :firstname, :lastname       # obrigatório

validates_length_of :password,
               ...
Finders
       Tarefa.find 42        # objeto com ID 42
       Tarefa.find [37, 42] # array com os objetos de ID 37 e 42
 ...
Named Scope
 >> Tarefa.hoje
 SELECT * FROM quot;tarefasquot; WHERE (data_inicio between
 '2008-06-29 00:00:00' and '2008-0...
RESTful Rails




             Rotas Restful
# config/routes.rb
ActionController::Routing::Routes.draw do |map|
  map.reso...
CRUD - SQL
      Create                     INSERT

       Read                    SELECT

   Update                      ...
Verbos HTTP
             GET

            POST

             PUT

           DELETE




CRUD - REST - DRY
 GET        /tar...
REST - Named Routes
    /tarefas                            tarefas_path
    /tarefas                            tarefas_p...
REST Templates
# app/controllers/tarefas_controller.rb
class TarefasController < ApplicationController
  # GET /tarefas/ne...
Nested Controllers
# app/controllers/anotacoes_controller.rb           # trocar X
class AnotacoesController < ApplicationC...
Namespaced Routes
>> mv app/helpers/usuarios_helper.rb app/helpers/usuarios_helper.rb.old

>> ./script/generate controller...
Active Scaffold
# app/controllers/admin/usuarios_controller.rb
class Admin::UsuariosController < ApplicationController
  a...
RESTful Rails
            Parte 2: has many through




          many-to-many
                                           ...
many-to-many
 >> admin = Usuario.find_by_login('admin')
 => #<Usuario id: 1, login: quot;adminquot;, name: quot;quot;, ema...
Cenário REST
                                                          assinaturas
class Assinatura < ActiveRecord::Base
 ...
Active Resource

          • Consumidor de recursos REST
          • Todo scaffold Rails, por padrão, é RESTful
          ...
Múltiplas Respostas




    http://localhost:3000/tarefas/1/anotacoes/1.xml




    Múltiplas Respostas
# app/controllers/...
Testes
                   Compilar != Testar




            Tipos de Teste

•   Testes Unitários: Models

•   Testes Func...
Fixtures

•   Carga de dados específicos de testes!

•   test/fixtures/nome_da_tabela.yml

•   Não se preocupar com números ...
tarefas e anotações
# test/fixtures/tarefas.yml                    # test/fixtures/anotacoes.yml
aula:                    ...
Ajustando - Parte 2
  def test_should_show_anotacao
    get :show, :tarefa_id => @tarefa.id, :id => anotacoes(:one).id
   ...
Executando
$ rake test

(in /Users/akitaonrails/rails/sandbox/impacta/tarefas)
/Users/akitaonrails/rails/sandbox/impacta/t...
Assertions




http://topfunky.com/clients/rails/ruby_and_rails_assertions.pdf




                  Assertions
Assertions




  Views
Ajustando Layouts
<!DOCTYPE html PUBLIC quot;-//W3C//DTD XHTML 1.0 Transitional//ENquot;
       quot;http://www.w3.org/TR/...
Ajustando Tarefas
 <!-- _form.html.erb -->
 <p>
   <%= form.label :usuario %><br />
   <%= form.collection_select :usuario...
calendar_helper




                              Ajax
<!-- app/views/tarefas/index.html.erb -->
<h1>Listing tarefas</h1>
...
Ajax
          <!-- app/views/tarefas/_tarefa_row.html.erb -->
          <tr id=quot;<%= dom_id(tarefa) %>quot;>
         ...
RJS - nos bastidores
<%= link_to_remote 'Destroy', :url => tarefa_path(tarefa), :confirm
=> 'Are you sure?', :method => :d...
FormHelper
f.check_box :accepted, { :class => 'eula_check' }, quot;yesquot;, quot;noquot;
f.file_field :file, :class => 'f...
PrototypeHelper
<%= link_to_remote quot;Destroyquot;, :url => person_url(:id => person), :method => :delete %>
<%= observe...
Nome e prefixo do form
          <% form_for(@tarefa) do |f| %>
            <%= f.error_messages %>

            <p>
      ...
HTTP Post
Processing TarefasController#create (for 127.0.0.1 at 2008-06-30 00:01:11) [POST]
  Session ID: BAh...c25

 Para...
Action Mailer
                       Envio simples de e-mail




>> ./script/plugin install git://github.com/caritos/actio...
# app/models/tarefa_mailer.rb
 class TarefaMailer < ActionMailer::Base
   def importante(tarefa, sent_at = Time.now)
     ...
Observações
•   Evitar enviar e-mails nas actions: é muito lento!

•   Estratégia:

    •   Fazer a action gravar numa tab...
Outras Dicas




                          Rotas

$ rm public/index.html

# config/routes.rb                 Redefine a raí...
Time Zone

       • No Rails 2.1, por padrão, todo horário é
          gravado em formato UTC
       • Time.zone recebe um...
Time Zone
                     # config/environment.rb
                     config.time_zone = 'Brasilia'

>> Time.now # h...
Time Zone Rake Tasks
                                          $ rake time:zones:us

                                     ...
Modificando ActiveScaffold
# lib/active_scaffold_extentions.rb
module ActiveScaffold
  module Helpers
    # Helpers that as...
Time Zone Views




             Carregando Zonas
# app/controllers/application.rb
class ApplicationController < ActionCon...
Carregando Zonas




                  Segurança
•   No Rails 2, sessions são gravadas no cookie, mas não
    criptografad...
Segurança
# config/environment.rb
config.action_controller.session = {
  :session_key => '_tarefas_session',
  :secret    ...
O que é?
• Criado por Thomas Enebo e Charles Nutter
• Suportado pela Sun
• Compilador e Interpretador compatível com
  Rub...
Desvantagens

• Tempo de inicialização um pouco mais lento
• Não é capaz de usar extensões feitas em C
• Não é compatível ...
Instalação
• Instalar o JDK mais recente (de preferência
    1.6)
• Baixar http://dist.codehaus.org/jruby/jruby-
    bin-1...
Configuração
   • Alterar database.yml
    <% jdbc = defined?(JRUBY_VERSION) ? 'jdbc' : '' %>
    development:
      adapte...
Impacta - Show Day de Rails
Impacta - Show Day de Rails
Impacta - Show Day de Rails
Impacta - Show Day de Rails
Impacta - Show Day de Rails
Impacta - Show Day de Rails
Impacta - Show Day de Rails
Impacta - Show Day de Rails
Impacta - Show Day de Rails
Impacta - Show Day de Rails
Upcoming SlideShare
Loading in...5
×

Impacta - Show Day de Rails

2,842

Published on

Apostilha do mini-curso ministrado na Impacta sobre Ruby e Rails.

Published in: Technology
2 Comments
1 Like
Statistics
Notes
  • Shoow. =)
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • very good
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total Views
2,842
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
118
Comments
2
Likes
1
Embeds 0
No embeds

No notes for slide

Transcript of "Impacta - Show Day de Rails"

  1. 1. Show Day Test Drive Ruby on Rails com Fabio Akita AkitaOnRails.com • Mais conhecido pelo blog AkitaOnRails.com e pelo Rails Brasil Podcast (podcast.rubyonrails.pro.br) • Escritor do primeiro livro de Rails em português: “Repensando a Web com Rails” • Revisor Técnico da recém-lançada tradução de “Desenvolvimento Web Ágil com Rails” • Trabalhou um ano como Rails Practice Manager para a consultoria americana Surgeworks LLC • Atualmente é Gerente de Produtos Rails na Locaweb
  2. 2. Introdução Ruby • Criado por Yukihiro Matsumoto (Matz) • Desde 1993 • “Ruby” inspirado por “Perl”, Python, Smalltalk • Livro “Programming Ruby” (PickAxe) por Dave Thomas, The Pragmatic Programmer • “MRI” (Matz Ruby Interpretor)
  3. 3. Ruby • Linguagem “Humana” • Linguagem Dinâmica • Princípio da Menor Surpresa • Quase totalmente orientada a objetos • Multi-paradigma (Funcional, Imperativa, Reflexiva, Objetos, etc) • Interpretada (até 1.8) Instalação • Mac (Leopard) - pré-instalado • Linux (Ubuntu) - apt-get • Windows - One-Click Installer
  4. 4. Mac OS X Leopard • Atualizar para versões mais recentes: • sudo gem update --system • sudo gem update • Instalar MacPorts (macports.org) Ubuntu 8.04 • apt-get para instalar ruby • baixar tarball rubygems • gem install rails
  5. 5. Ubuntu 8.04 sudo apt-get install ruby irb ri rdoc ruby1.8-dev libzlib-ruby libyaml-ruby libreadline-ruby libncurses-ruby libcurses-ruby libruby libruby-extras libfcgi-ruby1.8 build-essential libopenssl-ruby libdbm-ruby libdbi-ruby libdbd-sqlite3-ruby sqlite3 libsqlite3-dev libsqlite3- ruby libxml-ruby libxml2-dev wget http://rubyforge.org/frs/download.php/38646/rubygems-1.2.0.tgz tar xvfz rubygems-1.2.0.tgz cd rubygems-1.2.0 sudo ruby setup.rb sudo ln -s /usr/bin/gem1.8 /usr/bin/gem sudo gem install rails sqlite3-ruby mongrel capistrano Windows • Baixar One-Click Installer • gem install rails
  6. 6. Windows • gem install RubyInline • FreeImage • freeimage.sourceforge.net/download.html • copiar FreeImage.dll no c:windows system32 • mmediasys.com/ruby (image_science) RubyGems • gem update --system • gem install rubygems-update • update_rubygems • gem install rails --version=2.0.2 • gem list • gem uninstall rails --version=1.2.6
  7. 7. Ferramentas • Subversion • Ubuntu - apt-get install subversion • Mac - port install subversion • Windows - http://subversion.tigris.org/getting.html#windows • Git • Ubuntu - apt-get install git-core git-svn • Mac - port install git-core +svn • Windows - http://code.google.com/p/msysgit/ Ferramentas • MySQL 5 (banco de dados relacional) • Ubuntu - apt-get install mysql-server mysql-client libdbd-mysql-ruby libmysqlclient15-dev • Mac - port install mysql5 +server • Windows - http://dev.mysql.com/downloads/mysql/5.0.html
  8. 8. Ferramentas • ImageMagick (processador de imagens) • Ubuntu - apt-get install libmagick9-dev • Mac - port install tiff -macosx imagemagick +q8 +gs +wmf • Windows - (rmagick-win32) http://rubyforge.org/projects/rmagick/ Editores • Windows - Scite, UltraEdit, Notepad++ • Ubuntu - gEdit, Emacs,Vi • Mac - TextMate (macromates.com) • Qualquer um server - Aptana, Netbeans
  9. 9. IRB • Interpreted Ruby • Shell interativo que executa qualquer comando Ruby [20:42][~]$ irb >> 1 + 2 => 3 >> class Foo; end => nil >> f = Foo.new => #<Foo:0x11127e4> >> Aprendendo Ruby Adaptado de “10 Things Every Java Programmer Should Know” por Jim Weinrich
  10. 10. “é fácil escrever Fortran em qualquer linguagem” Jim Weinrich “uma linguagem que não afeta seu jeito de programar não vale a pena aprender” Alan Perlis
  11. 11. Convenções • NomesDeClasse • nomes_de_metodos e nomes_de_variaveis • metodos_fazendo_pergunta? • metodos_perigosos! • @variaveis_de_instancia • $variaveis_globais • ALGUMAS_CONSTANTES ou OutrasConstantes Convenções class MinhaClasse < ClassePai def hello_world(nome) return if nome.empty? $WORLD = quot;WORLD quot; monta_frase(nome) class ClassePai @frase.upcase! HELLO = quot;Hello quot; puts @frase end end def monta_frase(nome) @frase = HELLO + $WORLD + nome end end >> obj = MinhaClasse.new => #<MinhaClasse:0x10970e4> >> obj.hello_world quot;Fabioquot; HELLO WORLD FABIO
  12. 12. Estruturas class MinhaClasse < ClassePai def self.metodo_de_classe quot;nao é a mesma coisa que estáticoquot; end def metodo_de_instancia if funciona? ... while true case @teste break if completo? when quot;1quot; end quot;primeira condicaoquot; else when quot;2quot; return quot;não funcionaquot; quot;segunda condicaoquot; end else return unless completo? quot;condicao padraoquot; @teste = quot;1quot; if funciona? end ... end end Arrays e Strings >> a = [1,2,3,4] >> hello = quot;Helloquot; => [1, 2, 3, 4] => quot;Helloquot; >> b = [quot;umquot;, quot;doisquot;, quot;tresquot;] >> a = hello + ' Fulano' => [quot;umquot;, quot;doisquot;, quot;tresquot;] => quot;Hello Fulanoquot; >> c = %w(um dois tres) >> b = quot;#{hello} Fulanoquot; => [quot;umquot;, quot;doisquot;, quot;tresquot;] => quot;Hello Fulanoquot; >> c = <<-EOF multiplas linhas EOF => quot; multiplasn linhasnquot; >>
  13. 13. Arrays e Strings >> lista_longa = [<<FOO, <<BAR, <<BLATZ] teste1 teste2 FOO foo1 foo2 BAR alo1 alo2 BLATZ => [quot;teste1nteste2nquot;, quot;foo1nfoo2nquot;, quot;alo1nalo2nquot;] Apenas curiosidade. Não se costuma fazer isso. Carregar require 'activesupport' @teste = 1.gigabyte / 1.megabyte puts @teste.kilobyte • require • carrega arquivos .rb relativos a onde se está • carrega gems • pode ser passado um caminho (path) absoluto • carrega apenas uma vez (load carrega repetidas vezes) • não há necessidade do nome da classe ser a mesma que o nome do arquivo
  14. 14. Rubismos • Parênteses não obrigatórios • Argumentos se comportam como Arrays • não precisa de “return” • Arrays podem ser representados de diversas formas • Strings podem ser representados de diversas formas “Splat” def foo(*argumentos) arg1, *outros = argumentos [arg1, outros] end >> foo(1, 2, 3, 4) => [1, [2, 3, 4]] Joga uma lista de objetos >> a,b,c = 1,2,3 => [1, 2, 3] em um Array >> *a = 1,2,3,4 => [1, 2, 3, 4]
  15. 15. Strings e Symbols >> quot;testequot;.object_id => 9417020 >> quot;testequot;.object_id => 9413000 •String são mutáveis >> :teste.object_id •Symbols são imutáveis => 306978 >> :teste.object_id => 306978 •Symbols são comumente usados como chaves >> :teste.to_s.object_id => 9399150 >> :teste.to_s.object_id => 9393460 Hashes >> html = { :bgcolor => quot;blackquot;, :body => { :margin => 0, :width => 100 } } >> html[:bgcolor] => quot;blackquot; >> html[:body][:margin] => 0
  16. 16. Tudo é Objeto >> 1.class >> true.class >> Class.class => Fixnum => TrueClass => Class >> quot;aquot;.class >> nil.class >> {}.class => String => NilClass => Hash >> (1.2).class >> Array.class >> [].class => Float => Class => Array Tudo é Objeto >> Array.class >> a.class => Class => Array >> MeuArray = Array >> b.class => Array => Array >> a = Array.new(2) >> b.is_a? MeuArray => [nil, nil] => true >> b = MeuArray.new(3) >> a.is_a? MeuArray => [nil, nil, nil] => true Toda Classe é um objeto, instância de Class
  17. 17. Tudo é Objeto Não há “primitivas” def fabrica(classe) classe.new >> 1 + 2 end => 3 >> 4.* 3 >> 1.+(2) => 12 >> fabrica(Array) => 3 >> 4 * 3 => [] >> 3.-(1) => 12 >> fabrica(String) => 2 => quot;quot; Classe é um Operações aritméticas são objeto métodos de Fixnum Não Objetos >> foo = quot;testequot; • Nomes de variáveis => >> quot;testequot; a = foo não são objetos => quot;testequot; • Variáveis não >> => b = a quot;testequot; costumam ter referência a outros >> foo.object_id => 8807330 objetos >> a.object_id • Blocos (mais => >> 8807330 b.object_id adiante) => 8807330
  18. 18. Quase tudo são Mensagens • Toda a computação de Ruby acontece através de: • Ligação de nomes a objetos (a = b) • Estruturas primitivas de controle (if/ else,while) e operadores (+, -) • Enviando mensagens a objetos Mensagens • obj.metodo() • Java: “chamada” de um método • obj.mensagem • Ruby: envio de “mensagens” • pseudo: obj.enviar(“mensagem”)
  19. 19. Mensagens >> 1 + 2 => 3 Envio da mensagem “+” ao objeto “1” >> 1.+ 2 com parâmetro “2” => 3 >> quot;testequot;.size Envio da mensagem => 5 “size” ao objeto “teste” Mensagens method_missing irá class Pilha interceptar toda mensagem attr_accessor :buffer não definida como método def initialize @buffer = [] end def method_missing(metodo, *args, &bloco) @buffer << metodo.to_s end end
  20. 20. Mensagens >> pilha = Pilha.new => #<Pilha:0x1028978 @buffer=[]> >> pilha.blabla => [quot;blablaquot;] >> pilha.alo => [quot;blablaquot;, quot;aloquot;] >> pilha.hello_world => [quot;blablaquot;, quot;aloquot;, quot;hello_worldquot;] Meta-programação • Ruby: Herança Simples • Módulos: • permite “emular” herança múltipla sem efeitos colaterais • organiza código em namespaces • não pode ser instanciado
  21. 21. Classes Abertas class Fixnum def par? (self % 2) == 0 abrindo a classe padrão Fixnum e acrescentando um end novo método end >> p (1..10).select { |n| n.par? } # => [2, 4, 6, 8, 10] Mixins Fixnum.class_eval do include(Akita::MeuInteiro) end module Akita module MeuInteiro # ou def par? class Fixnum (self % 2) == 0 include Akita::MeuInteiro end end end end # ou Fixnum.send(:include, Akita::MeuInteiro)
  22. 22. Mixins class Pessoa >> carlos = Pessoa.new(quot;Carlosquot;, 20) include Comparable => #<Pessoa:0x103833c> attr_accessor :nome, :idade >> ricardo = Pessoa.new(quot;Ricardoquot;, 30) def initialize(nome, idade) => #<Pessoa:0x1033abc> self.nome = nome self.idade = idade >> carlos > ricardo end => false def <=>(outro) >> carlos == ricardo self.idade <=> outro.idade => false end >> carlos < ricardo end => true Métodos Singleton class Cachorro def rover.fale end puts quot;Rover Vermelhoquot; end rover = Cachorro.new fido = Cachorro.new rover.instance_eval do def fale puts quot;Rover vermelhoquot; end >> rover.fale end Rover Vermelho >> fido.fale NoMethodError: undefined method `fale' for #<Cachorro:0x10179e8> from (irb):90
  23. 23. Geração de Código class Module def trace_attr(sym) self.module_eval %{ class Cachorro def #{sym} trace_attr :nome printf quot;Acessando %s com valor %snquot;, def initialize(string) quot;#{sym}quot;, @#{sym}.inspect @nome = string end end } end end end >> Cachorro.new(quot;Fidoquot;).nome # => Acessando nome com valorquot;Fidoquot; Acessando nome com valor quot;Fidoquot; Geração de Código class Person def initialize(options = {}) @name = options[:name] @address = options[:address] @likes = options[:likes] end def name; @name; end def name=(value); @name = value; end def address; @address; end def address=(value); @address = value; end def likes; @likes; end def likes=(value); @likes = value; end end Tarefa chata! (repetitiva)
  24. 24. Geração de Código def MyStruct(*keys) Class.new do attr_accessor *keys def initialize(hash) hash.each do |key, value| instance_variable_set(quot;@#{key}quot;, value) end end end end Filosofia “Don’t Repeat Yourself” Geração de Código Person = MyStruct :name, :address, :likes dave = Person.new(:name => quot;davequot;, :address => quot;TXquot;, :likes => quot;Stiltonquot;) chad = Person.new(:name => quot;chadquot;, :likes => quot;Jazzquot;) chad.address = quot;COquot; >> puts quot;O nome do Dave e #{dave.name}quot; O nome do Dave e dave => nil >> puts quot;Chad mora em #{chad.address}quot; Chad mora em CO
  25. 25. Dynamic Typing Static Dynamic Weak Strong Dynamic Typing Static/Strong Java Dynamic/Weak Javascript Dynamic/”Strong” Ruby
  26. 26. “Strong” Typing class Parent def hello; puts quot;In parentquot;; end end class Child < Parent def hello; puts quot;In childquot; ; end end >> c = Child.new => #<Child:0x1061fac> >> c.hello In child “Strong Typing” class Child # remove quot;helloquot; de Child, mas ainda chama do Parent remove_method :hello end >> c.hello In parent class Child undef_method :hello # evita chamar inclusive das classes-pai end >> c.hello NoMethodError: undefined method `hello' for #<Child:0x1061fac> from (irb):79
  27. 27. Duck Typing • Se anda como um pato • Se fala como um pato • Então deve ser um pato • Compilação com tipos estáticos NÃO garante “código sem erro” • Cobertura de testes garante “código quase sem erro”. Compilação não exclui testes. Duck Typing class Gato def fala; quot;miauquot;; end end class Cachorro def fala; quot;au auquot;; end end for animal in [Gato.new, Cachorro.new] puts animal.class.name + quot; fala quot; + animal.fala end # Gato fala miau # Cachorro fala au au
  28. 28. Chicken Typing class Gato; def fala; quot;miauquot;; end; end class Cachorro; def fala; quot;au auquot;; end; end class Passaro; def canta; quot;piuquot;; end; end def canil(animal) return quot;Animal mudoquot; unless animal.respond_to?(:fala) return quot;Nao e cachorroquot; unless animal.is_a?(Cachorro) puts animal.fala end >> canil(Gato.new) => quot;Nao e cachorroquot; >> canil(Passaro.new) => quot;Animal mudoquot; >> canil(Cachorro.new) => au au Evite coisas assim! Blocos e Fechamentos lista = [1, 2, 3, 4, 5] for numero in lista puts numero * 2 loop tradicional end lista.each do |numero| puts numero * 2 loop com bloco end # mesma coisa: lista.each { |numero| puts numero * 2 }
  29. 29. Blocos e Fechamentos # jeito quot;antigoquot; f = nil begin f = File.open(quot;teste.txtquot;, quot;rquot;) texto = f.read ensure f.close end # com fechamentos File.open(quot;teste.txtquot;, quot;rquot;) do |f| texto = f.read end Blocos e Fechamentos # com yield def foo >> foo { puts quot;aloquot; } yield => alo end # como seria em quot;runtimequot; # como objeto def foo def foo(&block) puts quot;aloquot; block.call end end
  30. 30. Construções Funcionais >> (1..10).class => Range >> (1..10).map { |numero| numero * 2 } => [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] >> (1..10).inject(0) { |numero, total| total += numero } => 55 >> (1..10).select { |numero| numero % 2 == 0 } => [2, 4, 6, 8, 10] Construções Funcionais “Dada uma coleção de números de 1 a 50, qual a soma de todos os números pares, cada qual multiplicado por 2?” lista = [] total = 0 numero = 1 for numero in lista while numero < 51 if numero % 2 == 0 lista << numero total += (numero * 2) numero += 1 end end end => 1300
  31. 31. Construções Funcionais >> (1..50).select { |n| n % 2 == 0 }.map { |n| n * 2 }.inject(0) { |n, t| t += n } => 1300 (1..50).select do |n| n % 2 == 0 (1..50).select { |n| end.map do |n| n % 2 == 0 }.map { |n| n * 2 n * 2 }.inject(0) { |n, t| end.inject(0) do |n, t| t += n } t += n end Tudo Junto def tag(nome, options = {}) if options.empty? puts quot;<#{nome}>quot; else attr = options.map { |k,v| quot;#{k}='#{v}' quot; } puts quot;<#{nome} #{attr}>quot; end puts yield if block_given? puts quot;</#{nome}>quot; end
  32. 32. Tudo Junto >> tag :div <div> </div> >> tag :img, :src => quot;logo.gifquot;, :alt => quot;imagemquot; <img alt='imagem' src='logo.gif' > </img> >> tag(:p, :style => quot;color: yellowquot;) { quot;Hello Worldquot; } <p style='color: yellow' > Hello World </p> Ruby on Rails “Domain Specific Language for the Web”
  33. 33. Ruby on Rails • Criado em 2004 por David Heinemeir Hansson • Extraído da aplicação Basecamp, da 37signals • “Convention over Configuration” • DRY: “Don’t Repeat Yourself ” • YAGNI: “You Ain’t Gonna Need It” • Metodologias Ágeis Convention over Configuration • Eliminar XMLs de configuração • As pessoas gostam de escolhas mas não gostam necessariamente de escolher • Escolhas padrão são “Limitações” • “Limitações” nos tornam mais produtivos • O computador tem que trabalhar por nós e não nós trabalharmos para o computador
  34. 34. DRY • A resposta de “por que Ruby?” • Meta-programação é a chave • Novamente: fazer o computador trabalhar para nós • DSL: “Domain Specific Languages” • RoR é uma DSL para a Web YAGNI • Se tentar suportar 100% de tudo acaba-se tendo 0% de coisa alguma • Pareto em Software: “80% do tempo é consumido resolvendo 20% dos problemas” • RoR tenta resolver 80% dos problemas da forma correta
  35. 35. Metodologias Ágeis • Martin Fowler: metodologias monumentais não resolvem o problema • Metodologias Ágeis: pragmatismo e qualidade de software • Integração Contínua, Cobertura de Testes, Automatização de tarefas, etc. • TDD: “Test-Driven Development” Tecnologias • Plugins: extendem as capacidades do Rails • Gems: bibliotecas Ruby, versionadas • RubyForge • Agile Web Development (plugins • Github
  36. 36. Complementos • BDD: Behaviour Driven Development (RSpec) • Automatização: Rake, Capistrano,Vlad • Application Servers: Mongrel, Thin, Ebb, Phusion Passenger • Web Servers: Apache 2, NginX, Lightspeed • Bancos de Dados: MySQL, PostgreSQL, SQLite3, Oracle, SQL Server, etc Iniciando um projeto Ruby on Rails Obs.: aprenda a apreciar a linha de comando!
  37. 37. [20:57][~/rails/sandbox/impacta]$ rails _2.1.0_ tarefas create create app/controllers create app/helpers opcional create app/models create app/views/layouts create config/environments create config/initializers ... create doc/README_FOR_APP create log/server.log create log/production.log create log/development.log create log/test.log Estrutura Padrão • app - estrutura MVC • config - configurações • public - recursos estáticos (imagens, CSS, javascript, etc) • script - ferramentas • test - suíte test/unit • vendor - plugins, gems, etc
  38. 38. Pacotes ActiveResource Rails Aplicação Rails ActiveSupport ActionController Mongrel ActionPack ActiveRecord Ruby ActiveWS ActionView ActionMailer Algumas convenções • Nomes no Plural • Quando se fala de uma coleção de dados (ex. nome de uma tabela no banco) • Nomes do Singular • Quando se fala de uma única entidade (ex. uma linha no banco • Rails usa Chaves Primárias Surrogadas (id inteiro) • Foreign Key é o nome da tabela associada no singular com “_id” (ex. usuario_id)
  39. 39. Configurações • database.yml • configuração de banco de dados • environment.rb • configurações globais • environments • configurações por ambiente • initializers • organização de configurações • routes.rb Ambientes Separados # SQLite version 3.x # gem install sqlite3-ruby (not necessary on OS X Leopard) development: adapter: sqlite3 database: db/development.sqlite3 timeout: 5000 # Warning: The database defined as quot;testquot; will be erased and # re-generated from your development database when you run quot;rakequot;. # Do not set this db to the same as development or production. test: adapter: sqlite3 database: db/test.sqlite3 timeout: 5000 production: adapter: sqlite3 database: db/production.sqlite3 timeout: 5000
  40. 40. Ambientes Separados • Development • Tudo que é modificado precisa recarregar imediatamente, cache tem que ser desativado • Permitido banco de dados com sujeira • Test • Todos os testes precisam ser repetitíveis em qualquer ambiente, por isso precisa de um banco de dados separado • O banco precisa ser limpo a cada teste. Cada teste precisa ser isolado. • Production • Otimizado para performance, as classes só precisam carregar uma única vez • Os sistemas de caching precisam ficar ligados YAML • “Yet Another Markup Language” • “YAML Ain’t a Markup Language” • Formato humanamente legível de serialização de estruturas de dados • Muito mais leve e prático que XML • JSON é quase um subset de YAML • Identação é muito importante!
  41. 41. Plugins: aceleradores >> ./script/plugin install git://github.com/technoweenie/restful-authentication.git removing: /Users/akitaonrails/rails/sandbox/impacta/tarefas/vendor/plugins/restful- authentication/.git Initialized empty Git repository in tarefas/vendor/plugins/restful-authentication/.git/ remote: Counting objects: 409, done. remote: Compressing objects: 100% (259/259), done. remote: Total 409 (delta 147), reused 353 (delta 115) Receiving objects: 100% (409/409), 354.52 KiB | 124 KiB/s, done. Resolving deltas: 100% (147/147), done. ... >> ./script/plugin install git://github.com/tapajos/brazilian-rails.git >> rake brazilianrails:inflector:portuguese:enable >> ./script/plugin install git://github.com/lightningdb/activescaffold.git >> ./script/generate authenticated Usuario Sessao Rake (Ruby Make) >> rake -T (in /Users/akitaonrails/rails/sandbox/impacta/tarefas) /Users/akitaonrails/rails/sandbox/impacta/tarefas/vendor/plugins/brazilian-rails/tasks rake brazilianrails:inflector:portuguese:check # Checks if Brazilian Por... rake brazilianrails:inflector:portuguese:disable # Disable Brazilian Portu... rake brazilianrails:inflector:portuguese:enable # Enable Brazilian Portug... rake db:abort_if_pending_migrations # Raises an error if ther... rake db:charset # Retrieves the charset f... rake db:collation # Retrieves the collation... rake db:create # Create the database def... ... rake tmp:pids:clear # Clears all files in tmp... rake tmp:sessions:clear # Clears all files in tmp... rake tmp:sockets:clear # Clears all files in tmp... Executa tarefas automatizadas, como limpeza de logs, gerenciamento do banco de dados, execução dos testes, etc.
  42. 42. Rake (Ruby Make) >> rake db:create:all db/development.sqlite3 already exists db/production.sqlite3 already exists db/test.sqlite3 already exists >> rake db:migrate == 20080629001252 CreateUsuarios: migrating =================================== -- create_table(quot;usuariosquot;, {:force=>true}) -> 0.0042s -- add_index(:usuarios, :login, {:unique=>true}) -> 0.0034s == 20080629001252 CreateUsuarios: migrated (0.0085s) ========================== >> rake Started ............. Finished in 0.340325 seconds. 13 tests, 26 assertions, 0 failures, 0 errors Started .............. Finished in 0.306186 seconds. 14 tests, 26 assertions, 0 failures, 0 errors Migrations >> ./script/generate scaffold Tarefa usuario:references duracao:integer descricao:string data_inicio:datetime exists app/models/ ... create db/migrate/20080629003332_create_tarefas.rb class CreateTarefas < ActiveRecord::Migration def self.up create_table :tarefas do |t| t.references :usuario t.integer :duracao t.string :descricao t.datetime :data_inicio t.timestamps end end def self.down drop_table :tarefas end end
  43. 43. Migrations >> rake db:migrate == 20080629001252 CreateUsuarios: migrating =================================== -- create_table(quot;usuariosquot;, {:force=>true}) -> 0.0047s -- add_index(:usuarios, :login, {:unique=>true}) -> 0.0039s == 20080629001252 CreateUsuarios: migrated (0.0092s) ========================== == 20080629003332 CreateTarefas: migrating ==================================== -- create_table(:tarefas) -> 0.0039s == 20080629003332 CreateTarefas: migrated (0.0044s) =========================== Migrations CREATE TABLE quot;schema_migrationsquot; (quot;versionquot; varchar(255) NOT NULL) CREATE UNIQUE INDEX quot;unique_schema_migrationsquot; ON quot;schema_migrationsquot; (quot;versionquot;) Migrating to CreateUsuarios (20080629001252) SELECT version FROM schema_migrations CREATE TABLE quot;usuariosquot; (quot;idquot; INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, quot;loginquot; varchar(40) DEFAULT NULL NULL, quot;namequot; varchar(100) DEFAULT '' NULL, quot;emailquot; varchar(100) DEFAULT NULL NULL, quot;crypted_passwordquot; varchar(40) DEFAULT NULL NULL, quot;saltquot; varchar(40) DEFAULT NULL NULL, quot;created_atquot; datetime DEFAULT NULL NULL, quot;updated_atquot; datetime DEFAULT NULL NULL, quot;remember_tokenquot; varchar(40) DEFAULT NULL NULL, quot;remember_token_expires_atquot; datetime DEFAULT NULL NULL) CREATE UNIQUE INDEX quot;index_usuarios_on_loginquot; ON quot;usuariosquot; (quot;loginquot;) INSERT INTO schema_migrations (version) VALUES ('20080629001252') Migrating to CreateTarefas (20080629003332) SELECT version FROM schema_migrations CREATE TABLE quot;tarefasquot; (quot;idquot; INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, quot;usuario_idquot; integer DEFAULT NULL NULL, quot;duracaoquot; integer DEFAULT NULL NULL, quot;descricaoquot; varchar(255) DEFAULT NULL NULL, quot;data_inicioquot; datetime DEFAULT NULL NULL, quot;created_atquot; datetime DEFAULT NULL NULL, quot;updated_atquot; datetime DEFAULT NULL NULL) INSERT INTO schema_migrations (version) VALUES ('20080629003332') SELECT version FROM schema_migrations
  44. 44. Migrations • Versionamento do Schema do Banco de Dados • O Schema completo fica em db/schema.rb • Possibilita pseudo-”rollback” e, efetivamente, mais controle entre versões • Garante que os diferentes ambientes sempre estejam consistentes • Evita conflitos em times com mais de 2 desenvolvedores • Aumenta muito a produtividade Servidor >> ./script/server => Booting Mongrel (use 'script/server webrick' to force WEBrick) => Rails 2.1.0 application starting on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server ** Starting Mongrel listening at 0.0.0.0:3000 ** Starting Rails with development environment... ** Rails loaded. ** Loading any Rails specific GemPlugins ** Signals ready. TERM => stop. USR2 => restart. INT => stop (no restart). ** Rails signals registered. HUP => reload (without restart). It might not work well. ** Mongrel 1.1.5 available at 0.0.0.0:3000 ** Use CTRL-C to stop. Se não houver Mongrel instalado, ele sobe Webrick (não recomendado)
  45. 45. Servidor não esquecer de apagar public/index.html M.V.C. Model-View-Controller feito direito
  46. 46. requisição HTTP ! Mongrel Mongrel ! routes.rb routes.rb ! Controller Controller ! Action Action ! Model Action ! View Action ! resposta HTTP
  47. 47. Controllers # app/controllers/application.rb class ApplicationController < ActionController::Base helper :all # include all helpers, all the time protect_from_forgery end # app/controllers/tarefas_controller.rb class TarefasController < ApplicationController # GET /tarefas # GET /tarefas.xml def index @tarefas = Tarefa.find(:all) respond_to do |format| format.html # index.html.erb format.xml { render :xml => @tarefas } end end end Views - Templates <!DOCTYPE html PUBLIC quot;-//W3C//DTD XHTML 1.0 Transitional//ENquot; quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtdquot;> <html xmlns=quot;http://www.w3.org/1999/xhtmlquot; xml:lang=quot;enquot; lang=quot;enquot;> <head> <meta http-equiv=quot;content-typequot; content=quot;text/html;charset=UTF-8quot; /> <title>Tarefas: <%= controller.action_name %></title> <%= stylesheet_link_tag 'scaffold' %> </head> <body> <p style=quot;color: greenquot;><%= flash[:notice] %></p> <%= yield %> </body> </html>
  48. 48. Views - Templates • app/views/layouts • application.html.erb • controller_name.html.erb • app/views/controller_name • action_name.html.erb • action_name.mime_type.engine • mime-type: html, rss, atom, xml, pdf, etc • engine: erb, builder, etc Models # app/models/usuario.rb require 'digest/sha1' class Usuario < ActiveRecord::Base include Authentication include Authentication::ByPassword include Authentication::ByCookieToken validates_presence_of :login validates_length_of :login, :within => 3..40 validates_uniqueness_of :login, :case_sensitive => false validates_format_of :login, :with => RE_LOGIN_OK, :message => MSG_LOGIN_BAD ... end
  49. 49. Rotas # config/routes.rb ActionController::Routing::Routes.draw do |map| map.resources :tarefas map.logout '/logout', :controller => 'sessoes', :action => 'destroy' map.login '/login', :controller => 'sessoes', :action => 'new' map.register '/register', :controller => 'usuarios', :action => 'create' map.signup '/signup', :controller => 'usuarios', :action => 'new' map.resources :usuarios map.resource :sessao # Install the default routes as the lowest priority. map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format' jeito “antigo” (1.2) end Rotas - Antigo • http://www.dominio.com/tarefas/show/123 • map.connect ':controller/:action/:id' • tarefas_controller.rb ! TarefasController • def show ... end • params[:id] ! 123
  50. 50. Active Record
  51. 51. • “Patterns of Enterprise Application Architecture”, Martin Fowler • Uma classe “Model” mapeia para uma tabela • Uma instância da classe “Model” mapeia para uma linha na tabela • Toda lógica de negócio é implementada no “Model” • Suporte para Herança Simples, Polimorfismo, Associações Associações Simples # app/models/tarefa.rb class Tarefa < ActiveRecord::Base belongs_to :usuario # uma tarefa pertence a um usuario end # app/models/usuario.rb class Usuario < ActiveRecord::Base has_many :tarefas # um usuario tem varias tarefas ... end tarefas usuarios id: integer id: integer usuario_id: integer
  52. 52. Interatividade - Console >> Usuario.count => 0 >> Tarefa.count => 0 >> admin = Usuario.create(:login => 'admin', :password => 'admin', :password_confirmation => 'admin', :email => 'admin@admin.com') => #<Usuario id: nil, login: quot;adminquot;, name: quot;quot;, email: quot;admin@admin.comquot;, crypted_password: nil, salt: nil, created_at: nil, updated_at: nil, remember_token: nil, remember_token_expires_at: nil> >> Usuario.count => 0 >> admin.errors.full_messages => [quot;Password deve ter no minimo 6 caractere(s)quot;] Interatividade - Console >> admin = Usuario.create(:login => 'admin', :password => 'admin123', :password_confirmation => 'admin123', :email => 'admin@admin.com') => #<Usuario id: 1, login: quot;adminquot;, name: quot;quot;, email: quot;admin@admin.comquot;, crypted_password: quot;e66...abdquot;, salt: quot;731...b96quot;, created_at: quot;2008-06-29 20:21:10quot;, updated_at: quot;2008-06-29 20:21:10quot;, remember_token: nil, remember_token_expires_at: nil> >> admin.tarefas => [] >> admin.tarefas.create(:descricao => quot;Criando demo de Railsquot;, :duracao => 2, :data_inicio => Time.now) => #<Tarefa id: 1, usuario_id: 1, duracao: 2, descricao: quot;Criando demo de Railsquot;, data_inicio: quot;2008-06-29 20:21:40quot;, created_at: quot;2008-06-29 20:21:40quot;, updated_at: quot;2008-06-29 20:21:40quot;>
  53. 53. Interatividade - Console >> Tarefa.count => 1 >> tarefa = Tarefa.first => #<Tarefa id: 1, usuario_id: 1, duracao: 2, descricao: quot;Criando demo de Railsquot;, data_inicio: quot;2008-06-29 20:21:40quot;, created_at: quot;2008-06-29 20:21:40quot;, updated_at: quot;2008-06-29 20:21:40quot;> >> tarefa.usuario => #<Usuario id: 1, login: quot;adminquot;, name: quot;quot;, email: quot;admin@admin.comquot;, crypted_password: quot;e66...abdquot;, salt: quot;731...b96quot;, created_at: quot;2008-06-29 20:21:10quot;, updated_at: quot;2008-06-29 20:21:10quot;, remember_token: nil, remember_token_expires_at: nil> Validações # app/models/usuario.rb class Usuario < ActiveRecord::Base ... validates_presence_of :login validates_length_of :login, :within => 3..40 validates_uniqueness_of :login, :case_sensitive => false validates_format_of :login, :with => RE_LOGIN_OK, :message => MSG_LOGIN_BAD validates_format_of :name, :with => RE_NAME_OK, :message => MSG_NAME_BAD, :allow_nil => true validates_length_of :name, :maximum => 100 validates_presence_of :email validates_length_of :email, :within => 6..100 #r@a.wk validates_uniqueness_of :email, :case_sensitive => false validates_format_of :email, :with => RE_EMAIL_OK, :message => MSG_EMAIL_BAD ... end
  54. 54. Validações validates_presence_of :firstname, :lastname # obrigatório validates_length_of :password, :minimum => 8 # mais de 8 caracteres :maximum => 16 # menos que 16 caracteres :in => 8..16 # entre 8 e 16 caracteres :too_short => 'muito curto' :too_long => 'muito longo' validates_acceptance_of :eula # Precisa aceitar este checkbox :accept => 'Y' # padrão: 1 (ideal para checkbox) validates_confirmation_of :password # os campos password e password_confirmation precisam ser iguais validates_uniqueness_of :user_name # user_name tem que ser único :scope => 'account_id' # Condição: # account_id = user.account_id Validações validates_format_of :email # campo deve bater com a regex :with => /^(+)@((?:[-a-z0-9]+.)+[a-z]{2,})$/i validates_numericality_of :value # campos value é numérico :only_integer => true :allow_nil => true validates_inclusion_of :gender, # gender é m ou f (enumeração) :in => %w( m, f ) validates_exclusion_of :age # campo age não pode estar :in => 13..19 # entre 13 e 19 anos validates_associated :relation # valida que o objeto ‘relation’ associado também é válido
  55. 55. Finders Tarefa.find 42 # objeto com ID 42 Tarefa.find [37, 42] # array com os objetos de ID 37 e 42 Tarefa.find :all Tarefa.find :last Tarefa.find :first, :conditions => [ quot;data_inicio < ?quot;, Time.now ] # encontra o primeiro objeto que obedece à! condição Tarefa.all # idêntico à! Tarefa.find(:all) Tarefa.first # idêntico à! Tarefa.find(:first) Tarefa.last # idêntico à! Tarefa.find(:last) :order => 'data_inicio DESC'# ordenação :offset => 20 # começa a partir da linha 20 :limit => 10 # retorna apenas 10 linhas :group => 'name' # agrupamento :joins => 'LEFT JOIN ...' # espaço para joins, como LEFT JOIN (raro) :include => [:account, :friends] # LEFT OUTER JOIN com esses models # dependendo das condições podem ser # 2 queries :include => { :groups => { :members=> { :favorites } } } :select => [:name, :adress] # em vez do padrão SELECT * FROM :readonly => true # objetos não podem ser modificados Named Scopes # app/models/tarefa.rb class Tarefa < ActiveRecord::Base ... named_scope :curtas, :conditions => ['duracao < ?', 2] named_scope :medias, :conditions => ['duracao between ? and ?', 2, 6] named_scope :longas, :conditions => ['duracao > ?', 6] named_scope :hoje, lambda { { :conditions => ['data_inicio between ? and ?', Time.now.beginning_of_day, Time.now.end_of_day ] } } end
  56. 56. Named Scope >> Tarefa.hoje SELECT * FROM quot;tarefasquot; WHERE (data_inicio between '2008-06-29 00:00:00' and '2008-06-29 23:59:59') >> Tarefa.curtas SELECT * FROM quot;tarefasquot; WHERE (duracao < 2) >> Tarefa.medias.hoje SELECT * FROM quot;tarefasquot; WHERE ((data_inicio between '2008-06-29 00:00:00' and '2008-06-29 23:59:59') AND (duracao between 2 and 6)) Condições de SQL geradas de maneira automática Muito Mais • Associações complexas (many-to-many, self-referencial, etc) • Associações Polimórficas • Colunas compostas (composed_of) • extensões acts_as (acts_as_tree, acts_as_nested_set, etc) • Callbacks (filtros) • Transactions (não suporta two-phased commits), Lockings • Single Table Inheritance (uma tabela para múltiplos models em herança) • Finders dinâmicos, Named Scopes
  57. 57. RESTful Rails Rotas Restful # config/routes.rb ActionController::Routing::Routes.draw do |map| map.resources :tarefas end # app/views/tarefas/index.html.erb ... <td><%= link_to 'Edit', edit_tarefa_path(tarefa) %></td> # script/console >> app.edit_tarefa_path(123) => quot;/tarefas/123/editquot;
  58. 58. CRUD - SQL Create INSERT Read SELECT Update UPDATE Destroy DELETE CRUD - Antigo GET /tarefas index GET /tarefas/new new GET /tarefas/edit/1 edit GET /tarefas/show/1 show POST /tarefas/create create POST /tarefas/update update POST /tarefas/destroy destroy
  59. 59. Verbos HTTP GET POST PUT DELETE CRUD - REST - DRY GET /tarefas index POST /tarefas create GET /tarefas/new new GET /tarefas/1 show PUT /tarefas/1 update DELETE /tarefas/1 destroy GET /tarefas/1/edit edit
  60. 60. REST - Named Routes /tarefas tarefas_path /tarefas tarefas_path /tarefas/new new_tarefa_path /tarefas/1 tarefa_path(1) /tarefas/1 tarefa_path(1) /tarefas/1 tarefa_path(1) /tarefas/1/edit edit_tarefa_path(1) REST Controller # app/controllers/tarefas_controller.rb class TarefasController < ApplicationController # GET /tarefas def index # GET /tarefas/1 def show # GET /tarefas/new def new # GET /tarefas/1/edit def edit # POST /tarefas def create # PUT /tarefas/1 def update # DELETE /tarefas/1 def destroy end
  61. 61. REST Templates # app/controllers/tarefas_controller.rb class TarefasController < ApplicationController # GET /tarefas/new def new @tarefa = Tarefa.new respond_to do |format| format.html # new.html.erb format.xml { render :xml => @tarefa } end end <!-- /tarefas/1 --> ... <form action=quot;/tarefas/3quot; class=quot;edit_tarefaquot; end id=quot;edit_tarefa_3quot; method=quot;postquot;> <input name=quot;_methodquot; type=quot;hiddenquot; # app/views/tarefas/new.html.erb value=quot;putquot; /> <% form_for(@tarefa) do |f| %> ... ... <p> <p> <input id=quot;tarefa_submitquot; name=quot;commitquot; <%= f.submit quot;Createquot; %> type=quot;submitquot; value=quot;Createquot; /> </p> </p> <% end %> </form> Nested Controllers >> ./script/generate scaffold Anotacao tarefa:references anotacao:text >> rake db:migrate # app/models/tarefa.rb class Tarefa < ActiveRecord::Base belongs_to :usuario has_many :anotacoes end # app/models/anotacao.rb class Anotacao < ActiveRecord::Base belongs_to :tarefa end
  62. 62. Nested Controllers # app/controllers/anotacoes_controller.rb # trocar X class AnotacoesController < ApplicationController # por Y before_filter :load_tarefa ... Anotacao.find private @tarefa.anotacoes.find def load_tarefa Anotacao.new @tarefa = Tarefa.find(params[:tarefa_id]) @tarefa.anotacoes.build end end redirect_to(@anotacao) redirect_to([@tarefa, @anotacao]) # config/routes.rb :location => @anotacao ActionController::Routing::Routes.draw do |map| :location => [@tarefa, @anotacao] map.resources :tarefas, :has_many => :anotacoes ... anotacoes_url end tarefa_anotacoes_url(@tarefa) Nested Views # trocar X # por Y em todos os arquivos app/views/anotacoes/*.html.erb form_for(@anotacao) <!-- apagar de new.html.erb form_for([@tarefa, @anotacao]) e edit.html.erb --> <p> link_to 'Show', @anotacao <%= f.label :tarefa %><br /> link_to 'Show', [@tarefa, @anotacao] <%= f.text_field :tarefa %> </p> anotacoes_path tarefa_anotacoes_path(@tarefa) <!-- acrescentar ao final de edit_anotacao_path(@tarefa) index.html.erb --> edit_tarefa_anotacao_path(@tarefa, @anotacao) <%= link_to 'Back to Tarefa', tarefa_path(@tarefa) %> anotacoes_path tarefa_anotacoes_path(@tarefa) <!-- acrescentar ao final de tarefas/show.html.erb --> new_anotacao_path <%= link_to 'Anotações', new_tarefa_anotacao_path(@tarefa) tarefa_anotacoes_path(@tarefa) %>
  63. 63. Namespaced Routes >> mv app/helpers/usuarios_helper.rb app/helpers/usuarios_helper.rb.old >> ./script/generate controller Admin::Usuarios create app/controllers/admin create app/helpers/admin create app/views/admin/usuarios create test/functional/admin create app/controllers/admin/usuarios_controller.rb create test/functional/admin/usuarios_controller_test.rb create app/helpers/admin/usuarios_helper.rb >> mv app/helpers/usuarios_helper.rb.old app/helpers/usuarios_helper.rb # config/routes.rb ActionController::Routing::Routes.draw do |map| map.namespace :admin do |admin| admin.resources :usuarios, :active_scaffold => true end end Active Scaffold apague tudo em app/views/layouts e crie apenas este application.html.erb <!DOCTYPE html PUBLIC quot;-//W3C//DTD XHTML 1.0 Transitional//ENquot; quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtdquot;> <html xmlns=quot;http://www.w3.org/1999/xhtmlquot; xml:lang=quot;enquot; lang=quot;enquot;> <head> <meta http-equiv=quot;content-typequot; content=quot;text/html;charset=UTF-8quot; /> <title><%= controller.action_name %></title> <%= stylesheet_link_tag 'scaffold' %> <%= javascript_include_tag :defaults %> <%= active_scaffold_includes %> </head> <body> <p style=quot;color: greenquot;><%= flash[:notice] %></p> <%= yield %> </body> </html>
  64. 64. Active Scaffold # app/controllers/admin/usuarios_controller.rb class Admin::UsuariosController < ApplicationController active_scaffold :usuario do |config| config.columns = [:login, :email, :password, :password_confirmation ] config.list.columns.exclude [ :password, :password_confirmation ] config.update.columns.exclude [ :login] end end Active Scaffold
  65. 65. RESTful Rails Parte 2: has many through many-to-many anotacoes # app/models/anotacao.rb class Anotacao < ActiveRecord::Base id: int belongs_to :tarefa end tarefa_id: int # app/models/tarefa.rb class Tarefa < ActiveRecord::Base belongs_to :usuario tarefas has_many :anotacoes ... id: int end usuario_id: int # app/models/usuario.rb class Usuario < ActiveRecord::Base ... has_many :tarefas usuarios has_many :anotacoes, :through => :tarefas ... end id: int
  66. 66. many-to-many >> admin = Usuario.find_by_login('admin') => #<Usuario id: 1, login: quot;adminquot;, name: quot;quot;, email: quot;admin@admin.comquot;, crypted_password: quot;e66e...abdquot;, salt: quot;731f...b96quot;, created_at: quot;2008-06-29 20:21:10quot;, updated_at: quot;2008-06-29 20:21:10quot;, remember_token: nil, remember_token_expires_at: nil> SELECT * FROM quot;usuariosquot; WHERE (quot;usuariosquot;.quot;loginquot; = 'admin') LIMIT 1 >> admin.tarefas => [#<Tarefa id: 1, usuario_id: 1, duracao: 2, descricao: quot;Criando demo de Railsquot;, data_inicio: quot;2008-06-29 20:21:40quot;, created_at: quot;2008-06-29 20:21:40quot;, updated_at: quot;2008-06-29 20:21:40quot;>] SELECT * FROM quot;tarefasquot; WHERE (quot;tarefasquot;.usuario_id = 1) >> admin.anotacoes => [#<Anotacao id: 1, tarefa_id: 1, anotacao: quot;testequot;, created_at: quot;2008-06-29 21:29:52quot;, updated_at: quot;2008-06-29 21:29:52quot;>] SELECT quot;anotacoesquot;.* FROM quot;anotacoesquot; INNER JOIN tarefas ON anotacoes.tarefa_id = tarefas.id WHERE ((quot;tarefasquot;.usuario_id = 1)) Cenário Antigo # tabela 'revistas' clientes_revistas class Revista < ActiveRecord::Base # tabela 'clientes_revistas' has_and_belongs_to_many :clientes cliente_id: int end revista_id: int # tabela 'clientes' class Cliente < ActiveRecord::Base clientes # tabela 'clientes_revistas' has_and_belongs_to_many :revistas id: int end class RevistasController < ApplicationController revistas def add_cliente() end def remove_cliente() end id: int end class ClientesController < ApplicationController Qual dos dois controllers def add_revista() end def remove_revista() end está certo? end A revista controla o cliente ou o cliente controla a revista?
  67. 67. Cenário REST assinaturas class Assinatura < ActiveRecord::Base belongs_to :revista cliente_id: int belongs_to :cliente end revista_id: int class Revista < ActiveRecord::Base has_many :assinaturas clientes has_many :clientes, :through => :assinaturas end id: int class Cliente < ActiveRecord::Base has_many :assinaturas revistas has_many :revistas, :through => :assinaturas end id: int class AssinaturasController < ApplicationController def create() end def destroy() end end Tabelas many-to-many, normalmente podem ser um recurso próprio RESTful Rails Parte 3: ActiveResource
  68. 68. Active Resource • Consumidor de recursos REST • Todo scaffold Rails, por padrão, é RESTful • Para o exemplo: mantenha script/server rodando Active Resource >> require 'activesupport' => true >> require 'activeresource' => [] Via console IRB class Tarefa < ActiveResource::Base self.site = quot;http://localhost:3000quot; end >> t = Tarefa.find :first => #<Tarefa:0x1842c94 @prefix_options={}, @attributes={quot;updated_atquot;=>Sun Jun 29 20:21:40 UTC 2008, quot;idquot;=>1, quot;usuario_idquot;=>1, quot;data_inicioquot;=>Sun Jun 29 20:21:40 UTC 2008, quot;descricaoquot;=>quot;Criando demo de Railsquot;, quot;duracaoquot;=>2, quot;created_atquot;=>Sun Jun 29 20:21:40 UTC 2008} >> t = Tarefa.new(:descricao => 'Testando REST', :duracao => 1, :data_inicio => Time.now) => #<Tarefa:0x183484c @prefix_options={}, @attributes={quot;data_inicioquot;=>Sun Jun 29 19:00:57 -0300 2008, quot;descricaoquot;=>quot;Testando RESTquot;, quot;duracaoquot;=>1} >> t.save => true
  69. 69. Múltiplas Respostas http://localhost:3000/tarefas/1/anotacoes/1.xml Múltiplas Respostas # app/controllers/anotacoes_controller.rb class AnotacoesController < ApplicationController ... # GET /anotacoes/1 # GET /anotacoes/1.xml def show @anotacao = @tarefa.anotacoes.find(params[:id]) respond_to do |format| format.html # show.html.erb format.xml { render :xml => @anotacao } end end ... end
  70. 70. Testes Compilar != Testar Tipos de Teste • Testes Unitários: Models • Testes Funcionais: Controllers • Testes Integrados: Cenários de Aceitação
  71. 71. Fixtures • Carga de dados específicos de testes! • test/fixtures/nome_da_tabela.yml • Não se preocupar com números de primary keys • Associações podem se referenciar diretamente através do nome de cada entidade • Dar nomes significativos a cada entidade de teste restful-authentication quentin: login: quentin email: quentin@example.com salt: 356a192b7913b04c54574d18c28d46e6395428ab # SHA1('0') crypted_password: c38f11c55af4680780bba9c9f7dd9fa18898b939 # 'monkey' created_at: <%= 5.days.ago.to_s :db %> remember_token_expires_at: <%= 1.days.from_now.to_s %> remember_token: 77de68daecd823babbb58edb1c8e14d7106e83bb aaron: login: aaron email: aaron@example.com salt: da4b9237bacccdf19c0760cab7aec4a8359010b0 # SHA1('1') crypted_password: 028670d59d8eff84294668802470c8c8034c51b5 # 'monkey' created_at: <%= 1.days.ago.to_s :db %> remember_token_expires_at: remember_token: old_password_holder: login: old_password_holder email: salty_dog@example.com salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test created_at: <%= 1.days.ago.to_s :db %>
  72. 72. tarefas e anotações # test/fixtures/tarefas.yml # test/fixtures/anotacoes.yml aula: one: usuario: quentin tarefa: aula duracao: 1 anotacao: Aula de Rails descricao: Dando Aula data_inicio: 2008-06-28 21:33:32 two: tarefa: aula academia: anotacao: Precisa corrigir prova usuario: aaron duracao: 1 three: descricao: Exercitando tarefa: academia data_inicio: 2008-06-28 21:33:32 anotacao: Mudando rotina Ajustando - Parte 1 require File.dirname(__FILE__) + '/../test_helper' class AnotacoesControllerTest < ActionController::TestCase fixtures :tarefas, :usuarios, :anotacoes def setup @tarefa = tarefas(:aula) declarando quais end fixtures carregar def test_should_get_index get :index, :tarefa_id => @tarefa.id assert_response :success assert_not_nil assigns(:anotacoes) end def test_should_get_new adicionando get :new, :tarefa_id => @tarefa.id assert_response :success a chave :tarefa_id end ao hash params def test_should_create_anotacao assert_difference('Anotacao.count') do post :create, :tarefa_id => @tarefa.id, :anotacao => { :anotacao => quot;testequot; } end assert_redirected_to tarefa_anotacao_path(@tarefa, assigns(:anotacao)) end
  73. 73. Ajustando - Parte 2 def test_should_show_anotacao get :show, :tarefa_id => @tarefa.id, :id => anotacoes(:one).id assert_response :success end def test_should_get_edit get :edit, :tarefa_id => @tarefa.id, :id => anotacoes(:one).id assert_response :success end hash params def test_should_update_anotacao put :update, :tarefa_id => @tarefa.id, :id => anotacoes(:one).id, :anotacao => { :anotacao => quot;testequot;} assert_redirected_to tarefa_anotacao_path(@tarefa, assigns(:anotacao)) end def test_should_destroy_anotacao assert_difference('Anotacao.count', -1) do delete :destroy, :tarefa_id => @tarefa.id, :id => anotacoes(:one).id end assert_redirected_to tarefa_anotacoes_path(@tarefa) ajustando end end rotas nomeadas Ajustando - Parte 3 # test/functional/tarefas_controller.rb require File.dirname(__FILE__) + '/../test_helper' class TarefasControllerTest < ActionController::TestCase fixtures :tarefas, :usuarios ... get :show, :id => tarefas(:aula).id ... end mudar de “one” para “aula” conforme foi modificado em tarefas.yml
  74. 74. Executando $ rake test (in /Users/akitaonrails/rails/sandbox/impacta/tarefas) /Users/akitaonrails/rails/sandbox/impacta/tarefas/vendor/plugins/brazilian-rails/tasks /opt/local/bin/ruby -Ilib:test quot;/opt/local/lib/ruby/gems/1.8/gems/rake-0.8.1/lib/rake/ rake_test_loader.rbquot; quot;test/unit/anotacao_test.rbquot; quot;test/unit/tarefa_test.rbquot; quot;test/unit/ usuario_test.rbquot; Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.8.1/lib/rake/rake_test_loader Started ............... Finished in 0.370074 seconds. 15 tests, 28 assertions, 0 failures, 0 errors /opt/local/bin/ruby -Ilib:test quot;/opt/local/lib/ruby/gems/1.8/gems/rake-0.8.1/lib/rake/ rake_test_loader.rbquot; quot;test/functional/admin/usuarios_controller_test.rbquot; quot;test/functional/ anotacoes_controller_test.rbquot; quot;test/functional/sessoes_controller_test.rbquot; quot;test/functional/ tarefas_controller_test.rbquot; quot;test/functional/usuarios_controller_test.rbquot; Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.8.1/lib/rake/rake_test_loader Started ............................. Finished in 0.832019 seconds. 29 tests, 53 assertions, 0 failures, 0 errors /opt/local/bin/ruby -Ilib:test quot;/opt/local/lib/ruby/gems/1.8/gems/rake-0.8.1/lib/rake/ rake_test_loader.rbquot; Estatísticas $ rake stats (in /Users/akitaonrails/rails/sandbox/impacta/tarefas) +----------------------+-------+-------+---------+---------+-----+-------+ | Name | Lines | LOC | Classes | Methods | M/C | LOC/M | +----------------------+-------+-------+---------+---------+-----+-------+ | Controllers | 280 | 192 | 6 | 21 | 3 | 7 | | Helpers | 104 | 44 | 0 | 4 | 0 | 9 | | Models | 54 | 30 | 3 | 1 | 0 | 28 | | Libraries | 198 | 96 | 0 | 21 | 0 | 2 | | Integration tests | 0 | 0 | 0 | 0 | 0 | 0 | | Functional tests | 260 | 204 | 7 | 37 | 5 | 3 | | Unit tests | 119 | 98 | 3 | 16 | 5 | 4 | +----------------------+-------+-------+---------+---------+-----+-------+ | Total | 1015 | 664 | 19 | 100 | 5 | 4 | +----------------------+-------+-------+---------+---------+-----+-------+ Code LOC: 362 Test LOC: 302 Code to Test Ratio: 1:0.8 Esta taxa é muito baixa. Busque 1:3.0 pelo menos!
  75. 75. Assertions http://topfunky.com/clients/rails/ruby_and_rails_assertions.pdf Assertions
  76. 76. Assertions Views
  77. 77. Ajustando Layouts <!DOCTYPE html PUBLIC quot;-//W3C//DTD XHTML 1.0 Transitional//ENquot; quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtdquot;> <html xmlns=quot;http://www.w3.org/1999/xhtmlquot; xml:lang=quot;enquot; lang=quot;enquot;> <head> <meta http-equiv=quot;content-typequot; content=quot;text/html;charset=UTF-8quot; /> <title>Admin <%= controller.action_name %></title> <%= stylesheet_link_tag 'scaffold' %> <%= javascript_include_tag :defaults %> <%= active_scaffold_includes %> </head> <body> <p style=quot;color: greenquot;><%= flash[:notice] %></p> <%= yield %> </body> </html> Ajustando Tarefas <!-- index.html.erb --> ... <td><%=h tarefa.usuario.login if tarefa.usuario %></td> <td><%=h tarefa.duracao %></td> <td><%=h tarefa.descricao %></td> <td><%=h tarefa.data_inicio.to_s(:short) %></td> ... <!-- new.html.erb --> <h1>New tarefa</h1> <% form_for(@tarefa) do |f| %> <%= f.error_messages %> <%= render :partial => f, :locals => { :submit_text => 'Create' } %> <% end %> <%= link_to 'Back', tarefas_path %> <!-- edit.html.erb --> <h1>Editing tarefa</h1> <% form_for(@tarefa) do |f| %> <%= f.error_messages %> <%= render :partial => f, :locals => { :submit_text => 'Update' } %> <% end %> <%= link_to 'Show', @tarefa %> | <%= link_to 'Back', tarefas_path %>
  78. 78. Ajustando Tarefas <!-- _form.html.erb --> <p> <%= form.label :usuario %><br /> <%= form.collection_select :usuario_id, Usuario.all, 'id', 'login' %> </p> <p> <%= form.label :duracao %><br /> <!-- show.html.erb --> <%= form.text_field :duracao %> <p> </p> <b>Usuario:</b> <p> <%=h @tarefa.usuario.login %> <%= form.label :descricao %><br /> </p> <%= form.text_field :descricao %> ... </p> <p> <%= form.label :data_inicio %><br /> <%= form.datetime_select :data_inicio %> </p> <p> <%= form.submit submit_text %> </p> calendar_helper >> ./script/plugin install http://calendardateselect.googlecode.com/svn/tags/ calendar_date_select <!-- app/views/layouts/application.html.erb --> <%= stylesheet_link_tag 'scaffold' %> <%= stylesheet_link_tag 'calendar_date_select/default' %> <%= javascript_include_tag :defaults %> <%= javascript_include_tag 'calendar_date_select/calendar_date_select' %> <!-- app/views/tarefas/_form.html.erb --> ... <p> <%= form.label :data_inicio %><br /> <%= form.calendar_date_select :data_inicio %> </p> Sempre que instalar um plugin: reiniciar o servidor
  79. 79. calendar_helper Ajax <!-- app/views/tarefas/index.html.erb --> <h1>Listing tarefas</h1> <table id='tarefas_table'> <tr> <th>Usuario</th> <th>Duracao</th> <th>Descricao</th> <th>Data inicio</th> </tr> <% for tarefa in @tarefas %> <%= render :partial => 'tarefa_row', :locals => { :tarefa => tarefa } %> <% end %> </table> <br /> <h1>Nova Tarefa</h1> <% remote_form_for(Tarefa.new) do |f| %> <%= render :partial => f, :locals => { :submit_text => 'Create' } %> <% end %>
  80. 80. Ajax <!-- app/views/tarefas/_tarefa_row.html.erb --> <tr id=quot;<%= dom_id(tarefa) %>quot;> <td><%=h tarefa.usuario.login if tarefa.usuario %></td> <td><%=h tarefa.duracao %></td> <td><%=h tarefa.descricao %></td> <td><%=h tarefa.data_inicio.to_s(:short) %></td> <td><%= link_to 'Show', tarefa %></td> <td><%= link_to 'Edit', edit_tarefa_path(tarefa) %></td> <td><%= link_to_remote 'Destroy', :url => tarefa_path(tarefa), :confirm => 'Are you sure?', :method => :delete %></td> </tr> # app/views/tarefas/create.js.rjs page.insert_html :bottom, 'tarefas_table', :partial => 'tarefa_row', :locals => { :tarefa => @tarefa } page.visual_effect :highlight, dom_id(@tarefa) # app/views/tarefas/destroy.js.rjs page.visual_effect :drop_out, dom_id(@tarefa) # app/controllers/tarefas_controller.rb class TarefasController < ApplicationController ... def create @tarefa = Tarefa.new(params[:tarefa]) respond_to do |format| if @tarefa.save flash[:notice] = 'Tarefa was successfully created.' format.html { redirect_to(@tarefa) } format.js # create.js.rjs format.xml { render :xml => @tarefa, :status => :created, :location => @tarefa } else format.html { render :action => quot;newquot; } format.xml { render :xml => @tarefa.errors, :status => :unprocessable_entity } end end end def destroy @tarefa = Tarefa.find(params[:id]) @tarefa.destroy respond_to do |format| format.html { redirect_to(tarefas_url) } format.js # destroy.js.rjs format.xml { head :ok } end end end
  81. 81. RJS - nos bastidores <%= link_to_remote 'Destroy', :url => tarefa_path(tarefa), :confirm => 'Are you sure?', :method => :delete %> <a href=quot;#quot; onclick=quot;if (confirm('Are you sure?')) { new Ajax.Request('/tarefas/10', {asynchronous:true, evalScripts:true, method:'delete', parameters:'authenticity_token=' + encodeURIComponent('966...364')}); }; return false;quot;>Destroy</a> <% remote_form_for(Tarefa.new) do |f| %> ... <% end %> <form action=quot;/tarefasquot; class=quot;new_tarefaquot; id=quot;new_tarefaquot; method=quot;postquot; onsubmit=quot;new Ajax.Request('/tarefas', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;quot;> ... </form>
  82. 82. FormHelper f.check_box :accepted, { :class => 'eula_check' }, quot;yesquot;, quot;noquot; f.file_field :file, :class => 'file_input' f.hidden_field :token f.label :title f.password_field :pin, :size => 20 f.radio_button :category, 'rails' f.text_area :obs, :cols => 20, :rows => 40 f.text_field :name, :size => 20, :class => 'code_input' f.select :person_id, Person.all.map { |p| [ p.name, p.id ] }, { :include_blank => true } f.collection_select :author_id, Author.all, :id, :name, { :prompt => true } f.country_select :country f.time_zone_select :time_zone, TimeZone.us_zones, :default => quot;Pacific Time (US & Canada)quot; JavaScriptGenerator page[:element] page << quot;alert('teste')quot; page.alert(quot;testequot;) page.assign 'record_count', 33 page.call 'alert', 'My message!' page.delay(20) do page.visual_effect :fade, 'notice' end page.redirect_to(:controller => 'account', :action => 'signup') page.show 'person_6', 'person_13', 'person_223' page.hide 'person_29', 'person_9', 'person_0' page.toggle 'person_14', 'person_12', 'person_23' page.insert_html :after, 'list', '<li>Last item</li>' page.remove 'person_23', 'person_9', 'person_2' page.replace 'person-45', :partial => 'person', :object => @person page.replace_html 'person-45', :partial => 'person', :object => @person page.select('p.welcome b').first.hide page.visual_effect :highlight, 'person_12'
  83. 83. PrototypeHelper <%= link_to_remote quot;Destroyquot;, :url => person_url(:id => person), :method => :delete %> <%= observe_field :suggest, :url => search_tarefas_path, :frequency => 0.25, :update => :suggest, :with => 'q' %> <%= periodically_call_remote(:url => { :action => 'invoice', :id => customer.id }, :update => { :success => quot;invoicequot;, :failure => quot;errorquot; } %> <% remote_form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => quot;edit_postquot;, :id => quot;edit_post_45quot; } do |f| %> ... <% end %> <%= submit_to_remote 'update_btn', 'Update', :url => { :action => 'update' }, :update => { :success => quot;succeedquot;, :failure => quot;failquot; } %> <%= draggable_element(quot;my_imagequot;, :revert => true) %> <%= drop_receiving_element(quot;my_cartquot;, :url => { :controller => quot;cartquot;, :action => quot;addquot; }) %> <%= sortable_element(quot;my_listquot;, :url => { :action => quot;orderquot; }) %> Entendendo Forms
  84. 84. Nome e prefixo do form <% form_for(@tarefa) do |f| %> <%= f.error_messages %> <p> <%= form.label :usuario %><br /> <%= form.collection_select :usuario_id, Usuario.all, 'id', 'login' %> </p> <p> <%= form.label :duracao %><br /> <%= form.text_field :duracao %> </p> <p> <%= form.label :descricao %><br /> <%= form.text_field :descricao %> </p> <p> <%= form.label :data_inicio %><br /> <%= form.calendar_date_select :data_inicio %> </p> <p> <%= form.submit 'Create' %> </p> <% end %> <form action=quot;/tarefasquot; class=quot;new_tarefaquot; id=quot;new_tarefaquot; method=quot;postquot;> <div style=quot;margin:0;padding:0quot;> <input name=quot;authenticity_tokenquot; type=quot;hiddenquot; value=quot;966974fa19db6efaf0b3f456c2823a7f46181364quot; /> </div> <p> <label for=quot;tarefa_usuarioquot;>Usuario</label><br /> <select id=quot;tarefa_usuario_idquot; name=quot;tarefa[usuario_id]quot;> <option value=quot;1quot;>admin</option> <option value=quot;2quot;>akita</option> </select> </p> <p> <label for=quot;tarefa_duracaoquot;>Duracao</label><br /> <input id=quot;tarefa_duracaoquot; name=quot;tarefa[duracao]quot; size=quot;30quot; type=quot;textquot; /> </p> <p> <label for=quot;tarefa_descricaoquot;>Descricao</label><br /> <input id=quot;tarefa_descricaoquot; name=quot;tarefa[descricao]quot; size=quot;30quot; type=quot;textquot; /> </p> <p> <label for=quot;tarefa_data_inicioquot;>Data inicio</label><br /> <input id=quot;tarefa_data_inicioquot; name=quot;tarefa[data_inicio]quot; size=quot;30quot; type=quot;textquot; /> <img alt=quot;Calendarquot; onclick=quot;new CalendarDateSelect( $(this).previous(), {time:true, year_range:10} );quot; src=quot;/images/calendar_date_select/calendar.gif?1214788838quot; style=quot;border:0px; cursor:pointer;quot; /> </p> <p> <input id=quot;tarefa_submitquot; name=quot;commitquot; type=quot;submitquot; value=quot;Createquot; /> </p> </form>
  85. 85. HTTP Post Processing TarefasController#create (for 127.0.0.1 at 2008-06-30 00:01:11) [POST] Session ID: BAh...c25 Parameters: {quot;commitquot;=>quot;Createquot;, quot;authenticity_tokenquot;=>quot;966974fa19db6efaf0b3f456c2823a7f46181364quot;, quot;actionquot;=>quot;createquot;, quot;controllerquot;=>quot;tarefasquot;, quot;tarefaquot;=>{quot;usuario_idquot;=>quot;1quot;, quot;data_inicioquot;=>quot;June 28, 2008 12:00 AMquot;, quot;descricaoquot;=>quot;Teste de Criaçãoquot;, quot;duracaoquot;=>quot;1quot;}} Tarefa Create (0.000453) INSERT INTO quot;tarefasquot; (quot;updated_atquot;, quot;usuario_idquot;, quot;data_inicioquot;, quot;descricaoquot;, quot;duracaoquot;, quot;created_atquot;) VALUES('2008-06-30 03:01:11', 1, '2008-06-28 00:00:00', 'Teste de Criação', 1, '2008-06-30 03:01:11') Redirected to http://localhost:3000/tarefas/12 Completed in 0.01710 (58 reqs/sec) | DB: 0.00045 (2%) | 302 Found [http://localhost/tarefas] Valores do pacote HTTP serão desserializados no hash ‘params’ Note também que :action e :controller foram extraídos de POST /tarefas Params # app/controllers/tarefas_controller.rb class TarefasController < ApplicationController def create # pegando o hash params, na chave :tarefa @tarefa = Tarefa.new(params[:tarefa]) ... end end # equivalente a: @tarefa = Tarefa.new { :duracao=>quot;1quot;, :usuario_id=>quot;1quot;, :data_inicio=>quot;June 28, 2008 12:00 AMquot;, :descricao=>quot;Teste de Criaçãoquot; } # o hash params completo vem assim: params = {:action=>quot;createquot;, :authenticity_token=>quot;966...364quot;, :controller=>quot;tarefasquot;, :commit=>quot;Createquot;, :tarefa => {quot;usuario_idquot;=>quot;1quot;, quot;duracaoquot;=>quot;1quot;, quot;data_inicioquot;=>quot;June 28, 2008 12:00 AMquot;, quot;descricaoquot;=>quot;Teste de Criaçãoquot; } }
  86. 86. Action Mailer Envio simples de e-mail >> ./script/plugin install git://github.com/caritos/action_mailer_tls.git >> ./script/generate mailer TarefaMailer importante # config/environments/development.rb ... Para enviar config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { via Gmail :address => quot;smtp.gmail.comquot;, :port => 587, :authentication => :plain, :user_name => quot;fabioakitaquot;, :password => quot;----------quot; } config.action_mailer.perform_deliveries = true # app/controllers/tarefas_controller.rb class TarefasController < ApplicationController ... Ativa o def create @tarefa = Tarefa.new(params[:tarefa]) envio respond_to do |format| if @tarefa.save TarefaMailer.deliver_importante(@tarefa) if @tarefa.descricao =~ /^!/ ... end
  87. 87. # app/models/tarefa_mailer.rb class TarefaMailer < ActionMailer::Base def importante(tarefa, sent_at = Time.now) recipients tarefa.usuario.email subject 'Tarefa Importante' from 'fabioakita@gmail.com' sent_on sent_at body :tarefa => tarefa end # se tiver ActiveScaffold instalado def self.uses_active_scaffold? false Template ERB end end <!-- app/views/tarefa_mailer/importante.erb --> Notificação de Tarefa Importante Descrição: <%= @tarefa.descricao %> Duração: <%= @tarefa.duracao %> hs Início: <%= @tarefa.data_inicio.to_s(:short) %> # test/unit/tarefa_mailer_test.rb require 'test_helper' class TarefaMailerTest < ActionMailer::TestCase tests TarefaMailer fixtures :usuarios, :tarefas def test_importante @expected.subject = 'Tarefa Importante' @expected.from = 'fabioakita@gmail.com' @expected.to = tarefas(:aula).usuario.email @expected.body = read_fixture('importante') @expected.date = Time.now assert_equal @expected.encoded, TarefaMailer.create_importante(tarefas(:aula)).encoded end end # test/fixtures/tarefa_mailer/importante Notificação de Tarefa Importante Descrição: Dando Aula Duração: 1 hs Início: 28 Jun 21:33
  88. 88. Observações • Evitar enviar e-mails nas actions: é muito lento! • Estratégia: • Fazer a action gravar numa tabela que serve de “fila” com o status de “pendente” • Ter um script externo que de tempos em tempos puxa os pendentes, envia os e-mails e remarca como ‘enviado’ • Exemplo: usar ./script/runner para isso aliado a um cronjob. O Runner roda um script Ruby dentro do ambiente Rails, com acesso a tudo.
  89. 89. Outras Dicas Rotas $ rm public/index.html # config/routes.rb Redefine a raíz # adicionar: da aplicação map.root :tarefas # remover: Apps REST não map.connect ':controller/:action/:id' precisam disso map.connect ':controller/:action/:id.:format'
  90. 90. Time Zone • No Rails 2.1, por padrão, todo horário é gravado em formato UTC • Time.zone recebe um String, como definido em TimeZone.all • ActiveRecord converte os time zones de forma transparente Time Zone >> Time.now # horário atual, note zona GMT -03:00 => Mon Jun 30 13:04:09 -0300 2008 >> tarefa = Tarefa.first # pegando a primeira tarefa do banco => #<Tarefa id: 1 ...> >> anotacao = tarefa.anotacoes.create(:anotacao => quot;Teste com horaquot;) # criando anotacao => #<Anotacao id: 4 ...> >> anotacao.reload # recarregando anotacao do banco, apenas para garantir => #<Anotacao id: 4 ...> >> anotacao.created_at # data gravada no banco => Seg, 30 Jun 2008 16:04:31 UTC 00:00 # config/environment.rb config.time_zone = 'UTC'
  91. 91. Time Zone # config/environment.rb config.time_zone = 'Brasilia' >> Time.now # horario atual, local em GMT -3 => Mon Jun 30 13:07:42 -0300 2008 >> tarefa = Tarefa.first # novamente pega uma tarefa => #<Tarefa id: 1, ...> >> anotacao = tarefa.anotacoes.create(:anotacao => quot;Outro teste com horaquot;) # cria anotacao => #<Anotacao id: 5, tarefa_id: 1, ...> >> anotacao.created_at # horario local, automaticamente convertido de acordo com config.time_zone => Seg, 30 Jun 2008 13:08:00 ART -03:00 >> anotacao.created_at_before_type_cast # horario em UTC, direto do banco de dados => Mon Jun 30 16:08:00 UTC 2008 Time Zone Rake Tasks $ rake -D time rake time:zones:all Displays names of all time zones recognized by the Rails TimeZone class, grouped by offset. Results can be filtered with optional OFFSET parameter, e.g., OFFSET=-6 rake time:zones:local Displays names of time zones recognized by the Rails TimeZone class with the same offset as the system local time rake time:zones:us Displays names of US time zones recognized by the Rails TimeZone class, grouped by offset. Results can be filtered with optional OFFSET parameter, e.g., OFFSET=-6
  92. 92. Time Zone Rake Tasks $ rake time:zones:us * UTC -10:00 * Hawaii $ rake time:zones:local * UTC -09:00 * * UTC -03:00 * Alaska Brasilia * UTC -08:00 * Buenos Aires Pacific Time (US & Canada) Georgetown * UTC -07:00 * Greenland Arizona Mountain Time (US & Canada) * UTC -06:00 * Central Time (US & Canada) * UTC -05:00 * Eastern Time (US & Canada) Indiana (East) Adicionando Time Zone $ ./script/generate migration AdicionaTimeZoneUsuario # db/migrate/20080630182836_adiciona_time_zone_usuario.rb class AdicionaTimeZoneUsuario < ActiveRecord::Migration def self.up add_column :usuarios, :time_zone, :string, :null => false, :default => 'Brasilia' end def self.down remove_column :usuarios, :time_zone end end $ rake db:migrate <!-- app/views/usuarios/new.html.erb --> <p><label for=quot;time_zonequot;>Time Zone</label><br/> <%= f.time_zone_select :time_zone, TimeZone.all %></p>
  93. 93. Modificando ActiveScaffold # lib/active_scaffold_extentions.rb module ActiveScaffold module Helpers # Helpers that assist with the rendering of a Form Column module FormColumns def active_scaffold_input_time_zone(column, options) time_zone_select :record, column.name, TimeZone.all end end end end # config/environment.rb ... require 'active_scaffold_extentions' # app/controllers/admin/usuarios_controller.rb class Admin::UsuariosController < ApplicationController before_filter :login_required active_scaffold :usuario do |config| config.columns = [:login, :email, :time_zone, :created_at, :password, :password_confirmation ] config.columns[:time_zone].form_ui = [:time_zone] config.list.columns.exclude [ :password, :password_confirmation ] Views config.create.columns.exclude [ :created_at] config.update.columns.exclude [ :login, :created_at] end end
  94. 94. Time Zone Views Carregando Zonas # app/controllers/application.rb class ApplicationController < ActionController::Base helper :all # include all helpers, all the time include AuthenticatedSystem protect_from_forgery Retirar include before_filter :load_time_zone AuthenticatedSystem de private usuarios e sessoes def load_time_zone Time.zone = current_usuario.time_zone if logged_in? end end # app/controllers/admin/usuarios_controller.rb class Admin::UsuariosController < ApplicationController before_filter :login_required ... end
  95. 95. Carregando Zonas Segurança • No Rails 2, sessions são gravadas no cookie, mas não criptografadas. • Best Practice: não grave nada importante na session • Todo formulário HTML é protegido contra Cross Site Request Forgery (CSRF) • Toda operação ActiveRecord é sanitizada para evitar SQL Injection • Best Practice: não crie SQL manualmente concatenando strings originadas em formulários
  96. 96. Segurança # config/environment.rb config.action_controller.session = { :session_key => '_tarefas_session', :secret => 'aaa02677597285ff58fcdb2eafaf5a82a1c10572334acb5297ec94a0f6b1cf48fbcb8f54 6c00c08769a6f99695dd967a2d3fea33d6217548fcc4fd64e783caa6' } # app/controllers/application.rb class ApplicationController < ActionController::Base ... protect_from_forgery ... end <form action=quot;/tarefasquot; class=quot;new_tarefaquot; id=quot;new_tarefaquot; method=quot;postquot;> <div style=quot;margin:0;padding:0quot;> <input name=quot;authenticity_tokenquot; type=quot;hiddenquot; value=quot;5a2176fd77601a497a9d7ae8184d06b60df0ae28quot; /> </div> ... </form> JRuby
  97. 97. O que é? • Criado por Thomas Enebo e Charles Nutter • Suportado pela Sun • Compilador e Interpretador compatível com Ruby MRI 1.8.6 • Capaz de gerar bytecode Java a partir de código Ruby • Roda sobre JDK 1.4 até 1.6 Vantagens • Performance muitas vezes maior do que Ruby MRI atual • Capacidade de tirar proveito do HotSpot para otimização em runtime • Utilização de threads-nativas • Suporte a Unicode compatível com Java • Capaz de utilizar qualquer biblioteca Java
  98. 98. Desvantagens • Tempo de inicialização um pouco mais lento • Não é capaz de usar extensões feitas em C • Não é compatível com todas as gems e plugins disponíveis JRuby on Rails • Warble: capaz de encapsular uma aplicação Rails em um arquivo WAR comum • ActiveRecord-JDBC utiliza os drivers JDBC normais de Java • jetty_rails: desenvolvimento ágil mesmo em ambiente Java • Capaz de compartilhar objetos HTTPSession com aplicações Java no mesmo container
  99. 99. Instalação • Instalar o JDK mais recente (de preferência 1.6) • Baixar http://dist.codehaus.org/jruby/jruby- bin-1.1.2.zip • Descompactar e colocar o diretório bin/ no seu PATH (depende do seu sistema operacional) Instalação • jruby -v • ruby 1.8.6 (2008-05-28 rev 6586) [x86_64-jruby1.1.2] • jruby -S gem install rails • jruby -S gem install jruby-openssl • jruby -S gem install activerecord-jdbc-adapter • jruby -S gem install activerecord-jdbcmysql-adapter • jruby -S gem install activerecord-jdbcsqlite3-adapter • jruby -S gem install jdbc-mysql • jruby -S gem install jdbc-sqlite3 • jruby -S gem install jetty-rails
  100. 100. Configuração • Alterar database.yml <% jdbc = defined?(JRUBY_VERSION) ? 'jdbc' : '' %> development: adapter: <%= jdbc %>mysql database: tarefas username: root password: root • jruby -S jetty-rails Configuração • jruby -S gem install warbler • cd tarefas • jruby -S warble config # config/warble.rb Warbler::Config.new do |config| config.dirs = %w(app config lib log vendor tmp) config.gems = [quot;activerecord-jdbc-adapterquot;, quot;jruby-opensslquot;, quot;activerecord-jdbcmysql-adapterquot;, quot;jdbc-mysqlquot;] config.gem_dependencies = true config.webxml.rails.env = 'production' end

×