Impacta - Show Day de Rails

Post on 12-Sep-2014

5.141 views 2 download

Tags:

description

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

Transcript of 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

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

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

$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

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"

>>

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

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

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

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

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

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

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

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

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)

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

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)

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

Dynamic Typing

Static Dynamic

Weak Strong

Dynamic Typing

Static/Strong Java

Dynamic/Weak Javascript

Dynamic/”Strong” Ruby

“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

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

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

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

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

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

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”

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

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

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)

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

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

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

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

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

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

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

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

# 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

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: "admin", name: "", email:

"admin@admin.com", 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 =>

'admin@admin.com')

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

"admin@admin.com", 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">

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:

"admin@admin.com", 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 #r@a.wk

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

...end

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

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

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

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"

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

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

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

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

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

Anotacao.new@tarefa.anotacoes.build

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

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

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

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

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?

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

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

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

• Testes Unitários: Models

• Testes Funcionais: Controllers

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

Tipos de Teste

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

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

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!

Assertions

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

Assertions

Assertions

Views

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

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

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

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>

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'

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

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

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

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

# 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

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

endend

# test/fixtures/tarefa_mailer/importante

Notificação de Tarefa Importante

Descrição: Dando AulaDuraçã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

# 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

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'

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

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>

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

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

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

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

• 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

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

InsoshiMichael Hartl

Download

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: admin@example.com

password: admin

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

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

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>

Deploying

App

Rails

Ruby

App

Rails

Ruby

App

Rails

Ruby

Apache 2 + Passenger

MySQL

Phusion

Evoluindo

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

Obrigado!www.akitaonrails.com