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