SlideShare a Scribd company logo
1 of 110
Download to read offline
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
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)
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
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
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
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
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
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
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
“é 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
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
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;
                 >>
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
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]
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
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
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
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”)
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
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
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)
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
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)
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
Dynamic Typing

     Static        Dynamic



     Weak           Strong




    Dynamic Typing

  Static/Strong       Java


 Dynamic/Weak      Javascript


Dynamic/”Strong”     Ruby
“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
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
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 }
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
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
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
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”
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
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
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
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!
[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
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)
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
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!
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.
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
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
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)
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
    Action     !      Model
    Action     !      View
    Action     ! resposta HTTP
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>
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
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
Active Record
•   “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
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;>
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
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
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
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
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;
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
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
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
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
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)
                                                     %>
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>
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
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
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?
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
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
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
Testes
                   Compilar != Testar




            Tipos de Teste

•   Testes Unitários: Models

•   Testes Funcionais:
    Controllers

•   Testes Integrados:
    Cenários de Aceitação
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 %>
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
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
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!
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/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 %>
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
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 %>
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
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>
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'
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
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>
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; } }
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
# 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
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.
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'
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'
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
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>
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
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
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
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
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
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
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
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
Show Day: Test Drive Ruby on Rails with Fabio Akita
Show Day: Test Drive Ruby on Rails with Fabio Akita
Show Day: Test Drive Ruby on Rails with Fabio Akita
Show Day: Test Drive Ruby on Rails with Fabio Akita
Show Day: Test Drive Ruby on Rails with Fabio Akita
Show Day: Test Drive Ruby on Rails with Fabio Akita
Show Day: Test Drive Ruby on Rails with Fabio Akita
Show Day: Test Drive Ruby on Rails with Fabio Akita
Show Day: Test Drive Ruby on Rails with Fabio Akita
Show Day: Test Drive Ruby on Rails with Fabio Akita

More Related Content

What's hot

Perl.Hacks.On.Vim
Perl.Hacks.On.VimPerl.Hacks.On.Vim
Perl.Hacks.On.VimLin Yo-An
 
Beware: Sharp Tools
Beware: Sharp ToolsBeware: Sharp Tools
Beware: Sharp Toolschrismdp
 
Ruby 程式語言綜覽簡介
Ruby 程式語言綜覽簡介Ruby 程式語言綜覽簡介
Ruby 程式語言綜覽簡介Wen-Tien Chang
 
What I Love About Ruby
What I Love About RubyWhat I Love About Ruby
What I Love About RubyKeith Bennett
 
Ruby 入門 第一次就上手
Ruby 入門 第一次就上手Ruby 入門 第一次就上手
Ruby 入門 第一次就上手Wen-Tien Chang
 
Supercharging reflective libraries with InvokeDynamic
Supercharging reflective libraries with InvokeDynamicSupercharging reflective libraries with InvokeDynamic
Supercharging reflective libraries with InvokeDynamicIan Robertson
 
Rails-like JavaScript Using CoffeeScript, Backbone.js and Jasmine
Rails-like JavaScript Using CoffeeScript, Backbone.js and JasmineRails-like JavaScript Using CoffeeScript, Backbone.js and Jasmine
Rails-like JavaScript Using CoffeeScript, Backbone.js and JasmineRaimonds Simanovskis
 
Ruby on Rails Presentation
Ruby on Rails PresentationRuby on Rails Presentation
Ruby on Rails Presentationadamcookeuk
 
Merb Day Keynote
Merb Day KeynoteMerb Day Keynote
Merb Day KeynoteYehuda Katz
 
Groovy Grails DevJam Jam Session
Groovy Grails DevJam Jam SessionGroovy Grails DevJam Jam Session
Groovy Grails DevJam Jam SessionMike Hugo
 
5 Tips for Better JavaScript
5 Tips for Better JavaScript5 Tips for Better JavaScript
5 Tips for Better JavaScriptTodd Anglin
 
Node.js for PHP developers
Node.js for PHP developersNode.js for PHP developers
Node.js for PHP developersAndrew Eddie
 
JavaScript 1 for high school
JavaScript 1 for high schoolJavaScript 1 for high school
JavaScript 1 for high schooljekkilekki
 
"How was it to switch from beautiful Perl to horrible JavaScript", Viktor Tur...
"How was it to switch from beautiful Perl to horrible JavaScript", Viktor Tur..."How was it to switch from beautiful Perl to horrible JavaScript", Viktor Tur...
"How was it to switch from beautiful Perl to horrible JavaScript", Viktor Tur...Fwdays
 
Inside the JVM - Follow the white rabbit!
Inside the JVM - Follow the white rabbit!Inside the JVM - Follow the white rabbit!
Inside the JVM - Follow the white rabbit!Sylvain Wallez
 

What's hot (20)

Perl.Hacks.On.Vim
Perl.Hacks.On.VimPerl.Hacks.On.Vim
Perl.Hacks.On.Vim
 
Beware: Sharp Tools
Beware: Sharp ToolsBeware: Sharp Tools
Beware: Sharp Tools
 
Lettering js
Lettering jsLettering js
Lettering js
 
Ruby 程式語言綜覽簡介
Ruby 程式語言綜覽簡介Ruby 程式語言綜覽簡介
Ruby 程式語言綜覽簡介
 
What I Love About Ruby
What I Love About RubyWhat I Love About Ruby
What I Love About Ruby
 
Ruby 入門 第一次就上手
Ruby 入門 第一次就上手Ruby 入門 第一次就上手
Ruby 入門 第一次就上手
 
Supercharging reflective libraries with InvokeDynamic
Supercharging reflective libraries with InvokeDynamicSupercharging reflective libraries with InvokeDynamic
Supercharging reflective libraries with InvokeDynamic
 
Rails on Oracle 2011
Rails on Oracle 2011Rails on Oracle 2011
Rails on Oracle 2011
 
Rails-like JavaScript Using CoffeeScript, Backbone.js and Jasmine
Rails-like JavaScript Using CoffeeScript, Backbone.js and JasmineRails-like JavaScript Using CoffeeScript, Backbone.js and Jasmine
Rails-like JavaScript Using CoffeeScript, Backbone.js and Jasmine
 
JavaScript Primer
JavaScript PrimerJavaScript Primer
JavaScript Primer
 
Ruby on Rails Presentation
Ruby on Rails PresentationRuby on Rails Presentation
Ruby on Rails Presentation
 
Merb Day Keynote
Merb Day KeynoteMerb Day Keynote
Merb Day Keynote
 
Groovy Grails DevJam Jam Session
Groovy Grails DevJam Jam SessionGroovy Grails DevJam Jam Session
Groovy Grails DevJam Jam Session
 
5 Tips for Better JavaScript
5 Tips for Better JavaScript5 Tips for Better JavaScript
5 Tips for Better JavaScript
 
Node.js for PHP developers
Node.js for PHP developersNode.js for PHP developers
Node.js for PHP developers
 
JavaScript 1 for high school
JavaScript 1 for high schoolJavaScript 1 for high school
JavaScript 1 for high school
 
"How was it to switch from beautiful Perl to horrible JavaScript", Viktor Tur...
"How was it to switch from beautiful Perl to horrible JavaScript", Viktor Tur..."How was it to switch from beautiful Perl to horrible JavaScript", Viktor Tur...
"How was it to switch from beautiful Perl to horrible JavaScript", Viktor Tur...
 
Ruby on Rails for beginners
Ruby on Rails for beginnersRuby on Rails for beginners
Ruby on Rails for beginners
 
Go testdeep
Go testdeepGo testdeep
Go testdeep
 
Inside the JVM - Follow the white rabbit!
Inside the JVM - Follow the white rabbit!Inside the JVM - Follow the white rabbit!
Inside the JVM - Follow the white rabbit!
 

Similar to Show Day: Test Drive Ruby on Rails with Fabio Akita

A Toda Maquina Con Ruby on Rails
A Toda Maquina Con Ruby on RailsA Toda Maquina Con Ruby on Rails
A Toda Maquina Con Ruby on RailsRafael García
 
Hacking with ruby2ruby
Hacking with ruby2rubyHacking with ruby2ruby
Hacking with ruby2rubyMarc Chung
 
Rapid Development with Ruby/JRuby and Rails
Rapid Development with Ruby/JRuby and RailsRapid Development with Ruby/JRuby and Rails
Rapid Development with Ruby/JRuby and Railselliando dias
 
Ruby presentasjon på NTNU 22 april 2009
Ruby presentasjon på NTNU 22 april 2009Ruby presentasjon på NTNU 22 april 2009
Ruby presentasjon på NTNU 22 april 2009Aslak Hellesøy
 
Ruby presentasjon på NTNU 22 april 2009
Ruby presentasjon på NTNU 22 april 2009Ruby presentasjon på NTNU 22 april 2009
Ruby presentasjon på NTNU 22 april 2009Aslak Hellesøy
 
Ruby presentasjon på NTNU 22 april 2009
Ruby presentasjon på NTNU 22 april 2009Ruby presentasjon på NTNU 22 april 2009
Ruby presentasjon på NTNU 22 april 2009Aslak Hellesøy
 
Ruby For Java Programmers
Ruby For Java ProgrammersRuby For Java Programmers
Ruby For Java ProgrammersMike Bowler
 
My First Rails Plugin - Usertext
My First Rails Plugin - UsertextMy First Rails Plugin - Usertext
My First Rails Plugin - Usertextfrankieroberto
 
Testing Javascript with Jasmine
Testing Javascript with JasmineTesting Javascript with Jasmine
Testing Javascript with JasmineTim Tyrrell
 
When To Use Ruby On Rails
When To Use Ruby On RailsWhen To Use Ruby On Rails
When To Use Ruby On Railsdosire
 
What's new in Rails 2?
What's new in Rails 2?What's new in Rails 2?
What's new in Rails 2?brynary
 
Building Web Interface On Rails
Building Web Interface On RailsBuilding Web Interface On Rails
Building Web Interface On RailsWen-Tien Chang
 
Dealing with Legacy Perl Code - Peter Scott
Dealing with Legacy Perl Code - Peter ScottDealing with Legacy Perl Code - Peter Scott
Dealing with Legacy Perl Code - Peter ScottO'Reilly Media
 

Similar to Show Day: Test Drive Ruby on Rails with Fabio Akita (20)

A Toda Maquina Con Ruby on Rails
A Toda Maquina Con Ruby on RailsA Toda Maquina Con Ruby on Rails
A Toda Maquina Con Ruby on Rails
 
Hacking with ruby2ruby
Hacking with ruby2rubyHacking with ruby2ruby
Hacking with ruby2ruby
 
Rapid Development with Ruby/JRuby and Rails
Rapid Development with Ruby/JRuby and RailsRapid Development with Ruby/JRuby and Rails
Rapid Development with Ruby/JRuby and Rails
 
Ruby presentasjon på NTNU 22 april 2009
Ruby presentasjon på NTNU 22 april 2009Ruby presentasjon på NTNU 22 april 2009
Ruby presentasjon på NTNU 22 april 2009
 
Ruby presentasjon på NTNU 22 april 2009
Ruby presentasjon på NTNU 22 april 2009Ruby presentasjon på NTNU 22 april 2009
Ruby presentasjon på NTNU 22 april 2009
 
Ruby presentasjon på NTNU 22 april 2009
Ruby presentasjon på NTNU 22 april 2009Ruby presentasjon på NTNU 22 april 2009
Ruby presentasjon på NTNU 22 april 2009
 
Ruby For Java Programmers
Ruby For Java ProgrammersRuby For Java Programmers
Ruby For Java Programmers
 
Sinatra
SinatraSinatra
Sinatra
 
Groovy
GroovyGroovy
Groovy
 
Modern Perl
Modern PerlModern Perl
Modern Perl
 
Rack Middleware
Rack MiddlewareRack Middleware
Rack Middleware
 
Ruby 1.9
Ruby 1.9Ruby 1.9
Ruby 1.9
 
My First Rails Plugin - Usertext
My First Rails Plugin - UsertextMy First Rails Plugin - Usertext
My First Rails Plugin - Usertext
 
Testing Javascript with Jasmine
Testing Javascript with JasmineTesting Javascript with Jasmine
Testing Javascript with Jasmine
 
Perl Presentation
Perl PresentationPerl Presentation
Perl Presentation
 
When To Use Ruby On Rails
When To Use Ruby On RailsWhen To Use Ruby On Rails
When To Use Ruby On Rails
 
Smarty
SmartySmarty
Smarty
 
What's new in Rails 2?
What's new in Rails 2?What's new in Rails 2?
What's new in Rails 2?
 
Building Web Interface On Rails
Building Web Interface On RailsBuilding Web Interface On Rails
Building Web Interface On Rails
 
Dealing with Legacy Perl Code - Peter Scott
Dealing with Legacy Perl Code - Peter ScottDealing with Legacy Perl Code - Peter Scott
Dealing with Legacy Perl Code - Peter Scott
 

More from Fabio Akita

Devconf 2019 - São Carlos
Devconf 2019 - São CarlosDevconf 2019 - São Carlos
Devconf 2019 - São CarlosFabio Akita
 
Meetup Nerdzão - English Talk about Languages
Meetup Nerdzão  - English Talk about LanguagesMeetup Nerdzão  - English Talk about Languages
Meetup Nerdzão - English Talk about LanguagesFabio Akita
 
Desmistificando Blockchains p/ Developers - Criciuma Dev Conf 2018
Desmistificando Blockchains p/ Developers - Criciuma Dev Conf 2018Desmistificando Blockchains p/ Developers - Criciuma Dev Conf 2018
Desmistificando Blockchains p/ Developers - Criciuma Dev Conf 2018Fabio Akita
 
Desmistificando Blockchains - 20o Encontro Locaweb SP
Desmistificando Blockchains - 20o Encontro Locaweb SPDesmistificando Blockchains - 20o Encontro Locaweb SP
Desmistificando Blockchains - 20o Encontro Locaweb SPFabio Akita
 
Desmistificando Blockchains - Insiter Goiania
Desmistificando Blockchains - Insiter GoianiaDesmistificando Blockchains - Insiter Goiania
Desmistificando Blockchains - Insiter GoianiaFabio Akita
 
Blockchain em 7 minutos - 7Masters
Blockchain em 7 minutos - 7MastersBlockchain em 7 minutos - 7Masters
Blockchain em 7 minutos - 7MastersFabio Akita
 
Elixir -Tolerância a Falhas para Adultos - GDG Campinas
Elixir  -Tolerância a Falhas para Adultos - GDG CampinasElixir  -Tolerância a Falhas para Adultos - GDG Campinas
Elixir -Tolerância a Falhas para Adultos - GDG CampinasFabio Akita
 
Desmistificando Mitos de Tech Startups - Intercon 2017
Desmistificando Mitos de Tech Startups - Intercon 2017Desmistificando Mitos de Tech Startups - Intercon 2017
Desmistificando Mitos de Tech Startups - Intercon 2017Fabio Akita
 
30 Days to Elixir and Crystal and Back to Ruby
30 Days to Elixir and Crystal and Back to Ruby30 Days to Elixir and Crystal and Back to Ruby
30 Days to Elixir and Crystal and Back to RubyFabio Akita
 
Uma Discussão sobre a Carreira de TI
Uma Discussão sobre a Carreira de TIUma Discussão sobre a Carreira de TI
Uma Discussão sobre a Carreira de TIFabio Akita
 
THE CONF - Opening Keynote
THE CONF - Opening KeynoteTHE CONF - Opening Keynote
THE CONF - Opening KeynoteFabio Akita
 
A Journey through New Languages - Rancho Dev 2017
A Journey through New Languages - Rancho Dev 2017A Journey through New Languages - Rancho Dev 2017
A Journey through New Languages - Rancho Dev 2017Fabio Akita
 
Desmistificando Mitos de Startups - Sebrae - AP
Desmistificando Mitos de Startups - Sebrae - APDesmistificando Mitos de Startups - Sebrae - AP
Desmistificando Mitos de Startups - Sebrae - APFabio Akita
 
A Journey through New Languages - Guru Sorocaba 2017
A Journey through New Languages - Guru Sorocaba 2017A Journey through New Languages - Guru Sorocaba 2017
A Journey through New Languages - Guru Sorocaba 2017Fabio Akita
 
A Journey through New Languages - Insiter 2017
A Journey through New Languages - Insiter 2017A Journey through New Languages - Insiter 2017
A Journey through New Languages - Insiter 2017Fabio Akita
 
A Journey through New Languages - Locaweb Tech Day
A Journey through New Languages - Locaweb Tech DayA Journey through New Languages - Locaweb Tech Day
A Journey through New Languages - Locaweb Tech DayFabio Akita
 
A Journey through new Languages - Intercon 2016
A Journey through new Languages - Intercon 2016A Journey through new Languages - Intercon 2016
A Journey through new Languages - Intercon 2016Fabio Akita
 
Premature Optimization 2.0 - Intercon 2016
Premature Optimization 2.0 - Intercon 2016Premature Optimization 2.0 - Intercon 2016
Premature Optimization 2.0 - Intercon 2016Fabio Akita
 
Conexão Kinghost - Otimização Prematura
Conexão Kinghost - Otimização PrematuraConexão Kinghost - Otimização Prematura
Conexão Kinghost - Otimização PrematuraFabio Akita
 
The Open Commerce Conference - Premature Optimisation: The Root of All Evil
The Open Commerce Conference - Premature Optimisation: The Root of All EvilThe Open Commerce Conference - Premature Optimisation: The Root of All Evil
The Open Commerce Conference - Premature Optimisation: The Root of All EvilFabio Akita
 

More from Fabio Akita (20)

Devconf 2019 - São Carlos
Devconf 2019 - São CarlosDevconf 2019 - São Carlos
Devconf 2019 - São Carlos
 
Meetup Nerdzão - English Talk about Languages
Meetup Nerdzão  - English Talk about LanguagesMeetup Nerdzão  - English Talk about Languages
Meetup Nerdzão - English Talk about Languages
 
Desmistificando Blockchains p/ Developers - Criciuma Dev Conf 2018
Desmistificando Blockchains p/ Developers - Criciuma Dev Conf 2018Desmistificando Blockchains p/ Developers - Criciuma Dev Conf 2018
Desmistificando Blockchains p/ Developers - Criciuma Dev Conf 2018
 
Desmistificando Blockchains - 20o Encontro Locaweb SP
Desmistificando Blockchains - 20o Encontro Locaweb SPDesmistificando Blockchains - 20o Encontro Locaweb SP
Desmistificando Blockchains - 20o Encontro Locaweb SP
 
Desmistificando Blockchains - Insiter Goiania
Desmistificando Blockchains - Insiter GoianiaDesmistificando Blockchains - Insiter Goiania
Desmistificando Blockchains - Insiter Goiania
 
Blockchain em 7 minutos - 7Masters
Blockchain em 7 minutos - 7MastersBlockchain em 7 minutos - 7Masters
Blockchain em 7 minutos - 7Masters
 
Elixir -Tolerância a Falhas para Adultos - GDG Campinas
Elixir  -Tolerância a Falhas para Adultos - GDG CampinasElixir  -Tolerância a Falhas para Adultos - GDG Campinas
Elixir -Tolerância a Falhas para Adultos - GDG Campinas
 
Desmistificando Mitos de Tech Startups - Intercon 2017
Desmistificando Mitos de Tech Startups - Intercon 2017Desmistificando Mitos de Tech Startups - Intercon 2017
Desmistificando Mitos de Tech Startups - Intercon 2017
 
30 Days to Elixir and Crystal and Back to Ruby
30 Days to Elixir and Crystal and Back to Ruby30 Days to Elixir and Crystal and Back to Ruby
30 Days to Elixir and Crystal and Back to Ruby
 
Uma Discussão sobre a Carreira de TI
Uma Discussão sobre a Carreira de TIUma Discussão sobre a Carreira de TI
Uma Discussão sobre a Carreira de TI
 
THE CONF - Opening Keynote
THE CONF - Opening KeynoteTHE CONF - Opening Keynote
THE CONF - Opening Keynote
 
A Journey through New Languages - Rancho Dev 2017
A Journey through New Languages - Rancho Dev 2017A Journey through New Languages - Rancho Dev 2017
A Journey through New Languages - Rancho Dev 2017
 
Desmistificando Mitos de Startups - Sebrae - AP
Desmistificando Mitos de Startups - Sebrae - APDesmistificando Mitos de Startups - Sebrae - AP
Desmistificando Mitos de Startups - Sebrae - AP
 
A Journey through New Languages - Guru Sorocaba 2017
A Journey through New Languages - Guru Sorocaba 2017A Journey through New Languages - Guru Sorocaba 2017
A Journey through New Languages - Guru Sorocaba 2017
 
A Journey through New Languages - Insiter 2017
A Journey through New Languages - Insiter 2017A Journey through New Languages - Insiter 2017
A Journey through New Languages - Insiter 2017
 
A Journey through New Languages - Locaweb Tech Day
A Journey through New Languages - Locaweb Tech DayA Journey through New Languages - Locaweb Tech Day
A Journey through New Languages - Locaweb Tech Day
 
A Journey through new Languages - Intercon 2016
A Journey through new Languages - Intercon 2016A Journey through new Languages - Intercon 2016
A Journey through new Languages - Intercon 2016
 
Premature Optimization 2.0 - Intercon 2016
Premature Optimization 2.0 - Intercon 2016Premature Optimization 2.0 - Intercon 2016
Premature Optimization 2.0 - Intercon 2016
 
Conexão Kinghost - Otimização Prematura
Conexão Kinghost - Otimização PrematuraConexão Kinghost - Otimização Prematura
Conexão Kinghost - Otimização Prematura
 
The Open Commerce Conference - Premature Optimisation: The Root of All Evil
The Open Commerce Conference - Premature Optimisation: The Root of All EvilThe Open Commerce Conference - Premature Optimisation: The Root of All Evil
The Open Commerce Conference - Premature Optimisation: The Root of All Evil
 

Recently uploaded

Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfAddepto
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii SoldatenkoFwdays
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .Alan Dix
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsPixlogix Infotech
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity PlanDatabarracks
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.Curtis Poe
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Manik S Magar
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxhariprasad279825
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Mattias Andersson
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteDianaGray10
 
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfHyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfPrecisely
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningLars Bell
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...Fwdays
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxLoriGlavin3
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
Vertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsVertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsMiki Katsuragi
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebUiPathCommunity
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationSlibray Presentation
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Commit University
 

Recently uploaded (20)

Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdf
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and Cons
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity Plan
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptx
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test Suite
 
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfHyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine Tuning
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
Vertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsVertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering Tips
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio Web
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck Presentation
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!
 

Show Day: Test Drive Ruby on Rails with Fabio Akita

  • 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. 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. 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. 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. 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. 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. 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. 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. 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. “é 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. Dynamic Typing Static Dynamic Weak Strong Dynamic Typing Static/Strong Java Dynamic/Weak Javascript Dynamic/”Strong” Ruby
  • 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. [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. 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. 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. 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. 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. 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. 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. 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. Servidor não esquecer de apagar public/index.html M.V.C. Model-View-Controller feito direito
  • 46. requisição HTTP ! Mongrel Mongrel ! routes.rb routes.rb ! Controller Controller ! Action Action ! Model Action ! View Action ! resposta HTTP
  • 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. 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. 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
  • 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. Testes Compilar != Testar Tipos de Teste • Testes Unitários: Models • Testes Funcionais: Controllers • Testes Integrados: Cenários de Aceitação
  • 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. 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. 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. 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!
  • 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. # 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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