impacta - show day de rails

110
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

Post on 12-Sep-2014

5.140 views

Category:

Technology


2 download

DESCRIPTION

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

TRANSCRIPT

Page 1: Impacta - Show Day de Rails

Show DayTest 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

Page 2: Impacta - Show Day de Rails

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)

Page 3: Impacta - Show Day de Rails

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

Page 4: Impacta - Show Day de Rails

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

Page 5: Impacta - Show Day de 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.tgztar xvfz rubygems-1.2.0.tgzcd rubygems-1.2.0sudo ruby setup.rb

sudo ln -s /usr/bin/gem1.8 /usr/bin/gemsudo gem install rails sqlite3-ruby mongrel capistrano

Windows

• Baixar One-Click Installer

• gem install rails

Page 6: Impacta - Show Day de 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

Page 7: Impacta - Show Day de Rails

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

Page 8: Impacta - Show Day de Rails

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

Page 9: Impacta - Show Day de Rails

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

Page 10: Impacta - Show Day de Rails

“é 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

Page 11: Impacta - Show Day de Rails

Convenções

• NomesDeClasse

• nomes_de_metodos e nomes_de_variaveis

• metodos_fazendo_pergunta?

• metodos_perigosos!

• @variaveis_de_instancia

• $variaveis_globais

• ALGUMAS_CONSTANTES ou OutrasConstantes

$WORLD = "WORLD "

class ClassePai

HELLO = "Hello "

end

class MinhaClasse < ClassePai

def hello_world(nome)

return if nome.empty?

monta_frase(nome)

@frase.upcase!

puts @frase

end

def monta_frase(nome)

@frase = HELLO + $WORLD + nome

end

end

>> obj = MinhaClasse.new

=> #<MinhaClasse:0x10970e4>

>> obj.hello_world "Fabio"

HELLO WORLD FABIO

Convenções

Page 12: Impacta - Show Day de Rails

Estruturasclass MinhaClasse < ClassePai

def self.metodo_de_classe

"nao é a mesma coisa que estático"

end

def metodo_de_instancia

if funciona?

while true

break if completo?

end

else

return "não funciona"

end

return unless completo?

@teste = "1" if funciona?

...

...

case @teste

when "1"

"primeira condicao"

when "2"

"segunda condicao"

else

"condicao padrao"

end

end

end

Arrays e Strings>> a = [1,2,3,4]

=> [1, 2, 3, 4]

>> b = ["um", "dois", "tres"]

=> ["um", "dois", "tres"]

>> c = %w(um dois tres)

=> ["um", "dois", "tres"]

>> hello = "Hello"

=> "Hello"

>> a = hello + ' Fulano'

=> "Hello Fulano"

>> b = "#{hello} Fulano"

=> "Hello Fulano"

>> c = <<-EOF

multiplas

linhas

EOF

=> " multiplas\n linhas\n"

>>

Page 13: Impacta - Show Day de Rails

Arrays e Strings

>> lista_longa = [<<FOO, <<BAR, <<BLATZ]

teste1

teste2

FOO

foo1

foo2

BAR

alo1

alo2

BLATZ

=> ["teste1\nteste2\n", "foo1\nfoo2\n", "alo1\nalo2\n"]

Apenas curiosidade. Não se costuma fazer isso.

Carregar

• 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

require 'activesupport'

@teste = 1.gigabyte / 1.megabyte

puts @teste.kilobyte

Page 14: Impacta - Show Day de Rails

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]]

>> a,b,c = 1,2,3

=> [1, 2, 3]

>> *a = 1,2,3,4

=> [1, 2, 3, 4]

Joga uma lista de objetosem um Array

Page 15: Impacta - Show Day de Rails

Strings e Symbols>> "teste".object_id

=> 9417020

>> "teste".object_id

=> 9413000

>> :teste.object_id

=> 306978

>> :teste.object_id

=> 306978

>> :teste.to_s.object_id

=> 9399150

>> :teste.to_s.object_id

=> 9393460

•String são mutáveis•Symbols são imutáveis

•Symbols são comumente usados como chaves

Hashes

>> html = { :bgcolor => "black",

:body => { :margin => 0, :width => 100 } }

>> html[:bgcolor]

=> "black"

>> html[:body][:margin]

=> 0

Page 16: Impacta - Show Day de Rails

Tudo é Objeto

>> 1.class

=> Fixnum

>> "a".class

=> String

>> (1.2).class

=> Float

>> true.class

=> TrueClass

>> nil.class

=> NilClass

>> Array.class

=> Class

>> Class.class

=> Class

>> {}.class

=> Hash

>> [].class

=> Array

Tudo é Objeto

>> Array.class

=> Class

>> MeuArray = Array

=> Array

>> a = Array.new(2)

=> [nil, nil]

>> b = MeuArray.new(3)

=> [nil, nil, nil]

>> a.class

=> Array

>> b.class

=> Array

>> b.is_a? MeuArray

=> true

>> a.is_a? MeuArray

=> true

Toda Classe é um objeto, instância de Class

Page 17: Impacta - Show Day de Rails

Tudo é Objeto

def fabrica(classe)

classe.new

end

>> fabrica(Array)

=> []

>> fabrica(String)

=> ""

>> 1 + 2

=> 3

>> 1.+(2)

=> 3

>> 3.-(1)

=> 2

>> 4.* 3

=> 12

>> 4 * 3

=> 12

Classe é um objeto

Operações aritméticas sãométodos de Fixnum

Não há “primitivas”

Não Objetos

• Nomes de variáveis não são objetos

• Variáveis não costumam ter referência a outros objetos

• Blocos (mais adiante)

>> foo = "teste"

=> "teste"

>> a = foo

=> "teste"

>> b = a

=> "teste"

>> foo.object_id

=> 8807330

>> a.object_id

=> 8807330

>> b.object_id

=> 8807330

Page 18: Impacta - Show Day de Rails

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”)

Page 19: Impacta - Show Day de Rails

Mensagens

>> 1 + 2

=> 3

>> 1.+ 2

=> 3

>> "teste".size

=> 5

Envio da mensagem “+”ao objeto “1”

com parâmetro “2”

Envio da mensagem“size” ao objeto “teste”

Mensagens

class Pilha

attr_accessor :buffer

def initialize

@buffer = []

end

def method_missing(metodo, *args, &bloco)

@buffer << metodo.to_s

end

end

method_missing irá interceptar toda mensagem não definida como método

Page 20: Impacta - Show Day de Rails

Mensagens

>> pilha = Pilha.new

=> #<Pilha:0x1028978 @buffer=[]>

>> pilha.blabla

=> ["blabla"]

>> pilha.alo

=> ["blabla", "alo"]

>> pilha.hello_world

=> ["blabla", "alo", "hello_world"]

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

Page 21: Impacta - Show Day de Rails

Classes Abertas

class Fixnum

def par?

(self % 2) == 0

end

end

>> p (1..10).select { |n| n.par? }

# => [2, 4, 6, 8, 10]

abrindo a classe padrão Fixnum e acrescentando um

novo método

Mixins

module Akita

module MeuInteiro

def par?

(self % 2) == 0

end

end

end

Fixnum.class_eval do

include(Akita::MeuInteiro)

end

# ou

class Fixnum

include Akita::MeuInteiro

end

# ou

Fixnum.send(:include,

Akita::MeuInteiro)

Page 22: Impacta - Show Day de Rails

Mixins

class Pessoa

include Comparable

attr_accessor :nome, :idade

def initialize(nome, idade)

self.nome = nome

self.idade = idade

end

def <=>(outro)

self.idade <=> outro.idade

end

end

>> carlos = Pessoa.new("Carlos", 20)

=> #<Pessoa:0x103833c>

>> ricardo = Pessoa.new("Ricardo", 30)

=> #<Pessoa:0x1033abc>

>> carlos > ricardo

=> false

>> carlos == ricardo

=> false

>> carlos < ricardo

=> true

Métodos Singletonclass Cachorro

end

rover = Cachorro.new

fido = Cachorro.new

def rover.fale

puts "Rover Vermelho"

end

rover.instance_eval do

def fale

puts "Rover vermelho"

end

end>> rover.fale

Rover Vermelho

>> fido.fale

NoMethodError: undefined method `fale' for #<Cachorro:0x10179e8>

from (irb):90

Page 23: Impacta - Show Day de Rails

Geração de Código

class Module

def trace_attr(sym)

self.module_eval %{

def #{sym}

printf "Acessando %s com valor %s\n",

"#{sym}", @#{sym}.inspect

end

}

end

end

class Cachorro trace_attr :nome def initialize(string) @nome = string end end

>> Cachorro.new("Fido").nome # => Acessando nome com valor"Fido"

Acessando nome com valor "Fido"

Geração de Códigoclass 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)

Page 24: Impacta - Show Day de Rails

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("@#{key}", value)

end

end

end

end

Filosofia “Don’t Repeat Yourself”

Geração de Código

Person = MyStruct :name, :address, :likes

dave = Person.new(:name => "dave", :address => "TX",

:likes => "Stilton")

chad = Person.new(:name => "chad", :likes => "Jazz")

chad.address = "CO"

>> puts "O nome do Dave e #{dave.name}"

O nome do Dave e dave

=> nil

>> puts "Chad mora em #{chad.address}"

Chad mora em CO

Page 25: Impacta - Show Day de Rails

Dynamic Typing

Static Dynamic

Weak Strong

Dynamic Typing

Static/Strong Java

Dynamic/Weak Javascript

Dynamic/”Strong” Ruby

Page 26: Impacta - Show Day de Rails

“Strong” Typing

class Parent

def hello; puts "In parent"; end

end

class Child < Parent

def hello; puts "In child" ; end

end

>> c = Child.new

=> #<Child:0x1061fac>

>> c.hello

In child

“Strong Typing”class Child

# remove "hello" 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

Page 27: Impacta - Show Day de Rails

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 Typingclass Gato

def fala; "miau"; end

end

class Cachorro

def fala; "au au"; end

end

for animal in [Gato.new, Cachorro.new]

puts animal.class.name + " fala " + animal.fala

end

# Gato fala miau

# Cachorro fala au au

Page 28: Impacta - Show Day de Rails

Chicken Typingclass Gato; def fala; "miau"; end; endclass Cachorro; def fala; "au au"; end; endclass Passaro; def canta; "piu"; end; end

def canil(animal) return "Animal mudo" unless animal.respond_to?(:fala) return "Nao e cachorro" unless animal.is_a?(Cachorro) puts animal.falaend

>> canil(Gato.new)=> "Nao e cachorro">> canil(Passaro.new)=> "Animal mudo">> canil(Cachorro.new)=> au au

Evite coisas assim!

Blocos e Fechamentos

lista = [1, 2, 3, 4, 5]

for numero in lista

puts numero * 2

end

lista.each do |numero|

puts numero * 2

end

# mesma coisa:

lista.each { |numero| puts numero * 2 }

loop tradicional

loop com bloco

Page 29: Impacta - Show Day de Rails

Blocos e Fechamentos# jeito "antigo"

f = nil

begin

f = File.open("teste.txt", "r")

texto = f.read

ensure

f.close

end

# com fechamentos

File.open("teste.txt", "r") do |f|

texto = f.read

end

Blocos e Fechamentos

# com yield

def foo

yield

end

# como objeto

def foo(&block)

block.call

end

>> foo { puts "alo" }

=> alo

# como seria em "runtime"

def foo

puts "alo"

end

Page 30: Impacta - Show Day de Rails

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 = []

numero = 1

while numero < 51

lista << numero

numero += 1

end

total = 0

for numero in lista

if numero % 2 == 0

total += (numero * 2)

end

end

=> 1300

Page 31: Impacta - Show Day de Rails

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

end.map do |n|

n * 2

end.inject(0) do |n, t|

t += n

end

(1..50).select { |n|

n % 2 == 0 }.map { |n|

n * 2 }.inject(0) { |n, t|

t += n }

Tudo Junto

def tag(nome, options = {})

if options.empty?

puts "<#{nome}>"

else

attr = options.map { |k,v| "#{k}='#{v}' " }

puts "<#{nome} #{attr}>"

end

puts yield if block_given?

puts "</#{nome}>"

end

Page 32: Impacta - Show Day de Rails

Tudo Junto>> tag :div

<div>

</div>

>> tag :img, :src => "logo.gif", :alt => "imagem"

<img alt='imagem' src='logo.gif' >

</img>

>> tag(:p, :style => "color: yellow") { "Hello World" }

<p style='color: yellow' >

Hello World

</p>

Ruby on Rails

“Domain Specific Language for the Web”

Page 33: Impacta - Show Day de Rails

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

Page 34: Impacta - Show Day de Rails

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

Page 35: Impacta - Show Day de Rails

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

Page 36: Impacta - Show Day de Rails

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!

Page 37: Impacta - Show Day de Rails

[20:57][~/rails/sandbox/impacta]$ rails _2.1.0_ tarefas

create

create app/controllers

create app/helpers

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

opcional

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

Page 38: Impacta - Show Day de Rails

Pacotes

Ruby

Mongrel

Aplicação Rails

ActiveResource

ActiveSupport

ActionPack

ActiveWS

ActionMailer

Rails

ActionController

ActiveRecord

ActionView

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)

Page 39: Impacta - Show Day de Rails

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 "test" will be erased and

# re-generated from your development database when you run "rake".# Do not set this db to the same as development or production.

test: adapter: sqlite3

database: db/test.sqlite3 timeout: 5000

production:

adapter: sqlite3 database: db/production.sqlite3

timeout: 5000

Page 40: Impacta - Show Day de Rails

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!

Page 41: Impacta - Show Day de Rails

Plugins: aceleradores

>> ./script/plugin install git://github.com/technoweenie/restful-authentication.gitremoving: /Users/akitaonrails/rails/sandbox/impacta/tarefas/vendor/plugins/restful-

authentication/.gitInitialized 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/tasksrake 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.

Page 42: Impacta - Show Day de Rails

Rake (Ruby Make)>> rake db:create:alldb/development.sqlite3 already exists

db/production.sqlite3 already existsdb/test.sqlite3 already exists

>> rake db:migrate

== 20080629001252 CreateUsuarios: migrating ===================================-- create_table("usuarios", {: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

endend

Page 43: Impacta - Show Day de Rails

Migrations

>> rake db:migrate

== 20080629001252 CreateUsuarios: migrating ===================================-- create_table("usuarios", {: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) ===========================

MigrationsCREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL) CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version")

Migrating to CreateUsuarios (20080629001252)

SELECT version FROM schema_migrations CREATE TABLE "usuarios" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "login"

varchar(40) DEFAULT NULL NULL, "name" varchar(100) DEFAULT '' NULL, "email" varchar(100) DEFAULT NULL NULL, "crypted_password" varchar(40) DEFAULT NULL NULL, "salt" varchar(40)

DEFAULT NULL NULL, "created_at" datetime DEFAULT NULL NULL, "updated_at" datetime DEFAULT NULL NULL, "remember_token" varchar(40) DEFAULT NULL NULL, "remember_token_expires_at"

datetime DEFAULT NULL NULL) CREATE UNIQUE INDEX "index_usuarios_on_login" ON "usuarios" ("login")

INSERT INTO schema_migrations (version) VALUES ('20080629001252')

Migrating to CreateTarefas (20080629003332) SELECT version FROM schema_migrations

CREATE TABLE "tarefas" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "usuario_id" integer DEFAULT NULL NULL, "duracao" integer DEFAULT NULL NULL, "descricao" varchar(255)

DEFAULT NULL NULL, "data_inicio" datetime DEFAULT NULL NULL, "created_at" datetime DEFAULT NULL NULL, "updated_at" datetime DEFAULT NULL NULL)

INSERT INTO schema_migrations (version) VALUES ('20080629003332') SELECT version FROM schema_migrations

Page 44: Impacta - Show Day de Rails

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)

Page 45: Impacta - Show Day de Rails

Servidor

não esquecer de apagar public/index.html

M.V.C.

Model-View-Controller feito direito

Page 46: Impacta - Show Day de Rails

requisição HTTP ! Mongrel

Mongrel ! routes.rb

routes.rb ! Controller

Controller ! Action

Action ! Model

Action ! View

Action ! resposta HTTP

Page 47: Impacta - Show Day de Rails

Controllers# app/controllers/application.rbclass ApplicationController < ActionController::Base helper :all # include all helpers, all the time protect_from_forgeryend

# app/controllers/tarefas_controller.rbclass 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 endend

Views - Templates<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head> <meta http-equiv="content-type" content="text/html;charset=UTF-8" />

<title>Tarefas: <%= controller.action_name %></title> <%= stylesheet_link_tag 'scaffold' %>

</head><body>

<p style="color: green"><%= flash[:notice] %></p>

<%= yield %>

</body>

</html>

Page 48: Impacta - Show Day de Rails

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.rbrequire '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

Page 49: Impacta - Show Day de Rails

Rotas# config/routes.rbActionController::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'end

jeito “antigo” (1.2)

Rotas - Antigo

• http://www.dominio.com/tarefas/show/123

• map.connect ':controller/:action/:id'

• tarefas_controller.rb ! TarefasController

• def show ... end

• params[:id] ! 123

Page 50: Impacta - Show Day de Rails

Active Record

Page 51: Impacta - Show Day de Rails

• “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

# 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

Associações Simples

usuarios

id: integer

tarefas

id: integer

usuario_id: integer

Page 52: Impacta - Show Day de Rails

Interatividade - Console>> Usuario.count

=> 0

>> Tarefa.count

=> 0

>> admin = Usuario.create(:login => 'admin', :password =>

'admin', :password_confirmation => 'admin', :email =>

'[email protected]')

=> #<Usuario id: nil, login: "admin", name: "", email:

"[email protected]", 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

=> ["Password deve ter no minimo 6 caractere(s)"]

Interatividade - Console>> admin = Usuario.create(:login => 'admin', :password =>

'admin123', :password_confirmation => 'admin123', :email =>

'[email protected]')

=> #<Usuario id: 1, login: "admin", name: "", email:

"[email protected]", crypted_password: "e66...abd", salt: "731...b96",

created_at: "2008-06-29 20:21:10", updated_at: "2008-06-29 20:21:10",

remember_token: nil, remember_token_expires_at: nil>

>> admin.tarefas

=> []

>> admin.tarefas.create(:descricao => "Criando demo de

Rails", :duracao => 2, :data_inicio => Time.now)

=> #<Tarefa id: 1, usuario_id: 1, duracao: 2, descricao: "Criando

demo de Rails", data_inicio: "2008-06-29 20:21:40", created_at:

"2008-06-29 20:21:40", updated_at: "2008-06-29 20:21:40">

Page 53: Impacta - Show Day de Rails

Interatividade - Console

>> Tarefa.count

=> 1

>> tarefa = Tarefa.first

=> #<Tarefa id: 1, usuario_id: 1, duracao: 2, descricao: "Criando

demo de Rails", data_inicio: "2008-06-29 20:21:40", created_at:

"2008-06-29 20:21:40", updated_at: "2008-06-29 20:21:40">

>> tarefa.usuario

=> #<Usuario id: 1, login: "admin", name: "", email:

"[email protected]", crypted_password: "e66...abd", salt: "731...b96",

created_at: "2008-06-29 20:21:10", updated_at: "2008-06-29

20:21:10", remember_token: nil, remember_token_expires_at: nil>

Validações# app/models/usuario.rbclass 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 #[email protected]

validates_uniqueness_of :email, :case_sensitive => false validates_format_of :email, :with => RE_EMAIL_OK, :message => MSG_EMAIL_BAD

...end

Page 54: Impacta - Show Day de Rails

Validaçõesvalidates_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

Page 55: Impacta - Show Day de Rails

FindersTarefa.find 42 # objeto com ID 42Tarefa.find [37, 42] # array com os objetos de ID 37 e 42

Tarefa.find :allTarefa.find :last

Tarefa.find :first, :conditions => [ "data_inicio < ?", 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

Page 56: Impacta - Show Day de Rails

Named Scope>> Tarefa.hoje

SELECT * FROM "tarefas" WHERE (data_inicio between

'2008-06-29 00:00:00' and '2008-06-29 23:59:59')

>> Tarefa.curtas

SELECT * FROM "tarefas" WHERE (duracao < 2)

>> Tarefa.medias.hoje

SELECT * FROM "tarefas" 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

Page 57: Impacta - Show Day de Rails

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)

=> "/tarefas/123/edit"

Page 58: Impacta - Show Day de Rails

CRUD - SQL

Create INSERT

Read SELECT

Update UPDATE

Destroy DELETE

CRUD - AntigoGET /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

Page 59: Impacta - Show Day de Rails

Verbos HTTP

GET

POST

PUT

DELETE

CRUD - REST - DRYGET /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

Page 60: Impacta - Show Day de Rails

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.rbclass 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

Page 61: Impacta - Show Day de Rails

REST Templates# app/controllers/tarefas_controller.rbclass 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 ...

end

# app/views/tarefas/new.html.erb<% form_for(@tarefa) do |f| %>

... <p>

<%= f.submit "Create" %> </p>

<% end %>

<!-- /tarefas/1 --><form action="/tarefas/3" class="edit_tarefa"

id="edit_tarefa_3" method="post"> <input name="_method" type="hidden"

value="put" /> ...

<p> <input id="tarefa_submit" name="commit"

type="submit" value="Create" /> </p>

</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

Page 62: Impacta - Show Day de Rails

Nested Controllers# app/controllers/anotacoes_controller.rbclass AnotacoesController < ApplicationController

before_filter :load_tarefa ...

private

def load_tarefa @tarefa = Tarefa.find(params[:tarefa_id])

endend

# config/routes.rbActionController::Routing::Routes.draw do |map|

map.resources :tarefas, :has_many => :anotacoes ...

end

# trocar X# por Y

Anotacao.find

@tarefa.anotacoes.find

[email protected]

redirect_to(@anotacao)

redirect_to([@tarefa, @anotacao])

:location => @anotacao:location => [@tarefa, @anotacao]

anotacoes_url

tarefa_anotacoes_url(@tarefa)

Nested Views# trocar X# por Y em todos os arquivos app/views/anotacoes/*.html.erb

form_for(@anotacao)

form_for([@tarefa, @anotacao])

link_to 'Show', @anotacaolink_to 'Show', [@tarefa, @anotacao]

anotacoes_path

tarefa_anotacoes_path(@tarefa)

edit_anotacao_path(@tarefa)edit_tarefa_anotacao_path(@tarefa, @anotacao)

anotacoes_path

tarefa_anotacoes_path(@tarefa)

new_anotacao_pathnew_tarefa_anotacao_path(@tarefa)

<!-- apagar de new.html.erb e edit.html.erb -->

<p> <%= f.label :tarefa %><br />

<%= f.text_field :tarefa %></p>

<!-- acrescentar ao final de index.html.erb -->

<%= link_to 'Back to Tarefa', tarefa_path(@tarefa) %>

<!-- acrescentar ao final de

tarefas/show.html.erb --><%= link_to 'Anotações',

tarefa_anotacoes_path(@tarefa) %>

Page 63: Impacta - Show Day de Rails

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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head> <meta http-equiv="content-type" content="text/html;charset=UTF-8" />

<title><%= controller.action_name %></title> <%= stylesheet_link_tag 'scaffold' %>

<%= javascript_include_tag :defaults %> <%= active_scaffold_includes %>

</head><body>

<p style="color: green"><%= flash[:notice] %></p>

<%= yield %></body>

</html>

apague tudo em app/views/layouts e crie apenas este application.html.erb

Page 64: Impacta - Show Day de Rails

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

Page 65: Impacta - Show Day de Rails

RESTful RailsParte 2: has many through

many-to-many# app/models/anotacao.rbclass Anotacao < ActiveRecord::Base

belongs_to :tarefaend

# app/models/tarefa.rb

class Tarefa < ActiveRecord::Base belongs_to :usuario

has_many :anotacoes ...

end

# app/models/usuario.rbclass Usuario < ActiveRecord::Base

... has_many :tarefas

has_many :anotacoes, :through => :tarefas ...

end

anotacoes

id: int

tarefa_id: int

tarefas

id: int

usuario_id: int

usuarios

id: int

Page 66: Impacta - Show Day de Rails

many-to-many>> admin = Usuario.find_by_login('admin')=> #<Usuario id: 1, login: "admin", name: "", email: "[email protected]",

crypted_password: "e66e...abd", salt: "731f...b96", created_at: "2008-06-29 20:21:10", updated_at: "2008-06-29 20:21:10", remember_token: nil, remember_token_expires_at: nil>

SELECT * FROM "usuarios" WHERE ("usuarios"."login" = 'admin') LIMIT 1

>> admin.tarefas

=> [#<Tarefa id: 1, usuario_id: 1, duracao: 2, descricao: "Criando demo de Rails", data_inicio: "2008-06-29 20:21:40", created_at: "2008-06-29 20:21:40", updated_at:

"2008-06-29 20:21:40">]

SELECT * FROM "tarefas" WHERE ("tarefas".usuario_id = 1)

>> admin.anotacoes=> [#<Anotacao id: 1, tarefa_id: 1, anotacao: "teste", created_at: "2008-06-29

21:29:52", updated_at: "2008-06-29 21:29:52">]

SELECT "anotacoes".* FROM "anotacoes" INNER JOIN tarefas ON anotacoes.tarefa_id = tarefas.id WHERE (("tarefas".usuario_id = 1))

Cenário Antigo# tabela 'revistas'class Revista < ActiveRecord::Base

# tabela 'clientes_revistas' has_and_belongs_to_many :clientes

end

# tabela 'clientes'class Cliente < ActiveRecord::Base

# tabela 'clientes_revistas' has_and_belongs_to_many :revistas

end

class RevistasController < ApplicationController def add_cliente() end

def remove_cliente() endend

class ClientesController < ApplicationController

def add_revista() end def remove_revista() end

end

clientes_revistas

cliente_id: int

revista_id: int

clientes

id: int

revistas

id: int

Qual dos dois controllers está certo?

A revista controla o cliente ou o cliente controla a revista?

Page 67: Impacta - Show Day de Rails

Cenário RESTclass Assinatura < ActiveRecord::Base belongs_to :revista

belongs_to :clienteend

class Revista < ActiveRecord::Base

has_many :assinaturas has_many :clientes, :through => :assinaturas

end

class Cliente < ActiveRecord::Base has_many :assinaturas

has_many :revistas, :through => :assinaturasend

class AssinaturasController < ApplicationController

def create() end def destroy() end

end

assinaturas

cliente_id: int

revista_id: int

clientes

id: int

revistas

id: int

Tabelas many-to-many, normalmente podem ser

um recurso próprio

RESTful RailsParte 3: ActiveResource

Page 68: Impacta - Show Day de Rails

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'=> []

class Tarefa < ActiveResource::Base

self.site = "http://localhost:3000"end

>> t = Tarefa.find :first

=> #<Tarefa:0x1842c94 @prefix_options={}, @attributes={"updated_at"=>Sun Jun 29 20:21:40 UTC 2008, "id"=>1, "usuario_id"=>1, "data_inicio"=>Sun Jun 29 20:21:40 UTC

2008, "descricao"=>"Criando demo de Rails", "duracao"=>2, "created_at"=>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={"data_inicio"=>Sun Jun 29

19:00:57 -0300 2008, "descricao"=>"Testando REST", "duracao"=>1}

>> t.save=> true

Via console IRB

Page 69: Impacta - Show Day de Rails

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

Page 70: Impacta - Show Day de Rails

Testes

Compilar != Testar

• Testes Unitários: Models

• Testes Funcionais: Controllers

• Testes Integrados: Cenários de Aceitação

Tipos de Teste

Page 71: Impacta - Show Day de Rails

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-authenticationquentin:

login: quentin

email: [email protected]

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: [email protected]

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: [email protected]

salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd

crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test

created_at: <%= 1.days.ago.to_s :db %>

Page 72: Impacta - Show Day de Rails

tarefas e anotações

# test/fixtures/tarefas.ymlaula:

usuario: quentin duracao: 1

descricao: Dando Aula data_inicio: 2008-06-28 21:33:32

academia:

usuario: aaron duracao: 1

descricao: Exercitando data_inicio: 2008-06-28 21:33:32

# test/fixtures/anotacoes.ymlone:

tarefa: aula anotacao: Aula de Rails

two:

tarefa: aula anotacao: Precisa corrigir prova

three:

tarefa: academia anotacao: Mudando rotina

Ajustando - Parte 1require File.dirname(__FILE__) + '/../test_helper'

class AnotacoesControllerTest < ActionController::TestCase

fixtures :tarefas, :usuarios, :anotacoes

def setup

@tarefa = tarefas(:aula)

end

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

get :new, :tarefa_id => @tarefa.id

assert_response :success

end

def test_should_create_anotacao

assert_difference('Anotacao.count') do

post :create, :tarefa_id => @tarefa.id, :anotacao => { :anotacao => "teste" }

end

assert_redirected_to tarefa_anotacao_path(@tarefa, assigns(:anotacao))

end

declarando quaisfixtures carregar

adicionandoa chave :tarefa_idao hash params

Page 73: Impacta - Show Day de Rails

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

def test_should_update_anotacao

put :update, :tarefa_id => @tarefa.id, :id => anotacoes(:one).id, :anotacao => { :anotacao => "teste"}

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)

end

end

hash params

ajustandorotas 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

Page 74: Impacta - Show Day de Rails

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 "/opt/local/lib/ruby/gems/1.8/gems/rake-0.8.1/lib/rake/rake_test_loader.rb" "test/unit/anotacao_test.rb" "test/unit/tarefa_test.rb" "test/unit/usuario_test.rb" Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.8.1/lib/rake/rake_test_loaderStarted...............Finished in 0.370074 seconds.

15 tests, 28 assertions, 0 failures, 0 errors/opt/local/bin/ruby -Ilib:test "/opt/local/lib/ruby/gems/1.8/gems/rake-0.8.1/lib/rake/rake_test_loader.rb" "test/functional/admin/usuarios_controller_test.rb" "test/functional/anotacoes_controller_test.rb" "test/functional/sessoes_controller_test.rb" "test/functional/tarefas_controller_test.rb" "test/functional/usuarios_controller_test.rb" Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.8.1/lib/rake/rake_test_loaderStarted.............................Finished in 0.832019 seconds.

29 tests, 53 assertions, 0 failures, 0 errors/opt/local/bin/ruby -Ilib:test "/opt/local/lib/ruby/gems/1.8/gems/rake-0.8.1/lib/rake/rake_test_loader.rb"

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!

Page 75: Impacta - Show Day de Rails

Assertions

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

Assertions

Page 76: Impacta - Show Day de Rails

Assertions

Views

Page 77: Impacta - Show Day de Rails

Ajustando Layouts<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head> <meta http-equiv="content-type" content="text/html;charset=UTF-8" /> <title>Admin <%= controller.action_name %></title> <%= stylesheet_link_tag 'scaffold' %> <%= javascript_include_tag :defaults %> <%= active_scaffold_includes %></head><body>

<p style="color: green"><%= 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 %>

Page 78: Impacta - Show Day de Rails

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 /> <%= form.text_field :duracao %></p><p> <%= form.label :descricao %><br /> <%= form.text_field :descricao %></p><p> <%= form.label :data_inicio %><br /> <%= form.datetime_select :data_inicio %></p><p> <%= form.submit submit_text %></p>

<!-- show.html.erb --><p>

<b>Usuario:</b> <%=h @tarefa.usuario.login %>

</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

Page 79: Impacta - Show Day de Rails

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 %>

Page 80: Impacta - Show Day de Rails

Ajax<!-- app/views/tarefas/_tarefa_row.html.erb --><tr id="<%= dom_id(tarefa) %>">

<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.rjspage.insert_html :bottom, 'tarefas_table',

:partial => 'tarefa_row', :locals => { :tarefa => @tarefa }

page.visual_effect :highlight, dom_id(@tarefa)

# app/views/tarefas/destroy.js.rjspage.visual_effect :drop_out, dom_id(@tarefa)

# app/controllers/tarefas_controller.rbclass 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 => "new" } 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 endend

Page 81: Impacta - Show Day de Rails

RJS - nos bastidores<%= link_to_remote 'Destroy', :url => tarefa_path(tarefa), :confirm => 'Are you sure?', :method => :delete %>

<a href="#" onclick="if (confirm('Are you sure?')) { new

Ajax.Request('/tarefas/10', {asynchronous:true, evalScripts:true, method:'delete', parameters:'authenticity_token=' +

encodeURIComponent('966...364')}); }; return false;">Destroy</a>

<% remote_form_for(Tarefa.new) do |f| %> ...

<% end %>

<form action="/tarefas" class="new_tarefa" id="new_tarefa" method="post" onsubmit="new Ajax.Request('/tarefas',

{asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">

...</form>

Page 82: Impacta - Show Day de Rails

FormHelperf.check_box :accepted, { :class => 'eula_check' }, "yes", "no"

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 => "Pacific Time (US & Canada)"

JavaScriptGeneratorpage[:element]page << "alert('teste')"

page.alert("teste")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 => @personpage.select('p.welcome b').first.hide

page.visual_effect :highlight, 'person_12'

Page 83: Impacta - Show Day de Rails

PrototypeHelper<%= link_to_remote "Destroy", :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 => "invoice", :failure => "error" } %>

<% remote_form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>

...<% end %>

<%= submit_to_remote 'update_btn', 'Update', :url => { :action => 'update' },

:update => { :success => "succeed", :failure => "fail" } %>

<%= draggable_element("my_image", :revert => true) %><%= drop_receiving_element("my_cart", :url => { :controller => "cart",

:action => "add" }) %><%= sortable_element("my_list", :url => { :action => "order" }) %>

Entendendo Forms

Page 84: Impacta - Show Day de Rails

<% 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 %>

Nome e prefixo do form

<form action="/tarefas" class="new_tarefa" id="new_tarefa" method="post">

<div style="margin:0;padding:0">

<input name="authenticity_token" type="hidden"

value="966974fa19db6efaf0b3f456c2823a7f46181364" />

</div>

<p>

<label for="tarefa_usuario">Usuario</label><br />

<select id="tarefa_usuario_id" name="tarefa[usuario_id]">

<option value="1">admin</option>

<option value="2">akita</option>

</select>

</p>

<p>

<label for="tarefa_duracao">Duracao</label><br />

<input id="tarefa_duracao" name="tarefa[duracao]" size="30" type="text" />

</p>

<p>

<label for="tarefa_descricao">Descricao</label><br />

<input id="tarefa_descricao" name="tarefa[descricao]" size="30" type="text" />

</p>

<p>

<label for="tarefa_data_inicio">Data inicio</label><br />

<input id="tarefa_data_inicio" name="tarefa[data_inicio]" size="30" type="text" />

<img alt="Calendar" onclick="new CalendarDateSelect( $(this).previous(),

{time:true, year_range:10} );" src="/images/calendar_date_select/calendar.gif?1214788838"

style="border:0px; cursor:pointer;" />

</p>

<p>

<input id="tarefa_submit" name="commit" type="submit" value="Create" />

</p>

</form>

Page 85: Impacta - Show Day de Rails

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

Parameters: {"commit"=>"Create",

"authenticity_token"=>"966974fa19db6efaf0b3f456c2823a7f46181364", "action"=>"create", "controller"=>"tarefas",

"tarefa"=>{"usuario_id"=>"1", "data_inicio"=>"June 28, 2008 12:00 AM", "descricao"=>"Teste de Criação", "duracao"=>"1"}}

Tarefa Create (0.000453) INSERT INTO "tarefas"

("updated_at", "usuario_id", "data_inicio", "descricao", "duracao", "created_at") 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/12Completed 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

# app/controllers/tarefas_controller.rbclass TarefasController < ApplicationController

def create # pegando o hash params, na chave :tarefa

@tarefa = Tarefa.new(params[:tarefa]) ...

endend

# equivalente a:

@tarefa = Tarefa.new { :duracao=>"1", :usuario_id=>"1", :data_inicio=>"June 28, 2008 12:00 AM", :descricao=>"Teste de Criação" }

# o hash params completo vem assim:

params = {:action=>"create", :authenticity_token=>"966...364", :controller=>"tarefas", :commit=>"Create",

:tarefa => {"usuario_id"=>"1", "duracao"=>"1", "data_inicio"=>"June 28, 2008 12:00 AM",

"descricao"=>"Teste de Criação" } }

Params

Page 86: Impacta - Show Day de Rails

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...

config.action_mailer.delivery_method = :smtpconfig.action_mailer.smtp_settings = {

:address => "smtp.gmail.com", :port => 587,

:authentication => :plain, :user_name => "fabioakita",

:password => "----------"}

config.action_mailer.perform_deliveries = true

# app/controllers/tarefas_controller.rbclass TarefasController < ApplicationController

... def create

@tarefa = Tarefa.new(params[:tarefa])

respond_to do |format| if @tarefa.save

TarefaMailer.deliver_importante(@tarefa) if @tarefa.descricao =~ /^\!/ ...

end

Para enviarvia Gmail

Ativa oenvio

Page 87: Impacta - Show Day de Rails

# app/models/tarefa_mailer.rb

class TarefaMailer < ActionMailer::Base

def importante(tarefa, sent_at = Time.now)

recipients tarefa.usuario.email

subject 'Tarefa Importante'

from '[email protected]'

sent_on sent_at

body :tarefa => tarefa

end

# se tiver ActiveScaffold instalado

def self.uses_active_scaffold?

false

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) %>

Template ERB

# test/unit/tarefa_mailer_test.rbrequire 'test_helper'

class TarefaMailerTest < ActionMailer::TestCase

tests TarefaMailer fixtures :usuarios, :tarefas

def test_importante @expected.subject = 'Tarefa Importante'

@expected.from = '[email protected]' @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

endend

# test/fixtures/tarefa_mailer/importante

Notificação de Tarefa Importante

Descrição: Dando AulaDuração: 1 hs

Início: 28 Jun 21:33

Page 88: Impacta - Show Day de Rails

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.

Page 89: Impacta - Show Day de Rails

Outras Dicas

Rotas

$ rm public/index.html

# config/routes.rb

# adicionar:

map.root :tarefas

# remover:

map.connect ':controller/:action/:id'

map.connect ':controller/:action/:id.:format'

Redefine a raízda aplicação

Apps REST nãoprecisam disso

Page 90: Impacta - Show Day de Rails

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 => "Teste com hora") # 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'

Page 91: Impacta - Show Day de Rails

Time Zone

>> 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 => "Outro teste com hora") # 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

# config/environment.rb

config.time_zone = 'Brasilia'

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

Page 92: Impacta - Show Day de Rails

Time Zone Rake Tasks

$ rake time:zones:local

* UTC -03:00 *

Brasilia

Buenos Aires

Georgetown

Greenland

$ rake time:zones:us

* UTC -10:00 *Hawaii

* UTC -09:00 *

Alaska

* UTC -08:00 *Pacific Time (US & Canada)

* UTC -07:00 *

ArizonaMountain 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.rbclass 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 endend

$ rake db:migrate

<!-- app/views/usuarios/new.html.erb --><p><label for="time_zone">Time Zone</label><br/><%= f.time_zone_select :time_zone, TimeZone.all %></p>

Page 93: Impacta - Show Day de Rails

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'

Views

# app/controllers/admin/usuarios_controller.rbclass 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 ]

config.create.columns.exclude [ :created_at]

config.update.columns.exclude [

:login, :created_at]

end

end

Page 94: Impacta - Show Day de Rails

Time Zone Views

Carregando Zonas# app/controllers/application.rbclass ApplicationController < ActionController::Base helper :all # include all helpers, all the time include AuthenticatedSystem

protect_from_forgery before_filter :load_time_zone private def load_time_zone Time.zone = current_usuario.time_zone if logged_in? endend

# app/controllers/admin/usuarios_controller.rbclass Admin::UsuariosController < ApplicationController before_filter :login_required ...end

Retirar include AuthenticatedSystem de

usuarios e sessoes

Page 95: Impacta - Show Day de Rails

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

Page 96: Impacta - Show Day de Rails

Segurança# config/environment.rbconfig.action_controller.session = {

:session_key => '_tarefas_session', :secret =>

'aaa02677597285ff58fcdb2eafaf5a82a1c10572334acb5297ec94a0f6b1cf48fbcb8f546c00c08769a6f99695dd967a2d3fea33d6217548fcc4fd64e783caa6'

}

# app/controllers/application.rbclass ApplicationController < ActionController::Base

... protect_from_forgery

...end

<form action="/tarefas" class="new_tarefa" id="new_tarefa" method="post">

<div style="margin:0;padding:0"> <input name="authenticity_token" type="hidden"

value="5a2176fd77601a497a9d7ae8184d06b60df0ae28" /> </div>

...</form>

JRuby

Page 97: Impacta - Show Day de Rails

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

Page 98: Impacta - Show Day de Rails

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

Page 99: Impacta - Show Day de Rails

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

Page 100: Impacta - Show Day de Rails

Configuração

• Alterar database.yml

• jruby -S jetty-rails

<% jdbc = defined?(JRUBY_VERSION) ? 'jdbc' : '' %>

development:

adapter: <%= jdbc %>mysql

database: tarefas

username: root

password: root

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 = ["activerecord-jdbc-adapter", "jruby-openssl",

"activerecord-jdbcmysql-adapter", "jdbc-mysql"]

config.gem_dependencies = true

config.webxml.rails.env = 'production'

end

Page 101: Impacta - Show Day de Rails

Configuração# config/initializers/jruby.rbif defined?(JRUBY_VERSION) && defined?($servlet_context)

# Logger expects an object that responds to #write and #close device = Object.new

def device.write(message) $servlet_context.log(message)

end def device.close; end

# Make these accessible to wire in the log device class << RAILS_DEFAULT_LOGGER

public :instance_variable_get, :instance_variable_set end

old_device = RAILS_DEFAULT_LOGGER.instance_variable_get "@log" old_device.close rescue nil

RAILS_DEFAULT_LOGGER.instance_variable_set "@log", device end

# config/environment.rbconfig.action_controller.session_store = :java_servlet_store if defined?(JRUBY_VERSION)

# config/database.yml

production:

adapter: jdbcmysql

database: tarefas

username: root

password: root

•jruby -S warble config

Page 102: Impacta - Show Day de Rails

InsoshiMichael Hartl

Download

Page 103: Impacta - Show Day de Rails

Iniciando$ sudo gem install rails

$ sudo gem install ferret

$ sudo gem install sqlite3-ruby

$ sudo gem install mysql

$ sudo gem install image_science

$ rake install

$ rake db:test:prepare

$ rake spec

$ rake db:sample_data:reload

$ script/server

Admin:

email: [email protected]

password: admin

Page 104: Impacta - Show Day de Rails

Testes$ rake spec(in /Users/akitaonrails/rails/sandbox/insoshi)...

Finished in 9.798022 seconds

342 examples, 0 failures

$ rake stats(in /Users/akitaonrails/rails/sandbox/insoshi)+----------------------+-------+-------+---------+---------+-----+-------+| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |+----------------------+-------+-------+---------+---------+-----+-------+| Controllers | 1436 | 1125 | 18 | 131 | 7 | 6 || Helpers | 455 | 355 | 0 | 36 | 0 | 7 || Models | 1307 | 766 | 18 | 103 | 5 | 5 || Libraries | 1351 | 772 | 9 | 119 | 13 | 4 || Model specs | 1470 | 1173 | 0 | 6 | 0 | 193 || View specs | 73 | 56 | 0 | 0 | 0 | 0 || Controller specs | 1199 | 1001 | 0 | 5 | 0 | 198 || Helper specs | 143 | 100 | 0 | 0 | 0 | 0 |+----------------------+-------+-------+---------+---------+-----+-------+| Total | 7434 | 5348 | 45 | 400 | 8 | 11 |+----------------------+-------+-------+---------+---------+-----+-------+ Code LOC: 3018 Test LOC: 2330 Code to Test Ratio: 1:0.8

Plugins

NÃO USE FERRET!Prefira Sphinx

Suporte a RSpec

Uploader

MelhorPaginador

Page 105: Impacta - Show Day de Rails

Exemplo: Will Paginate

# app/controllers/forums_controller.rbdef show

@forum = Forum.find(params[:id]) @topics = @forum.topics.paginate(

:page => params[:page])end

<!-- app/views/forums/show.html.erb --><h2>Discussion topics</h2>

<ol class="list forum full">

<%= render :partial => @topics %></ol>

<%= will_paginate(@topics) %>

Analisando$./script/plugin install svn://rubyforge.org//var/svn/visualizemodels/visualize_models

$rake visualize_models(in /Users/akitaonrails/rails/sandbox/insoshi)

Looking at table: schema_infoLooking at table: people

Looking at table: photosLooking at table: communications

Looking at table: connectionsLooking at table: forums

Looking at table: topicsLooking at table: posts

Looking at table: blogsLooking at table: comments

Looking at table: activitiesLooking at table: feeds

Looking at table: preferencesLooking at table: email_verifications

Looking at table: page_viewsGenerated /Users/akitaonrails/rails/sandbox/insoshi/doc/model_overview_neato_hier.png

Generated /Users/akitaonrails/rails/sandbox/insoshi/doc/model_overview_neato_plain.png

Page 106: Impacta - Show Day de Rails

Deploying$gem install passenger$passenger-install-apache2-module

# /etc/apache2/httpd.conf

...LoadModule passenger_module /opt/local/lib/ruby/gems/1.8/gems/passenger-2.0.1/ext/apache2/

mod_passenger.soPassengerRoot /opt/local/lib/ruby/gems/1.8/gems/passenger-2.0.1

PassengerRuby /opt/local/bin/ruby

<VirtualHost *:80> ServerName insoshi.local

DocumentRoot "/Users/akitaonrails/rails/sandbox/insoshi/public" RailsEnv development

RailsAllowModRewrite off <directory "/Users/akitaonrails/rails/sandbox/insoshi/public">

Order allow,deny Allow from all

</directory></VirtualHost>

Page 107: Impacta - Show Day de Rails

Deploying

App

Rails

Ruby

App

Rails

Ruby

App

Rails

Ruby

Apache 2 + Passenger

MySQL

Page 108: Impacta - Show Day de Rails

Phusion

Evoluindo

Page 109: Impacta - Show Day de Rails

Websites

• akitaonrails.com

• nomedojogo.com

• rubyonda.com

• rubyonbr.org

• groups.google.com/group/rails-br

• rubyinside.com

• rubycorner.com

• rubylearning.com

• peepcode.com

• railscasts.com

• headius.blogspot.com

• nubyonrails.topfunky.com

• planetrubyonrails.com

• weblog.rubyonrails.org

Livros

• “Desenvolvendo a Web Ágil com Rails” - Artmed - Dave Thomas

• Beginning Ruby - Peter Cooper

• The Rails Way - Obie Fernandez

• The Ruby Way - Hal Fulton

• Advanced Rails Recipes - Mike Clark

• Deploying Rails Applications - Ezra

Page 110: Impacta - Show Day de Rails

Obrigado!www.akitaonrails.com