Book: Ruby on Rails: coloque sua aplicação web nos trilhos


© 2012, Casa do Código
Todos os direitos reservados e protegidos pela Lei nº9.610, de 10/02/1998.
Nenhuma parte deste livro poderá ser reproduzida, nem transmitida, sem auto-
rização prévia por escrito da editora, sejam quais forem os meios: fotográficos,
eletrônicos, mecânicos, gravação ou quaisquer outros.
Casa do Código
Livros para o programador
Rua Vergueiro, 3185 - 8º andar
04101-300 – Vila Mariana – São Paulo – SP – Brasil
Casa do Código
Agradecimentos
Monsters are real, and ghosts are real too. They live inside us, and sometimes, they
win.
– Stephen King
Este livro não existiria sem a ajuda dos meus grandes amigos Matheus Bodo,
Willian Molinari, Sérgio Schezar e Vinícius Uzêda, que me acompanharam nesse
processo quase todos os dias, revisando, criticando e opinando minuciosamente o
conteúdo desse livro. Muito obrigado!
Muito obrigado também a família Casa do Código e Caelum, pela oportunidade
de escrever esse livro e pelos ensinamentos, especialmente ao Adriano Almeida, pelo
difícil trabalho de colocar ordem às minhas palavras.
Agradecimentos especiais também ao GURU-SP (Grupo de Usuários Ruby de
São Paulo), à PlataformaTec e aos amigos do ICMC-USP, pois se sei alguma coisa,
devo tudo a eles.
Agradeço também a minha família e amigos, pela força e por tolerarem meses
sem notícias enquanto me mudo para outro país.
Por fim, agradeço principalmente a você leitor, por investir seu tempo a aprender
uma tecnologia que eu pessoalmente gosto tanto. Espero sinceramente que seja uma
jornada divertida e lucrativa ao mesmo tempo!
i
Casa do Código
Sumário
Sumário
1
Introdução
1
1.1
Para quem é este livro . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
1.2
Organização . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
A linguagem Ruby
5
2
Conhecendo Ruby
7
2.1
Instalação do Ruby e Rails . . . . . . . . . . . . . . . . . . . . . . . . .
9
2.2
Primeiros passos com Ruby
. . . . . . . . . . . . . . . . . . . . . . . .
11
2.3
Tipos e estrutura de dados . . . . . . . . . . . . . . . . . . . . . . . . .
11
2.4
Fluxos e laços . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.5
Funções, blocos, lambdas e closure . . . . . . . . . . . . . . . . . . . .
39
2.6
Classes e módulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
51
2.7
Bibliotecas e RubyGems . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
2.8
Fim! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Ruby on Rails
71
3
Conhecendo a aplicação
73
3.1
Arquitetura de aplicações web . . . . . . . . . . . . . . . . . . . . . . .
74
3.2
Recursos ao invés de páginas . . . . . . . . . . . . . . . . . . . . . . . .
74
3.3
Recursos no Colcho.net . . . . . . . . . . . . . . . . . . . . . . . . . . .
75
3.4
Conhecendo os componentes . . . . . . . . . . . . . . . . . . . . . . . 76
3.5
Os modelos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
77
iii
Sumário
Casa do Código
3.6
Controle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
3.7
Apresentação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
3.8
Rotas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
3.9
Suporte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
3.10 Considerações finais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
4
Primeiros passos com Rails
81
4.1
Gerar o alicerce da aplicação . . . . . . . . . . . . . . . . . . . . . . . .
81
4.2
Os ambientes de execução . . . . . . . . . . . . . . . . . . . . . . . . . 84
4.3
Os primeiros comandos . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
4.4
Os arquivos gerados pelo scaffold . . . . . . . . . . . . . . . . . . . . .
91
Mãos à massa
97
5
Implementação do modelo para o cadastro de usuários
99
5.1
O usuário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
5.2
Evite dados errados. Faça validações . . . . . . . . . . . . . . . . . . . 103
6
Tratando as requisições Web
111
6.1
Roteie as requisições para o controle . . . . . . . . . . . . . . . . . . . 111
6.2
Integre o controle e a apresentação . . . . . . . . . . . . . . . . . . . . 117
6.3
Controle o mass-assignment . . . . . . . . . . . . . . . . . . . . . . . . 123
6.4
Exibição do perfil do usuário . . . . . . . . . . . . . . . . . . . . . . . . 125
6.5
Permita a edição do perfil . . . . . . . . . . . . . . . . . . . . . . . . . . 127
6.6
Reaproveite as apresentações com partials . . . . . . . . . . . . . . . . 128
6.7
Mostre os erros no formulário . . . . . . . . . . . . . . . . . . . . . . . 131
6.8
Configure a ação raiz (root) . . . . . . . . . . . . . . . . . . . . . . . . 133
7
Melhore o projeto
137
7.1
Lição obrigatória: sempre aplique criptografia para armazenar senhas 137
7.2
Como adicionar plugins ao projeto? . . . . . . . . . . . . . . . . . . . . 138
7.3
Migração da tabela users . . . . . . . . . . . . . . . . . . . . . . . . . . 139
7.4
Melhoria de templates e CSS . . . . . . . . . . . . . . . . . . . . . . . . 142
7.5
Trabalhe com layout e templates para melhorar sua apresentação . . . 145
iv
Casa do Código
Sumário
7.6
O que é o Asset Pipeline? . . . . . . . . . . . . . . . . . . . . . . . . . . 148
7.7
Criando os novos stylesheets . . . . . . . . . . . . . . . . . . . . . . . . 150
7.8
Feedback em erros de formulário . . . . . . . . . . . . . . . . . . . . . 156
7.9
Duplicação de lógica na apresentação nunca mais. Use os Helpers . . 157
8
Faça sua aplicação falar várias línguas
161
8.1
O processo de internacionalização (I18n) . . . . . . . . . . . . . . . . . 161
8.2
Traduza os templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
8.3
Extra: alterar o idioma do site . . . . . . . . . . . . . . . . . . . . . . . 170
9
O cadastro do usuário e a confirmação da identidade
177
9.1
Entenda o ActionMailer e use o MailCatcher . . . . . . . . . . . . . . 177
9.2
Templates de email, eu preciso deles? . . . . . . . . . . . . . . . . . . . 179
9.3
Mais emails e a confirmação da conta de usuário . . . . . . . . . . . . 183
9.4
Um pouquinho de callbacks para realizar tarefas pontuais . . . . . . . 184
9.5
Roteamento com restrições . . . . . . . . . . . . . . . . . . . . . . . . . 187
9.6
Métodos espertos e os finders dinâmicos . . . . . . . . . . . . . . . . . 188
9.7
Em resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
10 Login do usuário
193
10.1
Trabalhe com a sessão . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
10.2 Controles e rotas para o novo recurso . . . . . . . . . . . . . . . . . . . 196
10.3 Sessões e cookies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
10.4 Consultas no banco de dados
. . . . . . . . . . . . . . . . . . . . . . . 206
10.5 Escopo de usuário confirmado . . . . . . . . . . . . . . . . . . . . . . . 214
11 Controle de acesso
217
11.1
Helpers de sessão
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
11.2
Não permita edição do perfil alheio . . . . . . . . . . . . . . . . . . . . 224
11.3
Relacionando seus objetos . . . . . . . . . . . . . . . . . . . . . . . . . 227
11.4
Relacione quartos a usuários . . . . . . . . . . . . . . . . . . . . . . . . 229
11.5
Limite o acesso usando relacionamentos . . . . . . . . . . . . . . . . . 234
11.6
Exibição e listagem de quartos . . . . . . . . . . . . . . . . . . . . . . . 238
v
Sumário
Casa do Código
12 Avaliação de quartos, relacionamentos muitos para muitos e organização
do código
245
12.1
Relacionamentos muitos-para-muitos . . . . . . . . . . . . . . . . . . 247
12.2 Removendo objetos sem deixar rastros . . . . . . . . . . . . . . . . . . 253
12.3
Criando avaliações com pitadas de AJAX . . . . . . . . . . . . . . . . . 254
12.4 Diga adeus a regras complexas de apresentação: use presenters . . . . 263
12.5
jQuery e Rails: fazer requisições AJAX ficou muito fácil . . . . . . . . 267
12.6 Média de avaliações usando agregações . . . . . . . . . . . . . . . . . . 269
12.7 Aplicações modernas usam fontes modernas . . . . . . . . . . . . . . 274
12.8 Eu vejo estrelas - usando CSS e JavaScript para melhorar as avaliações 278
12.9 Encerrando . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
13 Polindo o Colcho.net
287
13.1
Faça buscas textuais apenas com o Rails . . . . . . . . . . . . . . . . . 287
13.2
URLs mais amigáveis através de slugs . . . . . . . . . . . . . . . . . . . 292
13.3
Paginação de dados de forma descomplicada . . . . . . . . . . . . . . 295
13.4 Upload de fotos de forma simples . . . . . . . . . . . . . . . . . . . . . 299
13.5
Coloque a aplicação no ar com o Heroku . . . . . . . . . . . . . . . . . 303
14 Próximos passos
309
Índice Remissivo
315
Bibliografia
315
vi
Capítulo 1
Introdução
Quem, de três milênios, / Não é capaz de se dar conta / Vive na ignorância, na
sombra, / À mercê dos dias, do tempo.
– Goethe
Se você desenvolve para a Web, provavelmente já ouviu falar sobre Ruby on
Rails. O Ruby on Rails (ou também apenas “Rails”) é um framework open-source
para desenvolvimento de aplicações web, criado por David Heinemeier Hansson (ou
“DHH”). O framework, escrito na linguagem Ruby, foi extraído de um produto de
sua empresa, o Basecamp® (http://basecamp.com) em 2003.
Desde então ele ficou muito famoso, levando também a linguagem Ruby, ante-
riormente apenas conhecida no Japão e em poucos lugares dos Estados Unidos, ao
mundo todo. Mas por que cresceu tanto? O que a linguagem e o framework trouxe-
ram de novo para sair do anonimato e praticamente dominar o mercado de startups
nos Estados Unidos e no mundo?
Vamos primeiro à linguagem Ruby. A linguagem Ruby foi criada pelo extrema-
mente talentoso programador Yukihiro “Matz” Matsumoto. O objetivo dele ao criar
1.1. Para quem é este livro
Casa do Código
o Ruby em 1995 foi a felicidade e o prazer de quem programa, ao invés de perfor-
mance ou algum outro aspecto técnico. Ele usou Perl como inspiração, com várias
pitadas de Smalltalk. Veremos exemplos dessas influências, e também o que significa
esta “felicidade para o programador” no capítulo 2.
Quanto ao Rails, na época em que foi lançado, trouxe uma visão diferente ao
desenvolvimento Web. Naquele momento, desenvolver para Web era cansativo, os
frameworks eram complicados ou resultavam em sistemas difíceis de se manter e de
baixa qualidade.
O DHH, ao desenvolver o Basecamp, pensou principalmente nos seguintes as-
pectos:
• “Convention over configuration”, ou convenção à configuração: ao invés de
configurar um conjunto de arquivos XML, por exemplo, adota-se a convenção
e apenas muda-se o que for necessário;
• “Dont Repeat Yourself ”, ou “não se repita": nunca você deve fazer mais de uma
vez o que for necessário (como checar uma regra de negócio);
• Automação de tarefas repetidas: nenhum programador deve perder tempo em
tarefas repetitivas e sim investir seu tempo em resolver problemas interessan-
tes.
Esses conceitos são amplamente explorados no clássico The Pragmatic Program-
mer: From Journeyman to Master [1], leitura recomendada. Esta soma de tecnolo-
gias e práticas são bastante prazerosas de se trabalhar; é possível realizar muito com
pouco tempo e você verá, capítulo a capítulo, como essas ideias são representadas
em todos os aspectos do framework.
1.1
Para quem é este livro
O objetivo deste livro é apresentar um pouco da linguagem Ruby e aprender os pri-
meiros passos a desenvolver com o framework Ruby on Rails. Mais além, vamos
aprender a desenvolver aplicativos com Rails. Algumas vezes usaremos inclusive
componentes feitos em Ruby puro. Esses momentos são extremamente importantes
para aprendermos também algumas boas práticas.
Tendo isso em mente, este livro serve para pessoas que:
• Não conhecem ambas as tecnologias, ou conhecem apenas Ruby, mas desejam
conhecer o Rails;
2
Casa do Código
Capítulo 1. Introdução
• Já conhecem Rails, mas não estão confortáveis em como fazer aplicações bem
organizadas;
• Já conhecem Rails superficialmente, mas querem aprimorar conhecimentos e
boas práticas.
1.2
Organização
Este livro foi construído em três partes. A primeira parte é dedicada a entender a
linguagem Ruby. Nessa parte vamos aprender desde a sintaxe até boas práticas com
a linguagem usando exemplos práticos.
A segunda parte é dedicada a entender o contexto que o Ruby on Rails trabalha.
Essa é a parte teórica, na qual vamos entender quais são os principais conceitos por
trás do framework. Vamos ver também, em alto nível, quais são os componentes do
Rails e como eles se relacionam.
A terceira parte é onde vamos fazer a aplicação com Rails. Passo a passo, va-
mos implementar uma aplicação do início ao fim e, durante a construção de cada
funcionalidade, aprender como juntar as partes do framework. Vamos revisitar fun-
cionalidades, aprimorando-as, e mostrar como é o processo de criação de uma apli-
cação real, de forma que você aprenda uma das possíveis maneiras de construir suas
aplicações no futuro.
Vamos construir um aplicativo chamado Colcho.net. O Colcho.net é um site
para você publicar um espaço sobrando na sua casa para hospedar alguém por uma
ou mais noites. O site vai ter:
• Cadastro de usuário, com encriptação de senha;
• Login de usuários;
• Envio de emails;
• Internacionalização;
• Publicação e administração de quartos;
• Avaliação de quartos e ranking;
• Busca textual;
• URL slugs;
3
1.2. Organização
Casa do Código
• Uploads e thumbnails de fotos.
Embora este livro tenha sido construído de forma que a leitura progressiva seja
fácil, ele pode servir como consulta. Vamos dividir o sistema que será construído em
algumas funcionalidades principais e atacá-las individualmente em cada capítulo.
4
Parte I
A linguagem Ruby
Capítulo 2
Conhecendo Ruby
Não reze por uma vida fácil, mas sim para ter forças para uma vida difícil.
– Bruce Lee
Ruby é uma linguagem de script muito interessante. Como vimos na Introdu-
ção, Ruby tem como parentesco Perl e várias pitadas de Smalltalk, além de outras
características “Lispianas”.
Isso se reflete de várias formas na linguagem. Primeiramente, Ruby é uma lin-
guagem dinamicamente “tipada”, ou seja, não precisamos declarar os tipos dos obje-
tos e variáveis, característica comum de algumas linguagens de script, como Python
e PHP. Ruby também é uma linguagem orientada a objetos, porém com proprieda-
des não muito comuns. Uma delas, por exemplo é o fato de que, como em Smalltalk,
chamadas de métodos nada mais são do que envio de mensagens a objetos, e isso
é refletido na forma que podemos implementar programas. Para demonstrar esses
recursos, vamos a um exemplo onde chamamos um método em um objeto:
shopping_cart.clear
O mesmo método pode ser chamado da seguinte forma:
Casa do Código
shopping_cart.send 'clear'
Neste trecho de código, estamos enviando uma mensagem de nome ‘clear’, que
por sua vez chama o método clear do objeto shopping_cart. Note também o uso
de snake_case, ao invés de camelCase.
O que é camelCase e snake_case?
CamelCase e snake_case são duas formas bastante populares de escre-
ver código. Na forma CamelCase, diferenciamos cada palavra via letras
maiúsculas, sem separar as palavras. Já o snake_case, deixamos todas as
palavras em minúsculas as separamos usando underscore (_).
A plataforma Ruby também oferece ao programador várias ferramentas para que
um programa possa descobrir informações sobre si mesmo. Por exemplo, é possível
descobrir se uma constante foi declarada ou se um objeto responde a um método.
Veja o seguinte exemplo, no qual verificamos se o objeto shopping_cart responde
ao método clear:
shopping_cart.respond_to? 'clear' # => true
Query methods
Query methods são métodos terminados em ?. Eles essencialmente só
devem ser usados quando queremos saber se o resultado é verdadeiro ou
falso, independentemente do seu resultado de fato. São usados basica-
mente com if, unless e afins.
Neste exemplo, o próprio programa verifica se o objeto responde ao método
clear. Isso pode parecer estranho no início para quem nunca viu uma linguagem
reflexiva antes, mas isso dá bastante poder ao programador que deseja fazer soluções
rebuscadas.
Neste capítulo, iremos aprender um pouco da linguagem Ruby para que seja pos-
sível entender e criar aplicações simples em Rails. É importante ressaltar que é ainda
necessário aprender mais da linguagem, pois este livro vai apenas te ensinar o básico
8
Casa do Código
Capítulo 2. Conhecendo Ruby
o suficiente para dar o pontapé inicial. Mas para sermos bons programadores, preci-
samos sempre saber a fundo a linguagem que trabalhamos. No capítulo 14 “Próximos
Passos” você saberá onde buscar mais informações depois de terminar este livro.
2.1
Instalação do Ruby e Rails
Existem diversas implementações de Ruby, como JRuby (http://jruby.org/) e Rubi-
nius (http://rubini.us), mas iremos usar a versão MRI, ou Matz Ruby Interpreter, a
implementação canônica de Ruby, criada pelo autor original.
Instalação no OSX
Para quem está iniciando com Ruby on Rails, a forma mais simples é usar o RVM,
ou Ruby enVironment Manager. Antes de instalar o RVM, porém, é necessário ins-
talar os pacotes de desenvolvimento da Apple.
Se você não tem o Xcode, vá na página do Apple Developer Tools (https://
developer.apple.com/downloads/index.action) e baixe o ‘Command Line Tools for
Xcode’, versão mais atual e siga os passos de instalação. Se tiver o Xcode mais re-
cente, você pode abri-lo, ir nas Preferências > Downloads e instalar o ‘Command
Line Tools’.
Depois de instalar o Command Line Tools, basta executar o seguinte comando
no terminal:
curl -L get.rvm.io | bash -s stable --rails
source $HOME/.rvm/scripts/rvm
Aproveite e vá tomar um café, vai demorar um pouco. Ao finalizar, este comando
deixará instalado tudo que você precisa para começar a desenvolver com Ruby on
Rails.
Instalação no Linux
Tanto quanto usuários OSX, a forma mais simples de começar com Ruby on Rails
no Linux é usar o RVM, ou Ruby enVironment Manager.
Primeiramente é necessário instalar os pacotes de desenvolvimento do seu sis-
tema. Procure o manual da sua distribuição para saber como instalar. No Ubuntu,
por exemplo, basta instalar o pacote build-essential e mais algumas outras bibli-
otecas de dependências:
9

2.1. Instalação do Ruby e Rails
Casa do Código
sudo apt-get install build-essential libreadline-dev libssl-dev curl \
libsqlite3-dev
Em seguida, basta executar:
curl -L get.rvm.io | bash -s stable --rails
source $HOME/.rvm/scripts/rvm
Este comando irá instalar o RVM e também, automaticamente, irá instalar a ver-
são mais atual do Ruby e do Rails e todas as dependências. Aproveite para tomar um
café ou um chá, pois demora um pouco.
Instalação no Windows
Para instalar o ambiente de Ruby e Rails no Windows, o jeito mais fácil é usar
o RailsInstaller (http://www.railsinstaller.org), que já instala o Ruby versão MRI e
todas as dependências do Rails. Para instalar o ambiente, basta baixar o instalador e
seguir as instruções apresentadas. Ao completar a instalação, você terá um console
para executar comandos, como observado na imagem 2.1.
Figura 2.1: Console do RailsInstaller
Para acessá-lo novamente, basta usar o atalho ‘RailsInstaller > Command
Prompt with Ruby on Rails’.
10
Casa do Código
Capítulo 2. Conhecendo Ruby
2.2
Primeiros passos com Ruby
Para começar, o IRB, ou Interactive Ruby Shell é onde é possível executar pequenos
trechos de código. Recomenda-se o uso do IRB para testar pequenas ideias ou sin-
taxe da linguagem, para que, no futuro, seja incorporado dentro de um programa
completo.
Ao abri-lo, você vai se deparar com um terminal parecido com o seguinte:
irb(main):001:0>
Nele, você pode digitar trechos de código ruby. Ele vai ser avaliado e o retorno
dessa linha é impresso em seguida:
irb(main):001:0> a = "0"
=> "0"
irb(main):002:0> a
=> "0"
Para sair, basta digitar exit.
Notação
Para facilitar a leitura, a partir de agora não será mais incluso o prompt
do IRB nos exemplos de código, porém o resultado da operação será co-
locado adiante de cada trecho de código, usando um comentário da se-
guinte maneira:
# => Saída
2.3
Tipos e estrutura de dados
A primeira característica que vemos nessas linhas em Ruby é que a linguagem é de
tipagem dinâmica, ou seja, não precisamos declarar nenhum tipo para criar uma
variável. Dessa forma, é possível fazer o seguinte:
a = "string"
# => "string"
a = 100
# => 100
11
2.3. Tipos e estrutura de dados
Casa do Código
Strings
Strings, tal como outras linguagens de script, são fáceis de serem criadas, com
uma notação literal usando aspas duplas ou simples. Porém, como tudo em Ruby é
um objeto, é possível chamar métodos diretamente à notação literal:
"this is sparta".upcase
# => "THIS IS SPARTA"
É também possível compor strings maiores fazendo algumas operações:
"hello" + " " + "world"
# => "hello world"
O interessante também é que Strings reagem à multiplicação. A multiplicação
espera um inteiro como segundo parâmetro:
"." * 10
# => ".........."
É possível também acumular strings usando <<:
greetings = "Hello"
greetings << " "
greetings << "World"
puts greetings # Hello World
Interpolação de strings
Outro fato importante de strings é a interpolação, ou a combinação de código
para a composição de strings. É importante ressaltar que este tipo de interpolação
de strings só funciona com aspas duplas:
name = "Pedro"
"Tudo certo, #{name}?" # Tudo certo, Pedro?
'Tudo certo, #{name}?' # "Tudo certo, \#{name}?"
A interpolação vai ser avaliada apenas uma vez antes de qualquer outra operação
com a string resultante:
12
Casa do Código
Capítulo 2. Conhecendo Ruby
i = 0
"#{i = i + 1} " * 3 # "1 1 1 " e não "1 2 3 "
Há outra maneira mais complexa de fazer interpolação de Strings. Leitores que
conhecem C e Java vão se lembrar da função sprintf ao fazer interpolação com o
operador %. Neste caso, é possível usar aspas simples:
'Custo total: $%.2f' % 100 # Custo total: $100.00
Para a relação completa de como a formatação deste operador funciona,
veja a documentação online:
http://www.ruby-doc.org/core-1.9.3/Kernel.html#
method-i-sprintf .
Acessando caracteres e substrings
Strings, no final das contas, são cadeias de caracteres, como se fossem Arrays.
Portanto, é possível acessar caracteres via método [] com o índice:
a = "hello world"
a[0] # 'h'
a[6] # 'w'
a[-1] # "d" - Valores negativos contam do fim ao começo
a[-2] # "l"
Este acessor leva em conta o encoding atual, portanto, em encodings multibyte,
ou seja, encodings que podem usar mais de um byte para representar um caractere,
o acessor irá retornar o caractere em si e não o byte naquela posição:
# encoding: utf-8
a = "Olá, tudo bom com você?"
puts a[2]
# á
puts a.bytesize
# 25
puts a.length
# 23
Para acessar substrings, usamos um outro tipo de variável, o Range, ou 2 índices
indicando o começo e o fim:
a = "Hello world"
a[6, 10] # => "world"
a[6..10] # => "world"
a[-5..-1] # => "world"
a[-1..-5] # => "" - quando não é possível, o método retorna string vazia.
13
2.3. Tipos e estrutura de dados
Casa do Código
Inteiros e Floats
Inteiros e floats possuem representações literais como em qualquer linguagem:
a = 100
a = 100000000
# Fica difícil ler com muitos zeros
a = 100_000_000 # Pode usar _ para separar e possui o mesmo efeito
a = 100.0
100.0.to_i
# 100 - Conversão de float para inteiro
100.to_f
# 100.0 - Conversão de inteiro para float
Quando se faz operações entre os dois tipos, especialmente divisões, acontece a
coerção de tipos, ou seja, um inteiro é automaticamente convertido para float quando
há um float envolvido na conta.
Quando dividimos um inteiro com outro, o resultado será um inteiro truncado,
removendo-se a “parte quebrada”, ou seja, remove-se os decimais, e converte-se a um
inteiro. Por exemplo:
100 / 3 # => 33
Para obter um valor real/float, é necessário convertê-lo antes para float:
100.to_f / 3 # => 33.3333333...336
Cuidado com floats!
A representação de números reais usada é o IEEE-754, a forma padrão
de representar números reais em binários, usada não só por Ruby, mas
também por C, Java e JavaScript. Porém, este padrão possui um problema
perigoso de arredondamento:
0.0004 - 0.0003 == 0.0001 #=> false
Este é um problema sério, pois muitas pessoas não conhecem ou não
sabem deste problema e usam floats para valores financeiros, o que é er-
rado. Para isso, usa-se a classe BigDecimal, do conjunto de bibliotecas
padrão do Ruby. Apesar de ser muito mais lenta que floats, eles são pre-
cisos e se tratando de cálculos financeiros, performance geralmente não
é um problema, se comparado com aplicações científicas.
14
Casa do Código
Capítulo 2. Conhecendo Ruby
Constantes
Constantes em Ruby são todas as variáveis que começam com uma letra maiús-
cula, independente do restante. Veja o seguinte exemplo:
Pi = 3.14159
Porém, a comunidade Ruby adota um padrão de nomenclatura. Como classes
e módulos também usam constantes para identificá-los, adotamos CamelCase para
classes e MAIÚSCULAS para valores, de forma a distingui-los facilmente.
EULER = 2.718 # Constante da forma correta
Pi
# Classe?
Cuidado! Constantes não são constantes!
Infelizmente é possível alterar valores de constantes no Ruby. Ao ten-
tarmos fazer isso, porém, o interpretador irá exibir uma mensagem de
alerta:
PHI = 1.618
PHI = 999
# (irb):9: warning: already initialized constant Pi
# => 999
PHI
# => 999
Arrays
Arrays em Ruby possuem representação literal, parecido com Python e Perl e
também podem conter qualquer tipo de objetos:
a = [1, 2, 1+2]
# [1, 2, 3]
a << 4
# [1, 2, 3, 4]
a << "string!"
# [1, 2, 3, 4, "string"]
15
2.3. Tipos e estrutura de dados
Casa do Código
O comportamento de Arrays é bem parecido com strings, ou seja, é possível usar
Ranges e o método [] para acessar elementos diretamente:
a = ["a", "b", "c", "d", "e"]
a[0]
# 'a'
a[0..2]
# ["a", "b", "c"]
a[0, 2]
# ["a", "b"] - Atenção a este exemplo!
É possível fazer atribuição de elementos de Array de uma forma bastante conve-
niente:
list = ["a", "b", "c"]
first, second = list
first # => "a"
second # => "b"
Usando o splat operator, é possível obter o restante de toda a lista:
list = ["a", "b", "c"]
first, *tail = list
first # => "a"
tail # => ["b", "c"]
Array de strings são chatas de digitar, portanto o Ruby tem uma maneira para
facilitar isso:
a = %w{a b c d e}
a # ["a", "b", "c", "d", "e"]
# Porém, palavras com espaços são problemáticas
b = %w{"long words" small tiny}
# ["\"long", "words\"", "small", "tiny"]
# Palavras com espaços devem ser "escapadas"
c = %w{long\ words small tiny}
# ["long words", "small", "tiny"]
Arrays também fazem operações matemáticas:
a = ["a", "b", "c"]
b = ["a", 2, 3]
16
Casa do Código
Capítulo 2. Conhecendo Ruby
a + b # ["a", "b", "c", "a", 2, 3]
a - b # ["b", "c"]
b - a # [2, 3]
c = [1, 2]
c * 3 # [1, 2, 1, 2, 1, 2]
Além dessas operações mais canônicas, Arrays ainda suportam interseção e
união de conjuntos:
a = [1, 2, 3]
b = [3, 4, 5]
a + b
# [1, 2, 3, 3, 4, 5]
a | b
# [1, 2, 3, 4, 5] - Na união, elementos duplicados são removidos
a & b
# [3] - Na interseção, apenas os repetidos ficam
Arrays não deixam de ser objetos em Ruby, portanto possuem vários métodos
interessantes. No exemplo abaixo vemos o uso dos métodos mais comuns:
[1, 2, 3].reverse
# [3, 2, 1] - inverte o array
['acerola', 'laranja'].join(' e ')
# "acerola e laranja" - concatena strings com o parâmetro passado
[10, 20, nil, '', false, true].compact
# [10, 20, '', false, true] - remove nils
[6, 3, 9].sort # [3, 6, 9] - ordena os resultados
[3, 3, 9].uniq # [3, 9] - apenas os elementos únicos
[[3], 2, 1].flatten # [3, 2, 1] - achata listas internas
a = [1, 2, 3]
a.pop
# 3 - sai o último elemento
a
# [1,2]
a.shift # 1 - sai o primeiro elemento
a
# [2]
17
2.3. Tipos e estrutura de dados
Casa do Código
Note que a maioria desses métodos possui a versão bang, ou seja, terminada em
exclamação, que modifica o conteúdo da própria variável, ao invés de retornar uma
cópia modificada:
a = [[3], 2, 1]
b = a.flatten
a
# => [[3], 2, 1]
b
# => [3, 2, 1]
b = a.flatten!
a
# => [3, 2, 1],
b
# => [3, 2, 1]
18
Casa do Código
Capítulo 2. Conhecendo Ruby
Métodos bang (!)
Em Ruby, é possível criar métodos que terminam em ! e são usados em
principalmente dois casos:
1) Métodos que modificam estado interno do objeto, como vimos no
exemplo anterior;
2) Métodos que, ao falharem, disparam uma exceção.
No uso de métodos do Rails é possível observar o caso em que a versão
“bang” do método resulta em uma exceção:
Post.find_by_title 'Unexistent post' # => nil
Post.find_by_title! 'Unexistent post'
# ActiveError::RecordNotFound
Hashes
Ruby também possui representação literal para Hashes, tal como Python (apesar
de serem conhecidos naquelas terras por dicts ou dicionários). Hashes são estrutu-
ras de dados similares a Array, porém ao invés de acessarmos valores com índices
numéricos, podemos usar qualquer objeto:
frequency = Hash.new
frequency["hello"] = 1
frequency["world"] = 2
frequency[1] = 10
É possível criar hashes também com uma sintaxe mais cômoda:
# equivalente ao exemplo anterior
frequency = { "hello" => 1, "world" => 2, 1 => 10 }
19
2.3. Tipos e estrutura de dados
Casa do Código
Ainda é possível usar uma terceira sintaxe, usada para criar hashes contendo
símbolos como chaves (veremos mais detalhes sobre símbolos ainda nesta seção).
Veja o exemplo a seguir:
# Sintaxe clássica para hashes tendo símbolos como chaves
frequency = { :hello => 1, :world => 2 }
# Nova sintaxe
frequency = { hello: 1, world: 2 }
Uso de sintaxe de Hash
Não há nenhuma diferença entre as duas sintaxes de hashes com símbo-
los, ou seja, elas são puramente estéticas. Neste livro, vamos nos ater à
sintaxe clássica apenas para manter consistência com outras hashes, fi-
cando à critério do leitor escolher qual prefere usar. Lembre-se apenas
de ser consistente.
Hashes possuem métodos bem úteis. Alguns dos mais usados estão exemplifica-
dos em seguida:
frequency = { "hello" => 1, "world" => 2 }
frequency.keys
# ["hello", "world"]
frequency.values
# [1, 2]
frenquency.has_key?("hello")
# true
frequency.has_value?(3)
# false
Menção honrosa
É comum lidarmos com integrações com outros sistemas e APIs que retornam
construções complexas em hashes e quase sempre não são consistentes. Vejamos um
exemplo fictício abaixo:
20
Casa do Código
Capítulo 2. Conhecendo Ruby
user_data = {
'email' => '[email protected]',
'full_name' => 'Cicrano'
}
Imaginemos agora que queiramos acessar os atributos email, full_name e
address. address, porém, não está presente no exemplo de hash acima. Dessa
forma, ao fazermos o código abaixo, temos um problema:
address = user_data['address']
address.strip
# NoMethodError: undefined method `strip' for nil:NilClass
Por esse motivo, é bastante comum fazermos proteção contra nil usando uma
expressão idiomática. Dessa forma, nosso código fica mais limpo pois não temos
que ficar checando a existência de dados. Aplicando a expressão idiomática, temos
o resultado abaixo (para mais detalhes sobre essa expressão, veja na seção 2.4):
address = user_data['address'] || 'vazio'
address.strip
# "vazio"
Este caso é muito comum. Através do pouco usado método #fetch, temos uma
outra maneira de retornar um valor default, muito mais legível e sem problemas com
confusão de precedência de parâmetros:
address = user_data.fetch('address', 'vazio')
address.strip
# vazio
Notação para métodos
A comunidade Ruby adotou duas notações importantes quando se tra-
tam de documentação de métodos. Métodos começando com # indicam
que são aplicados à instâncias daquela classe, por exemplo Hash#fetch.
Métodos começando com : : ou . indicam métodos de classe, por exem-
plo Time::now.
O Ruby tem um comportamento às vezes indesejável de retornar sempre nil
quando a chave não existe:
21
2.3. Tipos e estrutura de dados
Casa do Código
user_data = {
'email' => '[email protected]',
'full_name' => 'Cicrano'
}
user_data['address']
# nil
Este caso é o que chamamos de falha silenciosa, ou seja, o código falhou, po-
rém, como não há erro, a execução do código segue adiante, ficando difícil perceber
quando há bugs. Dessa forma, quando fizer sentido tornar essa falha mais aparente,
podemos usar o #fetch sem fallback, disparando uma exceção KeyError quando a
chave não existe:
user_data = {
'email' => '[email protected]',
'full_name' => 'Cicrano'
}
user_data.fetch('address')
# KeyError: key not found: "address"
Símbolos
Símbolos são strings especiais. Símbolos são usados internamente pelo inter-
pretador MRI para localizar o método a ser executado em um objeto, portanto sua
implementação é tal que a torna imutável e única na instância do interpretador Ruby.
Ou seja, uma vez um símbolo mencionado e criado, ele vai existir por todo o período
de vida de execução do interpretador e nunca vai ser coletado pelo garbage collector:
a = "123"
b = "123"
a.object_id
# => 7022...7060
b.object_id
# => 7022...1360
a = :hello
b = :hello
22
Casa do Código
Capítulo 2. Conhecendo Ruby
a.object_id
# => 456328
b.object_id
# => 456328
O que é Garbage collector?
O garbage-collector é um mecanismo importante e complexo que faz
parte do interpretador do Ruby. Ele é capaz de rastrear todos os recursos
usados e não usados no sistema, de forma a liberar memória não utili-
zada ou alocar mais memória para seu programa quando necessário.
Em termos práticos, no Ruby 1.9.3, versão que usamos, isso não significa muita
coisa. Porém, símbolos possuem outro valor: são usados no lugar de strings para
serem identificadores especiais quando estamos nos referindo a métodos:
"string".respond_to? :upcase
# true
Também acontece para chaves de Hash, com uma pequena diferença:
• Símbolos devem ser usados quando nos tratamos de metadado e não dado em
si. O que isso quer dizer? Usamos símbolos quando estamos descrevendo o
tipo do dado e não dando um valor possível;
• Strings devem ser usadas quando a chave é um valor e não um descritor de
dados.
Exemplificando as duas regras:
# Exemplo 1 - usando símbolos
{
:name => 'Fulano',
:email => '[email protected]',
}
# Exemplo 2 - usando ambos
{
:people => {
'Fulano' => {
23
2.3. Tipos e estrutura de dados
Casa do Código
:email => '[email protected]'
}
}
}
Podemos facilmente converter strings em símbolos e vice-versa:
"um_simbolo".to_sym
# :um_simbolo
:um_simbolo.to_s
# "um_simbolo"
"E se a palavra for muito grande?".to_sym
# :"E se a palavra for muito grande?"
# Ainda é possível usar a notação de string para fazer interpolação!
method = 'flatten'
:"#{method}!"
# :flatten!
Ranges
Já vimos Ranges algumas vezes em outros exemplos. Range é um tipo interes-
sante e simples. Um objeto Range possui um início, um fim e dois ou três pontos
entre eles:
1..5 # Números inteiros entre 1 a 5, com o 5 inclusive
1...5 # Números inteiros entre 1 a 4, o 5 fica de fora
'a'..'e'
# Letras entre 'a' e 'e'
'a'...'e' # Letras entre 'a' e 'd', o 'e' fica de fora
A maior utilidade de Ranges é testar se um valor está em um intervalo:
valid_years = 1920..2010
valid_years.include? 1998 # true
valid_years.include? 1889 # false
Há um detalhe importante sobre Ranges. Eles podem ser discretos ou contínuos.
Por exemplo, ranges com floats são contínuos, ou seja, não existe um número finito
24
Casa do Código
Capítulo 2. Conhecendo Ruby
de valores inclusos dentro deste Range. Já um range discreto, existe um número
finito e enumerável de elementos.
Isso se reflete em alguns métodos, inclusive o bastante útil #to_a:
years = 2000..2012
years.to_a
# [2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
#
2010, 2011, 2012]
length = 1.0..5.0
length.to_a
# TypeError: can't iterate from Float
true, false e nil
Diferente de outras linguagens, Ruby não possui um tipo “booleano”. Tudo em
Ruby é um objeto, e true, false e nil não fogem desta regra. Portanto, true é uma
instância singleton (ou seja, apenas uma única instância dessa classe irá existir em
todo o ciclo de vida de uma aplicação) da classe TrueClass, false, da FalseClass
e nil da NilClass. Dessa forma, estes valores não são comparáveis com 0 e 1, como
é comum em outras linguagens.
1 == 1
# true
1 > 1
# false
if "object"
puts "Objetos em geral resultam em 'true'"
end
# Objetos em geral resultam em 'true'
if 0
puts "0 é um objeto, portanto, true!"
end
# 0 é um objeto, portanto, true!
puts "nil é false em if" if nil
puts "falso é... falso." if false
25
2.4. Fluxos e laços
Casa do Código
Para verificar se um objeto é nil, é possível testar usando o método #nil?, im-
plementado em todos os objetos:
a = nil
a.nil? # true
b = 1
b.nil? # false
2.4
Fluxos e laços
if
Como toda linguagem de programação imperativa, temos controles de fluxos ine-
rente na sintaxe.
a = "0"
if a == "0"
puts "É true!"
else
puts "É falso :("
end
# É true!
# => nil
Uma coisa interessante que aconteceu no exemplo de código acima é que o IRB
nos exibiu não uma, mas duas saídas. A primeira é a impressão do texto na tela,
papel da função puts. Porém, como no primeiro trecho, temos o resultado com o
símbolo =>, denotando o que o bloco de código em si retornou. Isso dá-se ao fato de
que todo bloco de código Ruby retorna alguma coisa:
a = "0"
b = if a == "0"
1
else
2
end
# => 1
26
Casa do Código
Capítulo 2. Conhecendo Ruby
puts b # 1
puts a # "0"
Outro exemplo:
a = 0
if a == 0
10
else
20
end * 100
# => 1000
Note que, em Ruby, blocos de if terminam sempre com o end, com o else sendo
opcional. Existe outra maneira de se fazer if no Ruby que é muito útil: o if como
modificador, herança da linguagem Perl:
puts message if message # Imprime message se ela estiver definida
O if como modificador é muito conveniente e, dependendo de como for usado,
torna o código bastante legível. Portanto é encorajado para se fazer trechos curtos
(ou seja, uma linha) de código.
Para compor cláusulas em um if, podemos usar os operadores booleanos:
• and - Clássico “e”, ou seja, sempre vai avaliar as duas expressões na direita e na
esquerda e verificar se ambas as expressões são verdadeiras;
• or - Clássico “ou”, ou seja, sempre vai avaliar as duas expressões na direita e
na esquerda e verificar se pelo menos uma delas é verdadeira;
• && - “E” lógico, é parecido com o and, porém possui a característica de “curto-
circuito”, ou seja, se a expressão da esquerda for falsa, a expressão da direita
não vai ser avaliada;
• || - “Ou” lógico, parecido com o or, porém possui característica de “curto-
circuito”, ou seja, se a expressão da esquerda for verdadeira, a expressão da
direita não é executada;
• ! - O “not”, ou seja, inverte true em false e vice-versa.
Exemplos:
27
2.4. Fluxos e laços
Casa do Código
age = 10
parents_are_together = true
puts "Não pode beber" if age < 18
puts "Pode votar, mas não beber" if age < 18 and age >= 16
puts "Pode votar, mas não beber" if age < 18 && age >= 16
puts "Pode ver o show" if age > 18 or parents_are_together
puts "Pode ver o show" if age > 18 || parents_are_together
puts "Pode ir pra balada" if !parents_are_together
Booleanos e valores truthy e falsy
“Truthy” e “falsy” são maneiras de explicar o comportamento de tipos
(inteiro, string, etc.) em lógica de controle, tal como o if. Ou seja, se um
valor é “truthy”, o if é executado e, se “falsy”, não.
No Ruby, apenas false e nil são “falsy”, todos os outros tipos são
“truthy”, mesmo que o inteiro seja 0 (zero) ou se a string for “” (vazia).
Tome bastante cuidado!
Atenção com precedência de operadores booleanos!
Os operadores booleanos são utilizados não somente para a composição de if, e
portanto os operadores or e || possuem precedência diferentes (e o mesmo acontece
para o “e”), tendo or e and menor precedência. Veja os exemplos abaixo:
do_something = "123"
do_other_stuff = "abc"
# Observemos que and e && retornam o valor da última sentença
# avaliada
do_something and do_other_stuff
# abc
do_something && do_other_stuff
# abc
28
Casa do Código
Capítulo 2. Conhecendo Ruby
# No ||, do_other_stuff não é avaliado por ser operador
# de curto circuito!
do_something || do_other_stuff
# 123
# Caso 1: and
if var = do_something and do_other_stuff
puts "var is #{var}"
end
# var is 123
# Caso 2: &&
if var = do_something && do_other_stuff
puts "var is #{var}"
end
# var is abc
Nestes exemplos, é possível ver que há uma confusão. No primeiro caso a asso-
ciação (var =) tem maior precedência e é executado antes do and, ou seja, a execu-
ção é a mesma que (var = do_something) and do_other_stuff. Já no segundo
caso, o && é executado antes, portanto temos o mesmo que var = (do_something
&& do_other_stuff). Fica muito difícil de ler, portanto a recomendação é sempre
usar o operador && e || e parênteses para deixar o código mais claro, ou ainda me-
lhor, fazer a associação fora do if.
elsif
É possível ainda usar o elsif, uma combinação de else com if:
a = 1
# => 1
if a == 0
puts "A é 0"
elsif a == 1
puts "A é 1"
else
puts "A não é nenhum"
end
# A is 1
29
2.4. Fluxos e laços
Casa do Código
unless
Também herdado do Perl é o unless, que basicamente inverte o comportamento
do if, ou seja, o bloco associado é executado se a expressão associada retorna false:
a = 0
# => 0
# Uso comum, sem else
unless a == 0
puts "A não é zero"
end
# => nil
# Uso com else
unless a == 0
puts "A não é zero"
else
puts "A é zero, na verdade!"
end
# A é zero, na verdade
# => nil
# Uso do unless como posfixo
puts "A é zero" unless a > 0
# A é zero
# => nil
Dica: Cuidados com o uso do unless
O unless pode ser muito confuso para entender caso você tenha múl-
tiplas cláusulas na expressão associada, já que ele inverte a lógica do if
que já é de costume.
Outro problema do unless é com o else, por exatamente o mesmo mo-
tivo. É preferível, neste caso, inverter os blocos e usar if diretamente,
que é muito mais legível e sem “pegadinhas”.
O operador ternário ? também pode ser usado, conhecido de linguagens como
Java e C:
30
Casa do Código
Capítulo 2. Conhecendo Ruby
a = 0
# => 0
a == 0 ? puts("É zero!") : puts("Não é zero")
# É zero!
# => nil
O operador ternário ? recebe, do seu lado esquerdo a expressão a ser avaliada.
No seu lado direito, a primeira parte é para o caso da expressão ser true e o outro
lado, caso seja false.
Use com cautela, pois, nesse caso por exemplo, é necessário explicitar os parên-
teses devido a confusão com precedência de operadores. O ? deve ser usado com
pequenas linhas de código para não prejudicar a legibilidade.
case
O case é usado quando há muitas condições a serem checadas, evitando muitos
else e elsif:
case
when a > 0
puts "A é maior que 0"
when a < 0
puts "A é menor que 0"
when a == 0
puts "A é 0"
else
puts "Quebramos a matemática!"
end
# A é 0
# => nil
31
2.4. Fluxos e laços
Casa do Código
Cascade em Ruby
Em linguagens como C e JavaScript, uma vez que o interpreta-
dor/compilador encontra um caso verdadeiro, todo o código restante é
executado, independente se os resultados dos outros cases sejam false.
Esse fenômeno é chamado de cascade:
// Cascade em JavaScript:
var a = 1;
switch(a) {
case 0:
console.log("0");
case 1:
console.log("1");
case 2:
console.log("2");
}
// 1
// 2
Para resolver esse problema, é necessário executar um break:
// Cascade em JavaScript:
var a = 1;
switch(a) {
case 0:
console.log("0");
break;
case 1:
console.log("1");
break;
case 2:
console.log("2");
break;
}
// 1
32
Esse fenômeno não acontece com Ruby, portanto não é necessário tem-
perar o case com “breaks”.
Casa do Código
Capítulo 2. Conhecendo Ruby
O case é especial. Para alguns tipos de objetos, ele pode reagir diferente, veja os
exemplos a seguir:
case number_of_kills
when 0..10
puts ""
when 11..20
puts "Monster kill!"
when 21..40
puts "Rampage!"
when 41..70
puts "DOMINATING!"
else
puts "GODLIKE!"
end
Neste exemplo, verificamos a que intervalo pertence a variável number_of_kills
com o uso de Range (veremos mais detalhes sobre Ranges na seção 2.3). Vejamos
agora um exemplo com strings:
case input_date
when /\d{4}-\d{2}-\d{2}/
puts "O formato é yyyy-mm-dd"
when /\d{2}-\d{2}-\d{4}/
puts "O formato é dd-mm-yyyy"
when /\d{2}-\d{2}-\d{2}/
puts "O formato é dd-mm-yy"
else
puts "Não sei que formato é"
end
Neste caso, o bloco associado ao when irá ser executado quando a string bater
com expressão regular.
Case statement operator
Não é escopo deste livro ensinar internamente como o Ruby verifica isso,
mas se você tiver interesse em saber como isto é feito, busque pelo mé-
todo “===” ou “case statement operator”.
33
2.4. Fluxos e laços
Casa do Código
while e until
while e until são conhecidos de qualquer linguagem imperativa. O while exe-
cuta um bloco associado de código enquanto a condição estiver satisfeita:
a = [1, 2, 3]
while a.length > 0 do
puts "Bye bye, #{a.pop}"
end
# Bye bye, 3
# Bye bye, 2
# Bye bye, 1
# => nil
O until (“até que” em inglês) faz justamente o contrário. Ele executa um bloco
associado de código até que a situação se faça verdadeira:
a = [1, 2, 3]
until a.empty? do
puts "Bye bye, #{a.pop}"
end
# Bye bye, 3
# Bye bye, 2
# Bye bye, 1
# => nil
Cuidados com until
O until possui o mesmo problema semântico do unless (seção 2.4),
ou seja, podemos ter problemas ao pensar em dupla negativa. Dessa
forma, recomenda-se usar apenas while, negando a expressão quando
pertinente.
for ... in
O for ...
in é uma maneira de iterar elementos de uma coleção, ou um objeto
“iterável”, ou seja, um objeto que seja um Enumerator. A estrutura do for é simples:
34
Casa do Código
Capítulo 2. Conhecendo Ruby
for variável in coleção
Vejamos um exemplo com Arrays, que são “iteráveis":
fruits = %w{pera uva maçã}
for fruit in fruits
puts "Gosto de " + fruit
end
# Gosto de pera
# Gosto de uva
# Gosto de maçã
# => ["pera", "uva", "maçã"]
Hashes também são iteráveis, com uma pequena diferença:
frequencies = {'hello' => 10, 'world' => 20}
for word, frequency in frequencies
puts "A frequência da palavra '#{word}' é #{frequency}"
end
# A frequência da palavra 'hello' é 10
# A frequência da palavra 'world' é 20
# => {"hello"=>10, "world"=>20}
Blocos
Blocos são estruturas singulares a Ruby, e são extremamente importantes, tanto é
que merecem atenção especial. Blocos são trechos de código associados a um método
que podem ser executados a qualquer momento por este método.
Este conceito é um pouco estranho para quem vem de linguagens como Java
(apesar de que Java possui uma forma de se fazer blocos, com classes anônimas) e C,
mas se você já ouviu falar de lambdas, está familiarizado com o conceito.
O exemplo mais clássico de blocos é o uso de iteradores:
fruits = %w{pera uva maçã}
fruits.each do |fruit|
puts "Gosto de " + fruit
end
# Gosto de pera
# Gosto de uva
35
2.4. Fluxos e laços
Casa do Código
# Gosto de maçã
# => ["pera", "uva", "maçã"]
Explicando o exemplo anterior, o método #each pega cada valor dentro de sua
coleção e repassa para o bloco associado, através da variável fruit.
Outro método que usa bloco bastante útil é o #map, de Hashes e Arrays, que
retorna um novo Array contendo como elementos o resultado devolvido a cada ite-
ração:
numbers = [1, 2, 3, 4]
squared_numbers = numbers.map { |number| number * number }
squared_numbers # [1, 4, 9, 16]
Notação para blocos
Como você pôde perceber, foram usadas duas notações distintas nos
exemplos anteriores, a notação com do ...
end e a notação com cha-
ves. Ambas funcionam da mesma maneira. Porém, a comunidade Ruby
adotou a seguinte regra:
• Para blocos curtos, de apenas uma linha, adota-se as chaves;
• Para blocos longos, de duas ou mais linhas, adota-se o do ...
end.
A grande utilidade de blocos é que existem inúmeras APIs que exigem operações
antes e depois de serem utilizadas. Com o uso de blocos, este procedimento fica
transparente, como é possível observar no próximo exemplo:
# Exemplo tradicional
file = File.new('file.txt', 'w')
file.puts "Escrevendo no arquivo"
file.close
# Ao invés de termos que nos preocupar com a abertura e o fechamento do
# arquivo, a própria API faz isso antes e depois do bloco.
36
Casa do Código
Capítulo 2. Conhecendo Ruby
File.open('another_file.txt', 'w') do |file|
file.puts "Escrevendo no arquivo com blocos!"
end
É possível também passar e receber mais de um argumento para blocos. O fun-
cionamento é bem o mesmo de argumentos para funções, vejamos o exemplo abaixo
com Hashes:
a = {:a => 1, :b => 2, :c => 3}
a.each do |key, value|
puts "A chave #{key} possui valor #{value}"
end
# A chave a possui valor 1
# A chave b possui valor 2
# A chave c possui valor 3
Escopo de variáveis
Variáveis declaradas no mesmo escopo em que o bloco se encontra são acessíveis
de dentro deste bloco.
a = %w{a b c d e}
counter = 0
a.each { |val| counter += 1 }
puts "O valor do contador é: #{counter}" # O valor do contador é: 5
Se usarmos um nome de variável para o parâmetro de bloco que é o mesmo que
uma variável externa ao bloco, a variável externa deixará de ser acessível dentro do
bloco, mas terá seu valor mantido:
a = %w{a b c}
i = 5
a.each do |i|
puts i
end # a; b; c
puts i # 5
37
2.4. Fluxos e laços
Casa do Código
Atenção ao escopo!
O comportamento do exemplo anterior é restrito à variáveis em parâ-
metros. Isso significa que variáveis externas ao bloco são modificáveis
dentro deste mesmo bloco, conforme o primeiro exemplo. O resultado
disso pode ser inesperado, já que o bloco pode estar alterando valores de
outras variáveis.
As variáveis declaradas dentro do bloco são criadas a cada vez que o bloco é
chamado, ou seja, essas variáveis não existirão caso o bloco seja chamado novamente
e serão criadas novamente:
a = %w{w o r d}
a.each do |letter|
word ||= ""
word << letter
puts word
end
# w
# o
# r
# d
Existem situações em que nós não ligamos para um dos valores passados para
o bloco, mas ainda assim precisamos respeitar o número de parâmetros. Podemos
assim usar o _, que irá “absorver” esse parâmetro sem ter a necessidade de ter que
declarar uma nova variável:
a = {:a => 1, :b => 2, :c => 3}
a.each do |_, value|
puts value
end # 1; 2; 3
É recomendado sempre usar o _ ao invés de simplesmente omitir os valores. Al-
gumas vezes é possível simplesmente omitir, porém existem métodos cujo compor-
tamento muda de acordo com o número de parâmetros passados (conhecido como
38
Casa do Código
Capítulo 2. Conhecendo Ruby
arity). Isso acontece porque é possível verificar quantos parâmetros o bloco pode
receber:
a = {:a => 1, :b => 2, :c => 3}
a.each do |key|
puts key
end
# a
# 1
# b
# 2
# c
# 3
Controle de fluxo em blocos
No caso de uso de blocos como iteradores, é possível controlar o fluxo com al-
gumas expressões. Existem várias que raramente são usadas. As principais são as
seguintes:
• break - Pára o iterador completamente no momento em que for chamado;
• next - Passa para o próximo elemento;
Vejamos alguns exemplos:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
numbers.each do |number|
next if number.odd?
puts number
end # 2; 4; 6; 8; 10
numbers.each do |number|
break if number > 5
puts number
end # 1; 2; 3; 4; 5
2.5
Funções, blocos, lambdas e closure
Em Ruby, declarações de funções e métodos são assim:
39
2.5. Funções, blocos, lambdas e closure
Casa do Código
def print_text(text)
puts text
end
# É possível omitir os parênteses
def print_text text, more_text
puts text + more_text
end
Retorno de métodos e funções
Diferente da maioria das linguagens, em Ruby não é necessário colocar um
return para retornar um valor, porque o último código executado de um método
será o objeto retornado:
def double_that(number)
var = "isso não é relevante"
number * number
end
puts double_that(10) # 100
Lembrando que a maioria dos blocos de código Ruby retornam valor, tal como
o próprio if (seção 2.4). Portanto, é possível construir funções da seguinte maneira:
def am_i_rich?(cash)
if cash > 1_000_000
"sim"
else
"não"
end
end
puts am_i_rich?(100) # "não"
Às vezes, porém, é interessante retornar em um ponto específico da função.
Dessa forma, usamos o return:
def factorial(n)
return 1 if n == 1
n * factorial(n-1)
40
Casa do Código
Capítulo 2. Conhecendo Ruby
end
factorial(1) # 1
factorial(3) # 6
No exemplo anterior, no caso em que n é 1, a função retorna 1, sem executar o
trecho na linha 4.
return e blocos
Cuidado com o uso de return dentro de blocos. Primeiro porque não
existe um comportamento intuitivo para o return de dentro de blocos.
Ele deve encerrar o loop, tal como o break? Ele deve ceder o fluxo de volta
para o método original, tal como o next? Nenhum dos dois. Ele de fato
não tem nenhum comportamento especial com blocos, ou seja, o return
termina a execução do método/função a qual o bloco está associado.
A segunda razão para não fazer isso é que, se houver um código im-
portante que precisa ser executado posterior ao bloco, como no caso do
File.open, ele deixará de ser executado.
alias
É possível criar múltiplos nomes para uma função via a expressão alias:
def factorial(n)
return 1 if n == 1
n * factorial(n-1)
end
alias fac factorial
fac(5) # 120
Esta possibilidade pode parecer meio inútil a princípio, mas, em diversas situa-
ções, torna a linguagem mais expressiva e natural, ou seja, a leitura de código Ruby
tende a ser bem próxima do inglês. Dessa forma, criam-se aliases para funções de
forma a melhorar a legibilidade do código produzido. O exemplo a seguir é usado
no próprio Rails:
41
2.5. Funções, blocos, lambdas e closure
Casa do Código
require 'active_support/all'
1.hour.ago
# 2012-04-30 20:15:26 -0300
# Leitura péssima, mas funciona
2.hour.ago
# 2012-04-30 19:15:26 -0300
# Agora faz sentido!
2.hours.ago
# 2012-04-30 19:15:26 -0300
Parâmetros default
É possível criar funções e métodos com parâmetros padrão usando a seguinte
sintaxe:
def truncate(string, length=20)
string[0,length-3] + "..."
end
puts truncate("Truncando essa linha longa")
# Truncando essa li...
puts truncate("Truncando essa linha longa", 10) # Truncan...
Números de argumentos variáveis
Em Ruby podemos criar métodos que aceitam um número variado de argumen-
tos. Conseguimos isso através do uso do operador splat:
def sum(*values)
puts values.inspect
values.reduce { |sum, value| sum + value }
end
sum(1)
# [1]; 1
sum(1, 1)
# [1,1]; 2
sum(1, 2, 3, 4, 10) # [1, 2, 3, 4, 10]; 20
É possível destacar alguns parâmetros caso isso seja interessante:
def sum(first, *values)
puts values.inspect
42
Casa do Código
Capítulo 2. Conhecendo Ruby
values.reduce(first) { |sum, value| sum + value }
end
sum(1)
# []; 1
sum(1, 1)
# [1]; 2
sum(1, 2, 3, 4, 10) # [2, 3, 4, 10]; 20
Hashes para parâmetros nomeados
Alguns métodos podem precisar receber muitos parâmetros. Agora, imagine
uma chamada de método dessa forma: process(1, 10, friends, Date.today,
3.days.from_now). Fica bem difícil lembrar o que cada parâmetro significa e pior,
qual é a ordem desses parâmetros. Ou mais além, no Rails, por exemplo, é possível
passar algumas opções para mudar algum comportamento do método. Este tipo de
problema é o chamado acoplamento por posição, ou seja, o código torna-se com-
plicado devido à grande dependência da posição dos parâmetros.
Por isso, é bem comum usar hashes como parâmetros para tornar a chamada de
método mais legível e evitar que o programador fique consultando manuais a todo
momento:
search_flights(:from => 'SAO', :to => 'NYC')
search_flights(:from
=> 'SAO',
:to
=> 'NYC',
:max_stops => 3,
:class
=> :first)
Resumindo, os principais usos para hashes como parâmetros são:
• Independência de ordem de parâmetros, reduzindo acoplamento por posição;
• Quando há muitos parâmetros;
• Quando há parâmetros opcionais;
• Quando é possível alterar o comportamento através de opções.
43
2.5. Funções, blocos, lambdas e closure
Casa do Código
Saiba mais: acoplamento por posição
Para entender melhor sobre este tipo de dependência e acoplamento, re-
comendo a leitura do artigo “Connascence as a Software Design Metric
[4]” do Gregory Brown e também ver a palestra “Building Blocks of Mo-
dularity [10]”, do Jim Weirich.
Nesses casos, é bastante comum métodos serem construídos da seguinte ma-
neira:
# Se nada for passado, todos os parâmetros serão os default
def search_flights(options={})
from
= options.fetch(:from, 'SAO')
to
= options.fetch(:to, '')
max_stops
= options.fetch(:max_stops, 9999)
flight_class = options.fetch(:class, :any)
# ...
end
Blocos associados a métodos
Vimos no final da seção 2.4 o funcionamento de blocos. Agora veremos como
construir funções que possuem comportamento semelhante. O procedimento é bas-
tante simples, basta usarmos duas expressões: yield e block_given?.
O yield passa o fluxo de execução do programa para o bloco associado ao mé-
todo ou função. Usamos o block_given? para verificar se algum bloco foi passado
de fato ao método. Observe os exemplos a seguir:
def announce_it
puts "Legen..."
yield
puts "Dary!"
end
announce_it { puts "Wait for it!" }
# Legen...
# Wait for it!
# Dary!
44
Casa do Código
Capítulo 2. Conhecendo Ruby
announce_it
# Legen...
# LocalJumpError: no block given (yield)
Agora, usando o block_given?:
def announce_it
puts "Legen..."
# yield não executado caso não haja um bloco!
yield if block_given?
puts "Dary!"
end
announce_it { puts "Wait for it!" }
# Legen...
# Wait for it!
# Dary!
announce_it
# Legen...
# Dary!
Sintaxe alternativa para blocos
Existe uma forma de se usar blocos que não usa o par yield/block_given?. Essa
forma é explicitar o bloco com um parâmetro da função através do operador &, que
sempre deverá vir por último na lista de parâmetros:
def announce_it(name, &block)
puts "Hey #{name}, it's gonna be... "
puts "Legen..."
# Se o bloco não for passado, block será nil
block.call if block
puts "Dary!"
end
announce_it("Ted")
announce_it("Ted") { puts "Wait for it..." }
45
2.5. Funções, blocos, lambdas e closure
Casa do Código
Apesar de não recomendado, mesmo usando esta forma é possível usar yield e
block_given?. A grande vantagem de usar esta forma é para repassar o bloco para
outros métodos, sendo impossível com o yield:
def header(&block)
puts "Começa..."
# Precisamos transformar a variável
# em bloco novamente, por isso colocamos o &
[1,2,3].each(&block)
puts "Termina!"
end
header { |num| puts num }
# Começa...
# 1
# 2
# 3
# Termina!
# => nil
Sintaxe de blocos
A sintaxe de blocos mais usada por desenvolvedores que trabalham com
Rails é a mesma dos desenvolvedores do próprio Rails: explicitar os blo-
cos no parâmetro com &. O motivo é simples: documentação, ou seja,
fica claro para o leitor do código que aquele método pode receber um
bloco.
procs
Uma coisa interessante na sintaxe usando o operador & (apelidado também de
operador “pretzel”) é que o bloco é transformado em um objeto da classe Proc, que
responde ao método #call:
def block_it(&block)
puts block.class
end
46
Casa do Código
Capítulo 2. Conhecendo Ruby
block_it {} # Proc
Procs são estruturas amplamente utilizadas em projetos Ruby por diversas ra-
zões, sendo a possibilidade de transformá-las em blocos é apenas uma delas. É im-
portante ressaltar esse fato: blocos não são procs e vice-versa, mas são facilmente
transformados um no outro usando o &. Vejamos alguns exemplos:
logger = proc { |x| puts "#{Time.now} -- #{x}" }
logger.call("Teste!") # 2012-05-08 15:52:58 -0300 -- Teste!
[1,2,3].each(&logger) # O proc vira bloco para o each com o &
# 2012-05-08 15:53:46 -0300 -- 1
# 2012-05-08 15:53:46 -0300 -- 2
# 2012-05-08 15:53:46 -0300 -- 3
Um uso extremamente útil desses conceitos é que símbolos implementam o mé-
todo #to_proc, que é chamado pelo operador &. Este proc resultante chama o mé-
todo do argumento cujo nome é o símbolo. Para ficar mais claro, vejamos os exem-
plos a seguir.
Primeiramente, vejamos como que funciona o #to_proc:
upcase_it = :upcase.to_proc
upcase_it.call('abcde') # ABCDE
upcase_it.call(123) # NoMethodError: undefined method
# ùpcase' for 123:Fixnum
Ou seja, o proc gerado basicamente chama o método no objeto. Porém, usado
com o &, podemos criar filtros de maneira simples e enxuta:
# Forma tradicional usando bloco
%w{pera uva jaca}.map { |fruit| fruit.upcase }
# ["PERA", "UVA", "JACA"]
# Usando o #to_proc
%w{pera uva jaca}.map(&:upcase)
# ["PERA", "UVA", "JACA"]
47
2.5. Funções, blocos, lambdas e closure
Casa do Código
lambdas
Lambdas são bem parecidos com procs, e muitas vezes são usados de forma in-
tercambiável. A forma de construir lambdas é bastante parecida com procs:
upcase_it = lambda { |x| x.upcase }
upcase_it.call("abc") # ABC
# Arity é o número de parâmetros que o lambda aceita
upcase_it.arity # 1
Porém, lambdas possuem um atalho especial:
upcase_it = ->(x) { x.upcase }
upcase_it.call("abc") # ABC
Não existe uma recomendação para o uso das notações. Porém a primeira é mais
utilizada apenas por razões históricas: a notação ->() é uma introdução recente à
linguagem. Fique à vontade para usar a que você mais gostar.
Diferenças entre procs e lambdas
Apesar de serem parecidos, procs e lambdas não são iguais em dois comporta-
mentos.
Primeiro, vimos anteriormente que variáveis em excesso não causam nenhum
problema com blocos e são simplesmente ignorados, e isso acontece com procs, mas
não com lambdas:
show = proc { |x, y| puts "#{x}, #{y}" }
show.call(1)
# 1,
show.call(1, 2, 3)
# 1, 2
show = ->(x,y) { puts "#{x}, #{y}" }
show.call(1, 2)
# 1, 2
show.call(1)
# ArgumentError: wrong number of arguments (1 for 2)
show.call(1, 2, 3)
# ArgumentError: wrong number of arguments (3 for 2)
A segunda diferença é no uso do return. Dentro de blocos, quando usamos
return, o que de fato é retornado é o método associado, já que blocos/procs não
consideram o return. Em contrapartida, lambdas são mais próximos a métodos e
funções e retornam apenas ao contexto a que pertencem:
48
Casa do Código
Capítulo 2. Conhecendo Ruby
def proc_stop
puts "Cheguei..."
proc { puts "Hey"; return; puts "Ho!" }.call
puts "Saindo..."
end
proc_stop # Cheguei...; Hey
def lambda_stop
puts "Cheguei..."
lambda { puts "Hey"; return; puts "Ho!" }.call
puts "Saindo..."
end
lambda_stop # Cheguei...; Hey; Saindo...
closures
“Closure”, ou fechamento, é uma característica de funções que podem ser as-
sociadas a variáveis e podem ser invocadas a qualquer momento. Quando funções
com essa característica são criadas, elas carregam em si não apenas o código a ser
executado mas também referências à todas as variáveis existentes naquele escopo.
Em Ruby, lambdas e procs são closures, portanto carregam em si referências às
variáveis em seu escopo. Vejamos um exemplo:
def create_lambda
value = 10
-> { value += 1; puts value }
end
l = create_lambda
l.call # 11
l.call # 12
O que acontece no exemplo anterior é que, quando o lambda é criado, a va-
riável value está em seu escopo e portanto o lambda possui uma referência à value.
Mesmo quando a função create_lambda termina de executar, o lambda l ainda con-
segue acessar a variável value pois ainda possui uma referência. Note o exemplo a
seguir, criando duas lambdas:
49
2.5. Funções, blocos, lambdas e closure
Casa do Código
def create_lambda
value = 10
-> { value += 1; puts value }
end
first_lambda = create_lambda
next_lambda = create_lambda
first_lambda.call # 11
next_lambda.call # 11 - "value" é outra variável neste escopo
first_lambda.call # 12
first_lambda.call # 13
Quando executamos create_lambda pela segunda vez, uma nova variável value
foi criada e associada ao escopo de next_lambda. Dessa forma, next_lambda e
first_lambda referenciam-se à diferentes value, criadas em momentos diferentes.
Agora vamos modificar um pouco o código. Vamos criar duas lambdas dentro
de um mesmo escopo:
def create_lambdas
value = 10
first = -> { value += 1; puts value }
last = -> { value += 1; puts value }
[first, last]
end
first_lambda, last_lambda = create_lambdas
first_lambda.call # 11
last_lambda.call # 12 - "value" é a mesma variável
first_lambda.call # 13
last_lambda.call # 14
É possível observar no exemplo anterior que ambos first_lambda e
last_lambda carregam o mesmo escopo, ou seja, compartilham a mesma re-
ferência à variável value.
50
Casa do Código
Capítulo 2. Conhecendo Ruby
Dica: cuidado com escopo de closures
Há situações que não é possível saber que momento e nem em que or-
dem que lambdas e procs são executados pois são propagados à outras
partes do código. Dessa forma, compartilhar variáveis via closures pode
resultar em comportamento difícil de prever e bugs bem complicados de
serem encontrados. Por isso, preste bastante atenção nas variáveis sendo
compartilhadas via closures.
2.6
Classes e módulos
Ruby é uma linguagem orientada a objetos, e portanto, não poderia faltar uma sin-
taxe para declaração de classes, que no caso, é com a palavra-chave class:
class Purchase
end
Veja que, para a declaração de classes, usa-se o padrão CamelCase, porém co-
meçando com uma letra maiúscula. Isso deve-se ao fato que qualquer nome, seja de
variável ou de classe, que começa com uma letra maiúscula é uma constante.
A classe Purchase não tem nada de interessante. Vamos torná-la um pouco me-
lhor.
Construtores
class Purchase
def initialize(value)
@value = value
end
end
payment = Purchase.new(100.00)
payment # #<Purchase:0x007f97e14e61e8 @value=100.0>
No exemplo anterior acontecem algumas coisas interessantes. Primeiramente,
criamos um método de instância chamado #initialize. Métodos de instância são
métodos que podem ser executados em instâncias da classe Purchase.
51
2.6. Classes e módulos
Casa do Código
Mas esse método não é qualquer método. Ele é um construtor, ou seja, ele é cha-
mado quando um novo objeto é criado. Você pode observar porém que não chama-
mos o método #initialize diretamente, mas o .new. Por que? O Ruby implementa
no método de classe .new (ou seja, método relacionado diretamente à classe e não ao
objeto) as mágicas internas necessárias para se criar um novo objeto e, em seguida,
repassa tudo que recebeu para o método #initialize e retorna a nova instância do
objeto (independente do último valor).
Essa nova instância do objeto criada é chamada também de self. Dessa forma,
usando variáveis com “arroba” (@) é possível associar valores apenas àquela instância.
Por essa razão, elas são chamadas de variáveis de instância.
Declaração de métodos
Declarar métodos é bem simples, basta seguir o exemplo do #initialize:
class Purchase
def initialize(value, shipping)
@value = value
@shipping_cost = shipping
end
def total_cost
@value + @shipping_cost
end
end
purchase = Purchase.new(100.00, 9.50)
purchase.total_cost # 109.5
Accessors
“Accessors” são os famosos “getters” e “setters” do Ruby. Ou seja, são métodos
de leitura e escrita. Uma coisa interessante do Ruby é que chamada de métodos,
em muitas situações, não precisam de parênteses. Por essa razão, um método de
leitura de variáveis, por exemplo, se parece como um simples acesso a uma variá-
vel pública. Não há necessidade também de nenhuma forma de prefixo, tal como
getShippingCost, como faríamos em outras linguagens.
class Purchase
def initialize(value, shipping)
52
Casa do Código
Capítulo 2. Conhecendo Ruby
@value = value
@shipping_cost = shipping
end
def shipping_cost
@shipping_cost
end
end
purchase = Purchase.new(100.00, 9.50)
purchase.shipping_cost # 9.5
O “setter”, ou método de escrita, é bem mais interessante. Como já vimos ante-
riormente, é possível escrever métodos com símbolos como ? e !. Mas também, é
possível escrever métodos terminando com = para criar expressões de associação:
class Purchase
def initialize(value, shipping)
@value = value
@shipping_cost = shipping
end
def shipping_cost=(new_shipping_cost)
@shipping_cost = new_shipping_cost
end
def shipping_cost
@shipping_cost
end
end
purchase = Purchase.new(100.00, 9.50)
purchase.shipping_cost
# 9.5
purchase.shipping_cost = 3 # é possível colocar espaço antes do '='
purchase.shipping_cost
# 3
53
2.6. Classes e módulos
Casa do Código
Cuidado com métodos de leitura e escrita
Métodos de leitura e escrita são para estes propósitos. Então tome cui-
dado com a sua implementação destes métodos. Se eles fizerem muito
mais do que escrever e ler variáveis de instância, talvez não fique óbvio
para quem esteja lendo o código o que o método está fazendo. Não sur-
preenda negativamente os leitores do seu código!
Para facilitar este tipo de construção, existe uma class macro que já cria esses mé-
todos para você. Basta chamar attr_accessor no corpo da classe, passando como
parâmetro uma lista de símbolos que representa o atributo a ser criado.
class Purchase
attr_accessor :shipping_cost
end
purchase = Purchase.new
purchase.shipping_cost = 10.0
purchase.shipping_cost # 10.0
O que é class macro?
Uma class macro é uma chamada de método no nível de classe e o resul-
tado dessa chamada é a alteração da classe, adicionando comportamento
e tornando algum comportamento mais conveniente ao programador.
Por exemplo, a macro attr_accessor gera métodos de leitura e escrita
para um certo atributo.
A macro attr_accessor já cria o método de leitura e escrita para você. É possível
porém ter apenas o método de leitura via a macro attr_reader e o método de escrita
via attr_writer.
Cuidado com acessores de escrita
Existe um problema muito comum que os accessors de escrita geram e que é bas-
tante complicado de depurar, portanto preste atenção! Imagine a seguinte situação:
54
Casa do Código
Capítulo 2. Conhecendo Ruby
1 class Purchase
2
attr_accessor :shipping_cost, :weight, :distance
3
4
def calculate_shipping_cost
5
shipping_cost = distance * weight
6
end
7 end
8
9 purchase = Purchase.new
10 purchase.weight = 0.5
11 purchase.distance = 200
12
13 purchase.calculate_shipping_cost
14
15 purchase.shipping_cost # Qual é o valor?
Se você percebeu o mistério, já deve estar duvidando que shipping_cost seja
100. De fato, o valor não é 100 e sim nil. Mas qual a razão? Na linha 5, o Ruby
simplesmente interpreta a associação como a criação de uma variável local, ao in-
vés de procurar uma chamada de método de escrita. Portanto, é imprescindível que,
quando usarmos acessors de escrita sejam acompanhados de self. Observe que aces-
sors de leitura não necessitam dessa correção.
1 class Purchase
2
attr_accessor :shipping_cost, :weight, :distance
3
4
def calculate_shipping_cost
5
self.shipping_cost = distance * weight
6
end
7 end
8
9 purchase = Purchase.new
10 purchase.weight = 0.5
11 purchase.distance = 200
12
13 purchase.calculate_shipping_cost
14
15 purchase.shipping_cost # Agora sim, 100.0!
55
2.6. Classes e módulos
Casa do Código
Declarando métodos de classe
Não vimos anteriormente mas é possível declarar método em qualquer objeto,
não apenas uma classe. Vejamos o seguinte exemplo:
a = "123"
def a.scream
puts "AAAAAAAARGH!"
end
a.scream # "AAAAAAAARGH!"
b = "abc"
b.scream # NoMethodError: undefined method `scream' for "abc":String
Usamos essa mesma ideia para declarar métodos de classe:
1 class Purchase
2
attr_accessor :shipping_cost, :weight, :distance
3
4
def Purchase.build_free_shipping
5
purchase = new
6
purchase.shipping_cost = 0
7
8
purchase
9
end
10 end
11
12 Purchase.build_free_shipping # #<Purchase:0x0...0 @shipping_cost=0>
Métodos de classe executam no escopo de classe (ou seja, self é a própria classe),
então métodos como o construtor (como na linha 5 no exemplo anterior) são aces-
síveis sem ter que especificar a própria classe.
Porém, podemos melhorar isso. Dentro do bloco class, self é a própria classe.
Por isso, ao invés de termos que repetir o nome da classe, podemos usar simples-
mente self:
class Purchase
puts self # Purchase
def self.build_free_shipping
#...
56
Casa do Código
Capítulo 2. Conhecendo Ruby
end
end
Herança
Impossível falar sobre uma linguagem orientada a objetos sem mencionar he-
rança. Herança nada mais é do que a especialização de uma classe, muitas vezes
mudando apenas alguns comportamentos pontuais e ainda mantendo algumas ca-
racterísticas fundamentais, como nome de método.
class Shipping
attr_accessor :distance, :dimension
def cost
cubed_weight_factor = 16.7
distance * dimension/1000 * cubed_weight_factor
end
end
class UltraShipping < Shipping
def cost
super + (distance) * 0.07
end
end
shipping = UltraShipping.new
shipping.distance = 200
shipping.dimension = 1.2
shipping.cost # 18.00800...
No exemplo anterior, é possível perceber que, independente se usarmos
Shipping ou UltraShipping, o código vai funcionar pois ambas as classes respon-
dem aos mesmos métodos.
Este comportamento é possível através de duas características importantes da
linguagem. A primeira é a declaração de herança, usando <. Em seguida, vimos
também como é possível chamar métodos de mesmo nome da super classe usando
o super. O super é bastante importante para dizer ao Ruby que ele deve buscar na
Árvore Genealógica da classe (classe mãe, classe avó e assim vai) o método a ser
executado.
57
2.6. Classes e módulos
Casa do Código
Lembrando também que construtores (new) nada mais do que são métodos, en-
tão também são herdados!
Visibilidade de métodos
É muito importante quebrar seu código em vários métodos. Porém, muitas ve-
zes eles servem apenas para uso interno dentro das classes, portanto é importante
declará-lo como private, ou seja, métodos apenas acessíveis internamente à classe:
class Invoice
attr_accessor :service
def total_price
service + tax
end
private
def tax
service * 0.008
end
end
invoice = Invoice.new
invoice.service = 1000
invoice.total_price # 1008.00
invoice.tax # NoMethodError: private method `tax'
#
called for #<Invoice:0x007fdf92810550 @service=1000>
Diferente de muitas linguagens em que private ou public é prefixado à decla-
ração de todos os métodos, no Ruby funciona como uma chave que começa ligada
no public. Quando você então escreve private, a chave é virada e todos os méto-
dos então declarados são privados. Você pode voltar a chave para público chamando
public.
Métodos privados também são acessíveis de classes filhas. Isso pode ser uma
surpresa para você leitor, pois este comportamento não é comum. Dessa forma, não
é necessário usar protected para que classes filhas tenham acesso a esses métodos.
Fique atento a isso!
58
Casa do Código
Capítulo 2. Conhecendo Ruby
protected
O protected existe em Ruby e frequentemente é usado erroneamente devido ao
seu comportamento em outras linguagens. A real utilidade do protected é pratica-
mente nula e portanto deve ser evitada.
Mas para você, desenvolvedor curioso, fica a explicação do protected: o funci-
onamento do protected é quase o mesmo do private, contudo, é possível que um
objeto de mesma classe ou subclasse chame o método (igual ao friend, do C++).
Vejamos o exemplo a seguir:
class Person
attr_accessor :name
def befriend(people)
people.each { |friend| friend.add_friend(self) }
end
protected
def add_friend(friend)
puts "#{name} diz: Olá meu novo amigo #{friend.name}!"
end
end
joao = Person.new; joao.name = 'João'
pedro = Person.new; pedro.name = 'Pedro'
joaquim = Person.new; joaquim.name = 'Joaquim'
joao.befriend([pedro])
# Pedro diz: Olá meu novo amigo João!
joaquim.add_friend(joao)
# NoMethodError: protected method àdd_friend' ...
Exercício para você leitor! Altere o protected para private e veja o que acon-
tece. Depois, public.
Módulos
Módulos em Ruby são basicamente agrupadores de métodos, constantes, clas-
ses e variáveis. O uso é quase o mesmo de classes, porém não podemos criar uma
59
2.6. Classes e módulos
Casa do Código
instância de um módulo e usamos a palavra chave module, ao invés de class. Ou-
tra diferença é que não existe hierarquia de módulos, ou seja, não faz sentido um
módulo “herdar” de outro.
Módulo são usados de duas maneiras, usados como namespaces, ou seja, uma
boa maneira de agrupar classes, constantes e métodos quando pertinente (por exem-
plo, agrupar todas as classes que fazem parte do meio de pagamento de um site) ou
como mixins, uma forma de adicionar um comportamento comum a qualquer classe.
Veremos mais exemplos sobre essa importante funcionalidade.
Módulos como namespaces
Quando um sistema se torna grande, é importante agrupar diversas classes que
fazem parte de um componente em um namespace. Esta funcionalidade é de extrema
importância, por exemplo, quando temos diversas classes com o mesmo nome, seja
por um código que escrevemos ou por alguma biblioteca que usamos. Se não criar-
mos namespaces, comportamentos estranhos podem acontecer.
Para construir um módulo, basta usar uma sintaxe muito parecida com a de clas-
ses:
module Payment
class Purchase
end
end
Purchase.new # NameError: uninitialized constant Purchase
Payment::Purchase.new # => #<Payment::Purchase:0x007fac81d0b0f8>
No exemplo anterior podemos notar duas importantes características de módu-
los:
• Módulos devem também ser nomeados como constantes, ou seja, CamelCase
com a primeira letra em maiúscula;
• Quando declaramos uma constante ou classe dentro de um módulo, é neces-
sário explicitar o escopo via ::.
O uso do :: pode ser omitido no caso de estarmos dentro do módulo. Vejamos
um exemplo:
60
Casa do Código
Capítulo 2. Conhecendo Ruby
module Payment
MIN_COST_FOR_FREE_SHIPPING = 10.00
class Purchase
attr_accessor :total_cost
def calculate_shipping!
if total_cost >= MIN_COST_FOR_FREE_SHIPPING
puts "Parabéns, frete grátis para você!"
else
puts "Sem frete grátis :-("
end
end
end
end
purchase = Payment::Purchase.new
purchase.total_cost = 15.00
purchase.calculate_shipping! # Parabéns,
# frete grátis para você!
puts MIN_COST_FOR_FREE_SHIPPING # uninitialized constant
puts Payment::MIN_COST_FOR_FREE_SHIPPING # 10.0
Em algumas situações é possível que duas classes de mesmo nome sejam acessí-
veis em um escopo. Para isso, é possível explicitar integral ou parcialmente o escopo
usando o ::. Caso o escopo seja o escopo raiz, é possível declarar da seguinte forma:
::File. Isso irá fazer com que a resolução de nomes do Ruby vá buscar a classe ou
módulo File no nível raiz, sem nenhum módulo.
Para entender melhor como organizar projetos e escopos, veja o Capítulo 8 do
livro Ruby Best Practices [3].
Módulos como mixins
Mixins é uma das funcionalidades mais importantes do Ruby. Além de classes,
é possível declarar métodos de instância dentro de um módulo. Assim, é possível
“misturar” esses métodos em qualquer classe ou objeto. Dessa forma, é possível fazer
diversas classes obterem um conjunto de comportamento comum com apenas uma
linha de código.
61
2.6. Classes e módulos
Casa do Código
Dois exemplos básicos de mixins em Ruby são os módulos Comparable e
Enumerable. O módulo Comparable, ao ser incluído em uma classe, basta que a
classe implemente o método <=> (também conhecido como spaceship method) e
ela ganha diversos outros comportamentos de graça, como <, <=, ==, >, >= e
between?. Com o Enumerable, implementar o método each faz com que a classe
ganhe várias funcionalidades, como all?, any?, map, count, detect e outros.
Veja a documentação do Ruby sobre Enumerable para mais detalhes (http://www.
ruby-doc.org/core-1.9.3/Enumerable.html).
Contudo, vamos a um exemplo simples para entender como mixins funcionam:
module Shipping
CUBED_WEIGHT_FACTOR = 167
def dimensional_weight
width * depth * height * CUBED_WEIGHT_FACTOR
end
end
class ShippingPrice
include Shipping
attr_accessor :width, :depth, :height
end
shipping = ShippingPrice.new
shipping.width = 0.5;
shipping.depth = 0.8;
shipping.height = 0.3;
shipping.dimensional_weight # 20.04
Para criar mixins, basta declarar um módulo e construir métodos diretamente
nele. Quando fazemos include de um módulo dentro de uma classe, seus métodos
são “misturados”, e portanto temos acesso como se o método estivesse implementado
diretamente na classe. O mesmo acontece com os métodos do módulo. Como a veri-
ficação da existência de métodos é apenas em tempo de execução, este “contrato” de
implementação não é verificado pelo interpretador, simplificando, portanto, a im-
plementação.
Uma característica importante é que, quando fazemos um mixin, o módulo acaba
se tornando um ancestral da classe que inclui este módulo. Isso nos dá uma série de
62
Casa do Código
Capítulo 2. Conhecendo Ruby
vantagens, tal como a verificação de tipos:
shipping.is_a? Shipping # true
O mesmo pode ser feito com módulos como Enumerable. Esta é uma forma de
garantir que um objeto responde a uma série de métodos de maneira uniforme.
Mixins com extend
Existe uma outra forma de fazer mixins, com a palavra-chave extend. A dife-
rença é que os métodos são incluídos a nível de classe, e não mais de instância:
module Builder
def build(attributes={})
new_object = new
attributes.each do |name, value|
# O código abaixo é o mesmo que
# new_object.name = value
new_object.send "#{name}=", value
end
new_object
end
end
class ShippingPrice
extend Builder
attr_accessor :width, :height, :depth
end
shipping = ShippingPrice.build({
:width => 0.8,
:height => 0.2,
:depth => 0.3
})
shipping.width # 0.8
O código anterior usa um pouco do que chamamos em Ruby de meta-
programação, ou seja, programação para gerar código. O módulo Builder basica-
mente cria um construtor em que um Hash é convertido para atributos de um objeto
63
2.6. Classes e módulos
Casa do Código
via métodos de escrita (métodos terminados com =). Usamos então o extend para
misturar a funcionalidade de construção ao nível da classe.
Meta-programação
Meta-programação é uma funcionalidade importante para quem escreve
bibliotecas e sistemas avançados. Como este é um livro para quem está
começando, meta-programação pode dar um nó na cabeça e portanto
não vamos ver muito mais detalhes sobre o assunto.
Porém, se você se sente um programador aventureiro, recomendo a lei-
tura do Metaprogramming Ruby [8], um livro excelente para quem quer
iniciar nas artes às vezes misteriosas da meta-programação.
Exceções
O controle de exceções em Ruby acontece em 3 fases. Primeiro, um bloco de
código, iniciado pela cláusula begin é executado. Em seguida, caso alguma exceção
aconteça (via erros de sintaxe Ruby ou pela cláusula raise), o interpretador Ruby vai
buscar em todas as possíveis cláusulas rescue qual é pertinente para o tratamento
daquela exceção. Isso é feito através da comparação de classes, ou sua hierarquia. E,
por fim, se existir, o bloco ensure é executado, independente da ocorrência ou não
de uma exceção.
Sim, muita coisa pode acontecer em um pedaço pequeno de código, então va-
mos com calma para entender cada passo. Primeiro, vejamos como tratar exceções
genericamente.
def calculate_installment_price(total_value, installments)
begin
puts "O resultado é #{total_value / installments}"
rescue
puts "Não foi possível calcular o valor da parcela"
end
end
calculate_installment_price(100, 5) # O resultado é 20.0
64
Casa do Código
Capítulo 2. Conhecendo Ruby
calculate_installment_price(100, 0) # Não foi possível calcular
# o valor da parcela
Neste caso, quando passamos um valor inválido para o número de parcelas (ins-
tallments), uma exceção é disparada, e a execução é interrompida, levando a exe-
cução do programa para o bloco associado ao rescue. Por essa razão, a impressão
do texto "O resultado é ...” não é executada. É importante notar que nesse caso não
importa qual exceção seja disparada, o bloco rescue será executado.
É possível, porém, associar um bloco rescue diretamente a uma classe. Dessa
forma, dependendo do erro que ocorrer no bloco associado, podemos tratá-lo de
maneira diferente. Vamos usar essa ideia para mostrar um erro diferente para o
usuário:
def calculate_installment_price(total_value, installments)
begin
puts "O resultado é #{total_value / installments}"
rescue ZeroDivisionError
puts "Número de parcelas deve ser > 0"
rescue
puts "Não foi possível calcular o valor da parcela"
end
end
calculate_installment_price(100, 5) # O resultado é 20.0
calculate_installment_price(100, 0) # Número de parcelas
# deve ser > 0
calculate_installment_price("", 0) # Não foi possível calcular
# o valor da parcela
Dessa forma, quando enviamos 0 como número de parcelas, a exceção ge-
rada pelo interpretador é a ZeroDivisionError, que é tratada pelo primeiro bloco
rescue. No segundo caso, porém, este bloco não corresponde à exceção gerada
(NoMethodError), portanto o último bloco é executado.
É bastante comum termos que garantir que algo aconteça mesmo que haja erro
durante um processo, como liberar recursos. Por isso, temos o bloco ensure. O bloco
ensure sempre será executado, independente da ocorrência de exceções. Inclusive,
o bloco ensure pode ocorrer mesmo sem blocos rescue.
def calculate_installment_price(total_value, installments)
begin
65
2.6. Classes e módulos
Casa do Código
puts "O resultado é #{total_value / installments}"
rescue
puts "Não foi possível calcular o valor da parcela"
ensure
puts "Pronto."
end
end
calculate_installment_price(100, 5) # O resultado é 20
# Pronto.
calculate_installment_price(100, 0) # Não foi possível calcular
# o valor da parcela
# Pronto.
O begin é bastante útil caso queiramos especificar um trecho de código que pode
sinalizar exceções. Porém, no caso de métodos inteiros, podemos omiti-lo:
def calculate_installment_price(total_value, installments)
puts "O resultado é #{total_value / installments}"
rescue
puts "Não foi possível calcular o valor da parcela"
ensure
puts "Pronto."
end
Esta definição é um pouco mais enxuta, sem perder clareza, então é recomen-
dado o uso quando possível.
Mais informações sobre exceptions
Tratamento de exceções, por mais simples que pareça, é um assunto deli-
cado, e é necessário bastante cuidado para que o código não se torne um
grande macarrão incompreensível.
Se você quiser saber mais sobre o assunto, recomendo a leitura do exce-
lente livro Exceptional Ruby [6].
66
Casa do Código
Capítulo 2. Conhecendo Ruby
2.7
Bibliotecas e RubyGems
Uma linguagem de programação não pode ser completa sem possuir um mecanismo
de compartilhamento de código, de modo que possamos quebrar um sistema grande
em diversos arquivos.
Bibliotecas
Em Ruby, existem três maneiras para se carregar um código externo:
• load: Carrega o arquivo referenciado;
• require: Carrega o arquivo referenciado, porém, se ele já foi carregado em
algum outro ponto do código, nada será feito;
• require_relative: Funciona da mesma maneira que o require, porém so-
mente funciona com caminhos relativos ao sistema de arquivos. Veremos mais
detalhes logo adiante.
O require é definitivamente a maneira mais usada para carregar arquivos Ruby
pelo fato do arquivo ser carregado apenas uma vez. Quando um arquivo é carregado
via require, ele é adicionado a uma lista chamada $LOADED_FEATURES. Ou seja, isso
significa que se o arquivo Ruby for editado externamente, ele não será recarregado,
havendo a necessidade de reiniciar o processo.
Uma particularidade sobre o require é que, se o caminho passado for relativo,
ele será procurado apenas no $LOAD_PATH e um erro será apresentado caso não seja
encontrado. Para carregar arquivos que não estão no $LOAD_PATH, é necessário men-
cionar o caminho relativo. Vejamos a seguir exemplos:
Usando o load:
load 'purchase.rb' #=> true
Purchase.new
# #<Purchase:0x007f9062b49b60>
Agora o require:
require './purchase' # Podemos omitir o .rb com o require!
Purchase.new
# #<Purchase:0x007f9062b49b60>
Note que se removermos o caminho do arquivo, o require necessita que a pasta
em que o arquivo se encontra esteja no $LOAD_PATH:
67
2.7. Bibliotecas e RubyGems
Casa do Código
require 'purchase' # LoadError: cannot load such file -- purchase
$LOAD_PATH << "./" # $LOAD_PATH é um Array com todos os caminhos
# a serem procurados
require 'purchase' # Agora funciona, o purchase.rb é encontrado
Purchase.new
# #<Purchase:0x007f9062b49b60>
require_relative 'purchase'
Purchase.new
# #<Purchase:0x007f9062b49b60>
Cuidado com require_relative
O require_relative possui um pequeno problema quando executa-
mos o comando via IRB. Isso acontece porque, quando executamos o
require_relative, o Ruby tenta inferir a pasta raiz pelo arquivo em
execução. O problema é que, quando executamos via IRB, não existe
nenhum arquivo, então o Ruby irá sinalizar uma exceção mencionando
este fato.
RubyGems
A linguagem Ruby possui uma comunidade bastante ativa de programadores
que compartilham código. Para facilitar esse compartilhamento, a comunidade Ruby
inventou as chamadas RubyGems, ou “gems”. A criação de uma RubyGem ocorre da
seguinte forma:
1) Um desenvolvedor constrói uma biblioteca e deseja compartilhá-la;
2) O desenvolvedor prepara um arquivo chamado gemspec que informa coisas como
nome do autor original, colaboradores, versão daquela biblioteca, site, documen-
tação, e várias outras informações;
3) Em seguida, ele prepara o pacote usando a ferramenta gem, que gera um arquivo
terminado em .gem.
4) Depois de criar uma conta no site do RubyGems (http://rubygems.org), gratuita-
mente, o desenvolvedor usa o comando gem para publicá-la.
68
Casa do Código
Capítulo 2. Conhecendo Ruby
Instalando Rubygems
Uma vez publicada, qualquer desenvolvedor pode instalar e usar essa gem. Va-
mos instalar a gem nokogiri, para processamento de XMLs. Para isso, primeira-
mente temos que instalá-la, usando a linha de comando:
$ gem install nokogiri
Fetching: nokogiri-1.5.3.gem (100%)
Building native extensions. This could take a while...
Successfully installed nokogiri-1.5.3
1 gem installed
Installing ri documentation for nokogiri-1.5.3...
Installing RDoc documentation for nokogiri-1.5.3...
No caso do nokogiri, é necessário compilar código C associado à RubyGem,
por isso o comando gem exibe “Building native extensions ...”. Depois de instalado,
o RubyGems gera documentação via ri e RDoc. Não entraremos em detalhes como
esses aplicativos funcionam pois usaremos documentações online que facilitam a
busca de informações.
Depois de instalado, é possível usar o nokogiri da seguinte forma:
require 'rubygems'
require 'nokogiri'
doc = Nokogiri::HTML('<html></html>')
require ‘rubygems’ retorna falso
Não fique preocupado se o require 'rubygems' retornar falso. Como
Rubygems é muito comum, muitas ferramentas podem fazer o require
para você. O que importa mesmo é que o require da gem que você está
usando é feito com sucesso.
É importante garantir que façamos o require do RubyGems para que ele possa
configurar o ambiente Ruby de modo a encontrar as bibliotecas instaladas, mas talvez
isso não seja suficiente. Para instalar e usar uma gem, é recomendado que você leia
a documentação disponibilizada na página da gem, pois nem sempre a instalação é
exatamente igual à que vimos anteriormente.
69
2.8. Fim!
Casa do Código
Escolhendo uma RubyGem
A comunidade Ruby é bastante efusiva em bibliotecas, então é muito provável
que já exista uma gem para resolver o seu problema! Mas antes de sair usando uma
gem que você encontrou, verifique os seguintes pré-requisitos:
• Existe uma comunidade ativa de desenvolvedores, ou seja, há atividade recente
de alterações ou não há uma lista grande de bugs no tracker do projeto;
• Verifique se a biblioteca possui testes unitários e melhor ainda, se a última
compilação do projeto está OK (projetos populares executam suas compila-
ções no Travis-CI [http://travis-ci.org/])
Outra dica é procurar por soluções no site RubyToolBox (https://www.
ruby-toolbox.com/). Uma solução famosa, acompanhada e usada por muita gente
é uma forma de tentar diminuir as chances de você encontrar bugs, pois alguém já
deve ter se encontrado na mesma situação e resolvido o problema.
2.8
Fim!
Ufa! Você está lendo até aqui ainda? Que bom. Cobrimos bastante coisa de Ruby,
mas é claro que a linguagem tem muito mais para oferecer. Durante o capítulo, fiz re-
ferência a diversos livros que podem trazer mais conhecimento em diversos aspectos
de Ruby. É extremamente recomendado que você os leia depois que terminar de ler
este livro, caso queira (espero que sim!) continuar sua carreira como desenvolvedor
Ruby e Ruby on Rails.
Ruby é uma linguagem fascinante, e espero sinceramente que tenha gostado.
Agora, vamos aprender o framework Ruby on Rails, e começar a fazer o Colcho.net
do início ao fim.
70
Parte II
Ruby on Rails
Capítulo 3
Conhecendo a aplicação
Para cada fato existe um conjunto infinito de hipóteses. Quanto mais você observa,
mais você enxerga.
– Robert Pirsig, em Zen and the Art of Motorcycle Maintenance
Então você já aprendeu bastante da linguagem Ruby para dar os primeiros pas-
sos, mas ainda temos muito o que aprender pela frente. Durante os próximos capí-
tulos, vamos aprender mais da linguagem enquanto aprendemos o framework Ruby
on Rails.
Mas antes de começar, você sabe qual a diferença entre um framework e uma
biblioteca? Quando usamos uma biblioteca, nós programadores escrevemos nosso
código, chamando a biblioteca quando necessário. A camada entre a biblioteca e o
nosso código é bastante distinta.
O mesmo não acontece com um framework. Para se ter uma ideia, muitas vezes
o ponto inicial do sistema não é um código que você escreve. Nosso código faz o
intermédio com diversas outras bibliotecas que compõem o framework, de forma
que o resultado torna-se mais poderoso do que a soma das partes.
3.1. Arquitetura de aplicações web
Casa do Código
O Ruby on Rails não é diferente. Ele é um framework usando uma estrutura
chamada MVC -- Model View Controller, bastante conveniente para a construção de
aplicativos Web. O Rails também é usado por muitos desenvolvedores já há bastante
tempo, portanto já foi testado em diversas situações, como alta carga, ou grande
número de usuários. É fácil começar com Rails pois ele faz muito trabalho por você,
mas se aprofundar no Rails te dá ainda mais poder.
É muita coisa para aprender, mas não se preocupe, este livro está em seu poder
justamente para facilitar esta tarefa. Vamos então ver como vamos modelar nossa
aplicação e em seguida vamos ver como o Ruby on Rails vai nos ajudar a construí-la.
3.1
Arquitetura de aplicações web
Desenhar aplicações bem feitas em Ruby on Rails é um pouco mais complicado do
que simplesmente criar páginas atrás de páginas. A razão disso é que ele é prepa-
rado para criar aplicações modernas e arrojadas. Isso significa que não somente
deve responder HTML aos usuários, mas também responder de maneira adequada
para aplicações ricas em client-side, interagindo com frameworks como Backbone.js
(http://backbonejs.org/), Ember.js (http://emberjs.com/) ou outros.
O que são aplicações ricas em client-side?
Depois da repopularizarão do JavaScript com o uso intensivo de AJAX
nas páginas Web, os browsers tem se tornado muito mais poderosos do
que antigamente, inclusive com os benefícios incorporados pelo HTML
5. Tendo isso em mente, programadores estão criando aplicações cada
vez mais complexas no browser.
Nos últimos anos, diversos frameworks tem sido lançados, tal como os
mencionados anteriormente. Dessa forma, existem aplicações Web que a
parte de back-end tornou-se uma API que está totalmente independente
da apresentação final ao usuário.
3.2
Recursos ao invés de páginas
Para que nossas aplicações fiquem elegantes, precisamos parar de pensar na maneira
antiquada de se criar aplicações Web cheias de páginas e interações complexas e pen-
74
Casa do Código
Capítulo 3. Conhecendo a aplicação
sarmos em recursos. Não é uma mudança fácil, mas vamos usar este pensamento
durante o livro todo, então até o final, você ficará mais confortável com essa ideia.
Se você já ouviu falar ou sabe o que é REST (Representational State Transfer,
ou Transferência de estado de representação), entender como vamos usar recursos
para modelar nossa aplicação será bem mais fácil. Se você quiser se aprofundar no
assunto, recomendo a leitura do livro Rest in Practice [7].
3.3
Recursos no Colcho.net
Então vamos lá, como fica um recurso no Colcho.net? Relembremos que o Col-
cho.net é um aplicativo em que os seus usuários podem mostrar quartos vagos, ou
até um colchonete que pode ser usado em sua sala em troca de uma grana e quem
sabe fazer novas amizades.
Assim, o primeiro recurso que podemos identificar é o “Quarto”. Em nosso sis-
tema, será possível listar quartos, exibir detalhes de um quarto, criar um novo quarto,
editar um quarto já existente e removê-lo.
Recursos assim são fáceis de serem mapeados, pois estão diretamente ligados
a uma unidade lógica do sistema. Porém, existem recursos que são menos óbvios.
Por exemplo, como representar o login de um usuário? Para isso, teremos o recurso
“Sessão”. Poderemos criar uma sessão ou destruí-la, porém nada mais que isso.
Em sistemas REST, ações com recursos são mapeados em duas partes: um verbo
HTTP e uma URL. Veja os seguintes exemplos:
75
3.4. Conhecendo os componentes
Casa do Código
O que são verbos HTTP?
Verbos HTTP são ações que podem ser executadas em um recurso iden-
tificado por uma URL. A implementação do comportamento fica a cri-
tério do servidor, não existe um padrão. Os verbos usados no Ruby on
Rails são:
• GET - para retornar um recurso específico ou uma coleção;
• POST - para enviar um novo elemento à uma coleção;
• PUT - para alterar um elemento existente;
• DELETE - para remover um elemento.
• GET /rooms: Verbo GET na URL /rooms mapeia para a listagem de todos os
quartos;
• POST /rooms: Verbo POST na URL /rooms mapeia para a inserção de mais um
novo quarto na coleção de quartos;
• DELETE /rooms/123: Verbo DELETE na URL /rooms/123 mapeia para a remo-
ção do quarto cujo identificador único é “123”.
Se fizermos dessa maneira, o Rails nos ajuda e bastante e de quebra ganhamos
facilidade ao usar frameworks de client-side quando necessário.
Essa é a visão externa da arquitetura de aplicações Web. Vamos entrar então na
arquitetura interna.
3.4
Conhecendo os componentes
O Ruby on Rails é um framework que adota o padrão de arquitetura chamado
Model-View-Controller (MVC), ou Modelo, Apresentação e Controle.
Modelos possuem duas responsabilidades: eles são os dados que normalmente
ficam persistidos em um ou mais banco de dados (seu perfil de usuário, por exem-
plo). Eles também fazem parte da regra de negócio, ou seja, cálculos e outros proce-
dimentos, como verificar se uma senha é válida.
76
Casa do Código
Capítulo 3. Conhecendo a aplicação
O Controle é a camada intermediária entre a Web e o seu sistema. Ele pega
os dados que vem de parâmetros na URL e/ou de um formulário e repassa para os
modelos, que vão fazer o trabalho pesado. Em seguida, pega o resultado e transforma
da maneira adequada para a Apresentação.
A Apresentação é como o aplicativo mostra o resultado das operações e os dados.
Normalmente podem ser uma bela página usando as novas tecnologias de CSS 3 e
HTML 5 a até pequenas representações de objetos em JSON.
3.5
Os modelos
Quando criamos aplicações Ruby on Rails, basicamente usamos um componente
chamado ActiveRecord. O ActiveRecord é um Object-Relational-Mapping, ou seja,
uma biblioteca que faz o mapeamento de estruturas relacionais (leia-se SQL e bancos
de dados relacionais) a objetos Ruby.
O ActiveRecord internamente usa uma biblioteca bastante poderosa chamada
Arel. Com o uso do Arel o ActiveRecord consegue transformar chamadas de mé-
todos Ruby em complexas consultas SQL e depois mapear de volta em objetos Ruby.
Isso tudo é muito conveniente, veja alguns exemplos:
Room.all
# SELECT "rooms".* FROM "rooms"
Room.where(:location => ['São Paulo', 'Rio de Janeiro'])
# SELECT "rooms".* FROM "rooms" WHERE "rooms"."location"
#
IN ('São Paulo', 'Rio de Janeiro')
Além disso, modelos ActiveRecord possuem outra característica importante.
Eles possuem ferramentas para validação de atributos, callbacks em momentos opor-
tunos (como antes de atualização ou criação), tradução e outros. Este componente é
o ActiveModel.
Mas os modelos não necessariamente precisam ser objetos ActiveRecord. É bas-
tante comum separarmos regras complexas em diversas classes em Ruby puro. Isso
é importante para evitar que os modelos ou controles fiquem grandes e complexos.
Vamos ver um exemplo disso no capítulo “10 Login do usuário”.
No caso do Colcho.net, o Quarto se encaixa perfeitamente como um modelo.
Assim, podemos criar validações como a presença de uma localidade ou calcular a
disponibilidade dele em uma certa data.
77
3.6. Controle
Casa do Código
3.6
Controle
A camada de controle é o intermédio entre os dados que vem dos usuários do site
e os Modelos. Outro principal papel da camada de controle é gerenciar sessão e
cookies de usuário, de forma que um usuário não precise enviar suas credenciais a
todo momento que fizer uma requisição.
Após obter os dados na camada de modelos, é papel da camada de Controle
determinar a melhor maneira de representar os dados, seja via a renderização de
uma página HTML ou na composição de um objeto JSON.
No Ruby on Rails, os componentes que trabalham nessa camada são o
ActionDispatch e o ActionController, ambos parte do pacote denominado
ActionPack. O ActionDispatch trabalha no nível do protocolo HTTP, fazendo o
parsing dos cabeçalhos, determinando tipos MIME, sessão, cookies e outras ativida-
des.
Já o ActionController dá ao desenvolvedor o suporte para escrever o seu código
de tratamento das requisições, invocando os modelos e as regras de negócio adequa-
das. Ele também dá suporte a filtros, para, por exemplo, verificar se um usuário está
logado no sistema para fazer uma reserva no site.
Vamos trabalhar bastante com esses componentes no decorrer do livro e apren-
der como eles funcionam e como podemos implementá-los.
3.7
Apresentação
A camada de apresentação é onde prepara-se a resposta para o usuário, depois da
execução das regras de negócio, consultas no banco de dados ou qualquer outra ta-
refa que sua aplicação deva realizar.
As maneiras mais comuns de se exibir dados atualmente na web é através de pági-
nas HTML. No Ruby on Rails, conseguimos construí-las com o auxílio da biblioteca
ActionView (também membro do ActionPack). Também é possível ter a represen-
tação JSON dos dados, via a serialização de objetos (transformação de objetos Ruby
puro em JSON) ou via a construção de templates com o JBuilder, por exemplo.
Depois que a apresentação final é montada, ela é entregue ao usuário via um
servidor web, como por exemplo, uma bela página mostrando todas as informações
de um quarto, ou um objeto JSON que vai ser lido pelo JavaScript de uma outra
página.
Por fim, o Rails ainda possui uma estrutura complexa para gerenciar imagens,
78

Casa do Código
Capítulo 3. Conhecendo a aplicação
stylesheets e javascripts, chamada Asset Pipeline, pré-processando os arquivos e
preparando-os para entregar da melhor forma para o usuário.
3.8
Rotas
Como o Ruby on Rails sabe qual Controle deve ser invocada para as requisições?
Para resolver essa questão, é preciso indicar que uma determinada URL, quando
invocada, dispara um Controle. O papel desse mapeamento é das Rotas, que são
voltadas para recursos, como vimos no início deste capítulo. Mas é possível fazer
outras diversas maneiras de rotas com a complexidade que desejar.
Veja a figura 3.1. Nela é possível ver a interação entre as diversas camadas MVC
e o roteador quando o usuário requisita uma página de edição do recurso “quarto":
Figura 3.1: Fluxo do MVC
79
3.9. Suporte
Casa do Código
3.9
Suporte
Em volta de todo os componentes que mencionamos antes, existe também
ActionSupport. É nele que ficam diversas ferramentas e extensões ao Ruby, para
deixá-lo ainda mais fácil de usar. Graças a ele é que podemos fazer a seguinte linha
de comando:
2.days.ago
# => Sun, 03 Jun 2012 01:08:33 BRT -03:00
Essa linha de código imprime a data de dois dias atrás.
Existem várias funcionalidades dentro do ActionSupport, mas não vale a pena
entrar em detalhe. Vamos usá-lo diversas e você acabará aprendendo esses truques
e recursos durante o livro.
3.10
Considerações finais
Não se preocupe em decorar todos os nomes que foram vistos nesse capítulo. Você
pode consultar o livro sempre que precisar saber. É importante porém saber que eles
existem para que você possa procurar ajuda ou documentação.
Aliás, falando em documentação, você aprenderá com o decorrer do livro que
a API do Rails é bem extensa e cheia de métodos. Ambiente-se a desenvolver com
o site da documentação oficial do Rails aberta. Ela está disponível em http://api.
rubyonrails.org/. Sempre que tiver alguma dúvida sobre algum método do Rails,
você pode procurar que estará lá.
O que é extremamente importante entender desse capítulo, porém, são as ideias
de recursos e entender o que cada parte do MVC faz devidamente. Isso significa
que, sempre que possível, devemos modelar nossas aplicações como recursos e que
nunca, jamais, devemos colocar regras importantes de negócio em Controles, por exemplo, pois não é o papel dele.
Entendido isso, vamos continuar. Agora é hora de voltar a programar!
80
Capítulo 4
Primeiros passos com Rails
Conhecer seus limites já é estar a frente deles.
– Georg Wilhelm Friedrich Hegel
Para criar uma aplicação Rails, usaremos um script que a gem do Ruby on Rails
instala no sistema. Se você ainda não instalou Ruby e Rails, dê uma olhada na se-
ção 2.1, lá você encontrará instruções de como fazê-lo e em seguida volte aqui para
começarmos a construção do Colcho.net.
4.1
Gerar o alicerce da aplicação
Vamos ao terminal gerar o esqueleto para o Colcho.net. Para criarmos um novo pro-
jeto, precisamos executar no terminal o comando rails new, passando em seguida
o nome do projeto que será criado, no caso colchonet.
Ao começar a instalar, aproveite para pegar mais café ou sua bebida favorita, pois
pode demorar um pouco.


Casa do Código
Capítulo 4. Primeiros passos com Rails
Figura 4.2: Estrutura de pastas geradas - 2
Cada pasta possui um significado e já é um efeito do conceito que vimos no
capítulo 1, chamado “Convenção sobre Configuração”. Isso significa que não teremos
um XML que dirá em que pasta fica o quê, em contrapartida, significa também que
temos que seguir a estrutura proposta. Eu pessoalmente acho que é uma ótima troca!
Vamos entender o que é cada uma delas:
• app - É onde fica o código principal da sua aplicação. Controles ficam na pasta
controllers, apresentações em views, javascript e CSS em assets e modelos em
models. A pasta helpers guarda códigos auxiliares usados em apresentações e
mailers são classes para o envio de emails.
• config - Configuração da aplicação, como informações sobre banco(s) de da-
dos, internacionalização de strings (I18n), time zone, e outras coisas.
• db - Armazena migrações e o esquema do banco de dados usado na aplicação.
• doc - Toda a documentação de sua aplicação deve estar aí.
• lib - Armazena código externo à sua aplicação mas que não faz parte de gems.
83
4.2. Os ambientes de execução
Casa do Código
Aí também ficam tarefas rake, código que deve ser executado fora do ambiente
Web.
• log - Onde ficarão seus logs e, se tudo der certo, uma pasta que você raramente
terá que acessar.
• public - Arquivos estáticos como favicon.ico, robots.txt, 404.html e
500.html, que serão exibidos nos casos de página não encontrada e erro de
servidor, respectivamente.
• script - Guarda o script rails, usado para gerar código (vamos ver mais de-
talhes sobre esse comando a seguir).
• test - Testes unitários, integração, funcionais e performance ficam aqui. Este
é um tópico bastante complicado e merece seu próprio livro, então, ao invés
de vermos o assunto pela metade, indico a leitura do RSpec Book [5] ou o Guia
Rápido de RSpec [9].
• tmp - Nesta pasta ficam arquivos temporários como PIDs, cache, entre outros.
• vendor - É onde você deverá instalar plugins de terceiros que não são gems.
Também é uma boa ideia instalar eventuais plugins javascript e CSS aqui, para
separar o código da sua aplicação.
Dê uma olhada no conteúdo das pastas e abra alguns arquivos, apenas por curi-
osidade, para quebrar o gelo, já que você e o Rails ainda são estranhos entre si. Dê
uma atenção especial ao arquivo config/application.rb, o arquivo de configura-
ções gerais da sua aplicação.
4.2
Os ambientes de execução
O Rails possui o conceito de ambientes de execução do aplicativo e cada um deles
possui um conjunto próprio de configurações. Um exemplo de configuração que
varia por ambiente é o caching de classes, ou seja, todo o código Ruby presente na
pasta app será carregado apenas uma vez. Isso é bastante útil em produção devido a
maior performance, porém, em desenvolvimento, atrapalha bastante.
Por padrão, o Rails possui, mas não se limita a, três ambientes:
• development: É o ambiente padrão, onde o Rails é totalmente configurado
para facilitar o desenvolvimento;
84
Casa do Código
Capítulo 4. Primeiros passos com Rails
• test: Ambiente para se executar testes de todos os níveis (unitários, integra-
ção, etc.);
• production: É o ambiente em que a aplicação deve executar quando rodando
no servidor, para os seus usuários. Por esse motivo, é otimizado para perfor-
mance.
Cada
ambiente
possui
seu
próprio
banco
de
dados.
O
arquivo
config/database.yml possui a definição da configuração de banco de dados
para cada ambiente. Veja o exemplo para o ambiente de desenvolvimento, usando o
adaptador para o PostgreSQL:
development:
adapter: postgresql
database: colchonet_dev
host: localhost
username: vinibaggio
password:
pool: 5
timeout: 5000
Outros
arquivos
relacionados
com
ambientes
ficam
na
pasta
config/environments, no qual cada arquivo é carregado automaticamente de
acordo com o ambiente em execução.
Variável de ambiente RAILS_ENV
A variável de ambiente RAILS_ENV é usada para determinar em que am-
biente o Rails irá executar, por padrão sendo development. Assim, é pos-
sível executar comandos específicos para cada ambiente, basta fazer, por
exemplo:
RAILS_ENV=production rake db:create
85
4.3. Os primeiros comandos
Casa do Código
4.3
Os primeiros comandos
Com seu terminal aberto na pasta do projeto (colchonet), execute o comando rails
server:
Comandos no terminal
A partir de agora, vou mostrar para você diversos comandos de terminal
e suas respostas. Por isso, nessas listagens você deve digitar apenas o que
segue o símbolo $ (dinheiro), ou se por um acaso a linha for muito longa,
será quebrada com o símbolo \(contra-barra). Exemplos:
$ rails server
=> Booting WEBrick
Nesse caso, você deverá digitar apenas rails server e apertar a tecla
Enter. O que não tiver esse prefixo será uma saída aproximada do que
você vai ver ao digitar o comando.
$ rails server
=> Booting WEBrick
=> Rails 3.2.8 application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
[2012-06-08 23:38:43] INFO WEBrick 1.3.1
[2012-06-08 23:38:43] INFO ruby 1.9.3 (2012-04-20) [x86_64-darwin11.3.0]
[2012-06-08 23:38:43] INFO WEBrick::HTTPServer#start: pid=77137
port=3000
Abra o seu browser em http://localhost:3000/ e voilà, você verá uma bela página
preparada pela equipe do Ruby on Rails. Nessa página, há alguns passos, como usar
o comando rails generate para criar modelos e controles e remover o arquivo
public/index.html. Vamos seguir essa sugestão.
86

Casa do Código
Capítulo 4. Primeiros passos com Rails
Figura 4.3: Seja bem vindo ao Ruby on Rails
O comando rails generate é um comando bastante útil. Se você digitá-lo sem
parâmetros, pode ver uma lista do que pode ser gerado. Essa lista pode aumentar,
dependendo das gems que você instalar no Ruby on Rails. Vamos ver isso no futuro.
O Rails inclui várias ferramentas para você e esse script de geração de templates
é um ótimo exemplo de como a ferramenta te ajuda a automatizar tarefas repetitivas.
Nesse espírito, vamos agora começar a criar nosso primeiro recurso no sistema, o re-
curso quarto, ou Room, que terá um título, uma descrição e uma localização. Faremos
isso através do comando rails generate scaffold, que irá gerar vários arquivos
para nós:
$ rails generate scaffold room title location description:text
invoke active_record
create
db/migrate/20120609065934_create_rooms.rb
create
app/models/room.rb
invoke
test_unit
create
test/unit/room_test.rb
87

Casa do Código
Capítulo 4. Primeiros passos com Rails
Aqui podemos ver como o comando foi composto. Fazemos o generate de um
scaffold, ou andaime. Scaffolds são interessantes pois já geram desde o modelo até
a apresentação e páginas HTML, para que possamos modificar para ter o resultado
que quisermos.
Em seguida, especificamos o nome do recurso e seus atributos. Por padrão, todos
os campos serão “string”, ou um campo de texto curto a não ser que seja especificado,
como é o caso de description, a descrição de um quarto pode ser bem longa. Para
isso, podemos usar o : para especificar o tipo daquela informação.
O comando scaffold já gera tudo que é necessário para que possamos fazer as
operações mais comuns em um recurso, o famoso CRUD. CRUD nada mais é do
que create, retrieve, update e delete, ou criar, buscar/listar, editar e remover. Para tal,
primeiro precisamos criar o banco de dados. Não se preocupe em configurar um
servidor MySQL ou PostgreSQL por enquanto, pois vamos usar um banco de dados
bom e simples para o nosso propósito no momento, o SQLite. Para criar o banco de
dados e deixar tudo pronto para ver o resultado do scaffold, basta executar, na pasta
do projeto, os comandos rake db:create e rake db:migrate:
$ rake db:create
$ rake db:migrate
== CreateRooms: migrating =================================
-- create_table(:rooms)
-> 0.0019s
== CreateRooms: migrated (0.0020s) ========================
O primeiro irá criar o banco de dados, se não existir. No caso do SQLite,
o comando irá criar os arquivos db/development.sqlite3, db/test.sqlite3 e
db/production.sqlite3.
Em seguida, o rake db:migrate irá criar a tabela rooms no banco de dados
usando a migração gerada pelo gerador. Veremos no futuro o que é uma migração,
não se preocupe se você não souber o que é isso.
89

Casa do Código
Capítulo 4. Primeiros passos com Rails
Logs
Toda a requisição que você fizer, você vai ver que o servidor do Rails
mostrar um monte de coisas, desde consultas SQL executadas até quando
o servidor serve arquivos estáticos. Todo este texto será enviado também
para a pasta log, no arquivo com o nome do ambiente em execução (por
exemplo, log/development.log):
Started GET "/rooms" for 127.0.0.1 at 2012-08-14 21:58:15 -0700
Processing by RoomsController#index as HTML
Room Load (0.2ms) SELECT "rooms".* FROM "rooms"
Rendered rooms/index.html.erb within layouts/application (2.1ms)
Completed 200 OK in 12ms (Views: 10.3ms | ActiveRecord: 0.2ms)
É sempre importante estar de olho nessas requisições. Podemos observar
os parâmetros enviados pelo usuário, as consultas SQLs resultantes, o
tempo gasto em cada parte da aplicação e o código de resposta enviado
ao usuário.
4.4
Os arquivos gerados pelo scaffold
Vimos há pouco tempo que o scaffold gerou vários arquivos para nós. Mas o que
significa cada um deles? O que eles fazem e qual seu impacto no nosso projeto?
Migrações
Migrações são pequenos deltas de um banco de dados, ou seja, elas registram as
modificações que o schema, ou esquema, do banco de dados vai sofrendo. Elas con-
tém três informações importantes: uma informação de versão, um código de regres-
são e um código de incremento e todas elas ficam dentro do diretório db/migrate
do projeto.
Uma migração então possui um método up, que será executado quando o banco
for migrado de uma versão à outra. Quando houver a necessidade de se regredir
uma versão, talvez por causa de um bug, o método executado deverá ser o down. O
nome da versão da migração encontra-se no nome do arquivo, que nada mais é do
que uma data (ano, mês, dia, hora, minuto e segundo).
91
4.4. Os arquivos gerados pelo scaffold
Casa do Código
O Rails já conhece também o equivalente de desfazer para alguns comandos,
como drop_table para desfazer um create_table. Assim, é possível criar o método
change, que substitui ambos up e down. Você escreve o comando para incremento de
versão e o Rails encontra o equivalente quando a versão for decrementada, poupando
retrabalho pelo desenvolvedor.
Veja a pasta db/migrate. Lá haverá um arquivo que inicia com uma data, que
depende da hora que você executou o comando, e termina com _create_rooms.rb:
class CreateRooms < ActiveRecord::Migration
def change
create_table :rooms do |t|
t.string :title
t.string :location
t.text :description
t.timestamps
end
end
end
Vimos que, para executar as migrações pendentes, basta executar:
rake db:migrate
No capítulo 5, vamos revisitar e aprofundar em outros comandos de migrações.
Modelo
O arquivo room.rb gerado pelo scaffold no diretório app/models é o modelo que
tem seus dados guardados no banco de dados quando você clica no botão “Create
Room”. Nesse arquivo também ficam as regras de validação, relacionamentos com
outros modelos e outras coisas. O código atual é simples e veremos como customizá-
lo no futuro:
class Room < ActiveRecord::Base
attr_accessible :description, :location, :title
end
Os próximos dois arquivos são relacionados a testes.
92
Casa do Código
Capítulo 4. Primeiros passos com Rails
Teste unitário
Testes unitários são artefatos muito importantes para um sistema de qualidade.
Escrever teste, porém, é algo que é bastante complicado para iniciantes, pois existe
muita subjetividade em como escrever um teste. Por exemplo, devemos escrever um
teste antes do código de produção (ou seja, código que vai ser de fato executado
quando o site estiver no ar) ou depois? Devo usar qual técnica? Como usar mocks e
quando usar stubs?
Esse assunto é bastante complicado e importante, portanto recomendo a leitura
dos livros The RSpec Book, [5] e o Test-Driven Development: By Example [2]. A comu-
nidade Rails leva testes tão a sério que o próprio framework vem com um conjunto
de ferramentas para auxiliar o desenvolvedor a criar criá-los.
Um exemplo disso são os arquivos gerados. Toda vez que o Rails gera um mo-
delo, controle ou apresentação, arquivos equivalentes para cada unidade são criados.
Outro artefato gerado são as fixtures, ou acessório, em uma tradução grosseira. As
fixtures são arquivos no formato YAML (http://yaml.org/), uma linguagem simples
para serialização de dados, que será inserido no banco de dados antes de executar os
testes unitários. Eles são úteis para podermos testar consultas ou para construir um
cenário em que um código deve ser executado para que tudo funcione (por exemplo,
para testar uma autenticação, é necessário que haja um usuário no banco de dados).
Rotas
Prosseguindo, a próxima modificação é no arquivo de rotas. O arquivo de rotas é
onde o Rails mapeia a URL da requisição à um controle que você escreve. Se você se
lembra bem, falamos na seção 3.2 que o Rails é bastante voltado à recursos, e agora
vemos bem isso olhando o arquivo routes.rb no diretório config:
resources :rooms
Essa linha é tudo o que você precisa dizer para ganhar as seguintes rotas:
• GET /rooms - ação index - lista todos os quartos disponíveis;
• GET /rooms/new - ação new - mostra uma página com um formulário para a
criação de novos quartos;
• POST /rooms - ação create - cria um novo recurso na coleção de quartos;
93
4.4. Os arquivos gerados pelo scaffold
Casa do Código
• GET /rooms/:id - ação show - exibe detalhes de um quarto cuja chave primá-
ria (famoso id) seja especificada na URL (id 123 para a URL /rooms/123, por
exemplo)
• GET /rooms/:id/edit - ação edit - exibe o formulário para a edição de um
quarto;
• PUT /rooms/:id - ação update - altera alguma informação do recurso cujo id
seja especificado pelo parâmetro :id;
• DELETE /rooms/:id - ação destroy - destrói o objeto identificado pelo parâ-
metro :id
Essas ações são geradas por padrão e são todas as ações mais básicas que quei-
ramos fazer em um recurso (o “CRUD”). É claro que dá para customizar, remover
algumas dessas ações ou fazer URLs mais interessantes, mas sempre que fizermos
recursos dessa forma, vamos poupar trabalho.
Controle
O próximo é o controle em si. Se você abrir o controle em um editor de código,
vai ver exatamente as sete ações mencionadas anteriormente e um código básico que
o Rails já gera para você. Vamos entrar em mais detalhes sobre controles no futuro.
Apresentações
Em seguida, temos as seguintes páginas, apresentações de algumas das ações
mencionadas:
create
app/views/rooms/index.html.erb
create
app/views/rooms/edit.html.erb
create
app/views/rooms/show.html.erb
create
app/views/rooms/new.html.erb
create
app/views/rooms/_form.html.erb
Há três coisas importantes nessa lista. A primeira é que você pode ver que que os
arquivos são todos terminados em “erb”. O ERB é a linguagem de templating padrão
do Rails, mas é possível usar outras. Vamos usar ERB durante esse livro, então não se
preocupe com outra linguagem de template por enquanto. Ela não é nada de especial
na verdade, é o Ruby embutido dentro de um arquivo HTML.
94
Casa do Código
Capítulo 4. Primeiros passos com Rails
A segunda observação é que não há sete ações e que não necessariamente todas as
páginas mapeiam para uma ação no controle/rota. Isso deve-se ao fato que algumas
rotas não possuírem uma apresentação em si, apenas redirecionam ou apresentam
outras. Um exemplo é a ação create, pois quando há sucesso, o usuário é redirecio-
nado à ação index, e quando há erro, o formulário da ação new é reapresentado.
A terceira e última é que há um arquivo iniciado com _. Isso significa que
essa página é uma partial, ou seja, o conteúdo dela é embutido em outras pági-
nas. Nesse caso, o _form.html.erb é incluído tanto na página new.html.erb quanto
em edit.html.erb pois o formulário é o mesmo, evitando problemas de Ctrl-C
Ctrl-V e duplicidade de código. Alterando em um lugar, as mudanças se refletem
em todas as páginas que usam essa partial.
Agora o projeto está criando vida. Vamos então à nossa primeira funcionalidade:
cadastro de usuários.
95
Parte III
Mãos à massa
Nossa aplicação está começando a tomar alguma forma. Já temos um recurso,
quartos, e já podemos cadastrá-lo, editá-lo e removê-lo e fizemos tudo isso com a
ajuda do Rails.
Mas agora que já temos uma breve noção da estrutura do nosso projeto e também
do próprio framework, podemos começar a construir uma funcionalidade do início
ao fim, e bastante importante para o Colcho.net: cadastro e autenticação de usuários,
o login.
Primeiramente, vamos construir o modelo de usuários. Vamos garantir integri-
dade das informações, ou seja, construir validações para que um usuário cadastrado
com sucesso no site possua todas as informações necessárias.
Uma vez que tivermos a base do modelo pronta, vamos montar as páginas de
cadastro e edição de usuário e por fim visualização de perfil. Durante esse trabalho,
vamos montar as rotas e construir as ações do controle sem o auxílio de geradores.
Mais para frente, vamos aprimorar o modelo, garantindo que o usuário possua
uma senha encriptada no banco de dados, para que ninguém, nem mesmo os admi-
nistradores do sistema, consigam acesso a uma informação tão sigilosa.
Os templates estarão bem crus, então vamos aplicar um pouco de CSS e HTML
para tornar as telas mais interessantes. Vamos aprender como fazer tudo isso, e,
em seguida, iremos aprender a usar o sistema de internacionalização do Rails para
traduzir nossas páginas.
Para concluir o cadastro de usuários, vamos enviar um e-mail para o usuário
para que ele possa confirmar sua conta, uma prática muito comum nos sites atuais
para evitar cadastros falsos.
Capítulo 5
Implementação do modelo para o
cadastro de usuários
É bom perseguir um objetivo para uma jornada, mas no fim o que realmente importa
é a jornada em si.
– Ursula Guin
5.1
O usuário
Para o modelo de usuários, vamos precisar dos seguintes campos:
• Nome completo
• Senha
• Localidade
5.1. O usuário
Casa do Código
• Bio
Para gerar esse modelo, execute:
$ rails generate model user full_name email password location bio:text
Geradores
Você pode observar que muitas vezes vamos usar os geradores que o Rails
provê. Porém, é completamente possível desenvolver sem o uso de ne-
nhum gerador e criar todos os arquivos necessários na mão. Recomendo
tomar esse caminho para quando você estiver mais experiente com o fra-
mework e tiver mais conforto para decidir o que é mais útil para você.
Vejamos a migração gerada:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :full_name
t.string :email
t.string :password
t.string :location
t.text :bio
t.timestamps
end
end
end
Era de se esperar ver SQL, já que estamos tratando de banco de dados, mas ao
invés disso vemos Ruby. Isso é uma grande vantagem, já que infelizmente código
SQL pode diferenciar entre sistemas de bancos de dados. O ActiveRecord já sabe
lidar com essas diferenças e portanto apenas escrevemos em uma única linguagem
comum.
A
migração
nada
mais
é
do
que
uma
classe
que
herda
de
ActiveRecord::Migration. Toda vez que o banco for migrado para uma ver-
são superior, o Rails irá executar o que estiver no método #change, ou seja, criando
a tabela users no banco de dados. Quando o banco for migrado para uma versão
100
Casa do Código
Capítulo 5. Implementação do modelo para o cadastro de usuários
inferior, o equivalente do método create_table será executado, o drop_table,
removendo essa tabela.
Dentro do bloco associado ao método create_table, os campos serão criados
com os tipos que declaramos no gerador. Por fim, o Rails adicionou por conta própria
os campos relacionado à timestamps. São eles o created_at e o updated_at. O mais
legal de tudo é que o ActiveRecord faz essa gestão para você automaticamente!
Altere o código da migração para que ela fique da seguinte forma:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :full_name
t.string :email
t.string :password
t.string :location
t.text :bio
t.timestamps
end
add_index :users, :email, :uniqueness => true
end
end
Com essa alteração, estaremos criando um índice no campo email da tabela
users com uma propriedade especial: unicidade. Vejamos o resultado disso em ação.
Primeiro, faça:
$ rake db:migrate
== CreateUsers: migrating =======================
-- create_table(:users)
-> 0.0340s
-- add_index(:users, :email, {:uniqueness=>true})
-> 0.0047s
== CreateUsers: migrated (0.0341s) ==============
Isso irá criar a tabela no banco de dados e adicionar o índice. Agora vamos ao
console do Rails, digitando $ rails console. Esse comando nos leva ao IRB, porém
com todo o ambiente do Rails carregado, portanto é possível fazer alterações diretas
ao banco de dados.
101
5.1. O usuário
Casa do Código
Atalhos para o comando rails
O comando rails possui alguns atalhos para não termos que digitar o
nome completo do comando. Veja a lista de atalhos a seguir:
• rails c - equivalente ao rails console
• rails s - equivalente ao rails server
• rails g - equivalente ao rails generate
• rails db - equivalente ao rails dbconsole (abre o console do
cliente de banco de dados, dependendo do qual estiver sendo
usado)
User.create :email => '[email protected]'
# A saída é a seguinte (quebrada em linhas para facilitar a leitura):
#<User id: 1,
#
full_name: nil,
#
email: "[email protected]",
#
password: nil,
#
location: nil,
#
bio: nil,
#
created_at: "2012-06-14 06:51:00",
#
updated_at: "2012-06-14 06:51:00">
Podemos observar algumas coisas importantes nesse pequeno exemplo. Pri-
meiro é que o Rails, mesmo sem termos colocado nada na migração, criou o campo
id. Essa chave primária é tão padrão no desenvolvimento de projetos que o Rails
sempre vai criar para você nas migrações de criação de tabelas.
Podemos ver vários atributos como nil, o que é esperado, pois não os espe-
cificamos. Porém, podemos ver que o Rails automaticamente populou os campos
created_at e updated_at. Esses campos também são automaticamente gerencia-
dos pelo Rails, caso os campos existam, mas não é obrigatório.
102
Casa do Código
Capítulo 5. Implementação do modelo para o cadastro de usuários
Dica: console do Rails em modo sandbox
É muito comum fazermos alguns testes manualmente, criando, alterando
ou deletando objetos do banco de dados. Para que nossos testes não fi-
quem poluindo o sistema, é possível iniciar o console usando uma chave
para sandbox: rails console --sandbox. Quando você iniciar a ses-
são do console, o Rails irá emitir um início de transação no banco por
todo o tempo. Quando você sair, um ROLLBACK será enviado ao banco,
desfazendo todas as alterações.
Vamos
dar
uma
espiada
no
modelo
usuário,
localizado
em
app/models/user.rb:
class User < ActiveRecord::Base
attr_accessible :bio, :email, :full_name, :location, :password
end
A classe está praticamente vazia, com exceção de apenas uma linha de código. O
attr_accessible é uma class macro que o ActiveRecord provê para adicionar um
comportamento muito importante, porém peço a sua paciência, pois vamos entrar
em detalhes sobre isso um pouco mais adiante, na seção 6.2.
Se você observar no exemplo que executamos no console, poderá ver que muitos
campos ficaram em branco. Para evitar cadastros muito ruins ou até mesmo aciden-
tes, vamos colocar algumas validações, garantindo que o usuário coloque todos os
dados necessários.
5.2
Evite dados errados. Faça validações
O ActiveRecord nos disponibiliza diversos tipos de validações já prontas para
grande parte das situações comuns, tais como validar presença, formato, inclusão
em uma lista (masculino ou feminino, por exemplo), entre vários outros, além de
disponibilizar ferramentas para criarmos as nossas próprias validações.
Tudo isso é feito de forma bastante conveniente, usando class macros. Vamos
então validar:
• a presença do email, nome completo, localização e senha;
103
5.2. Evite dados errados. Faça validações
Casa do Código
• a confirmação de senha, ou seja, o usuário precisa preencher um campo con-
tendo a senha e outro para verificar se o usuário não cometeu erros de digita-
ção;
• mínimo de 30 caracteres para a bio.
Vamos primeiro adicionar as validações de presença, as famosas informações
obrigatórias:
class User < ActiveRecord::Base
attr_accessible :bio, :email, :full_name, :location, :password
validates_presence_of :email, :full_name, :location, :password
end
Salve o arquivo, pois vamos experimentar essas validações no console (basta exe-
cutar novamente o comando rails console):
user = User.new
user.valid? # => false
user.save
# => false
user = User.new
user.email = '[email protected]'
user.full_name = 'João'
user.location = 'São Paulo, Brasil'
user.password = 'segredo'
user.valid? # => true
user.save
# => true
Quando executamos o método #valid? no modelo, o ActiveRecord irá executar
a série de validações que o modelo tiver e retornar o resultado. Se o objeto estiver
válido, retornará verdadeiro, e falso caso contrário, como é de se esperar.
O #save irá executar todas as validações (via o método #valid?) e se o modelo
estiver válido, tentará salvar o modelo no banco de dados e retornar verdadeiro. Po-
rém, se algum problema ocorrer no processo, seja validação ou salvar o objeto no
banco de dados, método #save retornará falso.
104
Casa do Código
Capítulo 5. Implementação do modelo para o cadastro de usuários
De onde surgiram os métodos acessores?
Quando
criamos
nossos
modelos
e
os
fazemos
herdar
de
ActiveRecord::Base, automaticamente, o Rails disponibiliza os
métodos de leitura e escrita de todas as informações que a tabela possui.
Bom, já temos uma ideia de como funcionam as validações, então vamos adici-
onar uma nova:
class User < ActiveRecord::Base
attr_accessible :bio, :email, :full_name, :location, :password
validates_presence_of :email, :full_name, :location, :password
validates_confirmation_of :password
end
Dica: recarregando as classes
Se você alterar o modelo e não reiniciar sua sessão do console do Rails,
você não vai conseguir interagir com as suas novas alterações. Para não
haver a necessidade de sair e entrar novamente no console, basta executar
o comando reload! que o Rails irá recarregar tudo o que estiver dentro
da pasta app.
O funcionamento do validates_confirmation_of é bem interessante. A partir
do momento que adicionamos essa validação, para conseguirmos salvar um objeto
no banco de dados, precisamos passar um novo atributo “virtual”, ou seja, um atri-
buto que não existe no banco de dados, chamado password_confirmation. Se ele
não estiver igual ao campo de senha (password), o modelo não pode ser gravado no
banco.
Com o mesmo console aberto, podemos executar:
reload!
# Reloading...
# => true
105
5.2. Evite dados errados. Faça validações
Casa do Código
user = User.new
user.email = '[email protected]'
user.full_name = 'João'
user.location = 'São Paulo, Brasil'
user.password = 'segredo'
user.password_confirmation = 'errei_o_segredo'
user.valid? # => false
user.errors.messages
# => {:password=>["doesn't match confirmation"]}
Quando a validação é executada (tanto pelo método #valid? quanto pelo
#save), o ActiveRecord popula um atributo especial no modelo, chamado errors.
Com ele é possível verificar, em mensagens legíveis, quais foram os erros de valida-
ção.
Agora vamos a última validação, o tamanho da bio:
class User < ActiveRecord::Base
attr_accessible :bio, :email, :full_name, :location, :password
validates_presence_of :email, :full_name, :location, :password
validates_confirmation_of :password
validates_length_of :bio, :minimum => 30, :allow_blank => false
end
O validates_length_of faz diversos tipos de validação com tamanho de texto.
Nesse caso, aplicamos ao atributo “bio” apenas duas restrições: o tamanho mínimo
de 30 caracteres e ele não pode ser em branco. Porém, o validates_length_of aceita
muitas outras opções, veja algumas delas:
• :maximum: Limita o tamanho máximo;
• :in: Ao invés de passar :minimum e :maximum, basta fazer, por exemplo, :in
=> 5..10 para validar o mínimo de 5 e o máximo de 10 caracteres;
• :is: Limita o tamanho exato do texto;
• :allow_blank: Permite que o atributo fique em branco (ignorando as valida-
ções de tamanho);
106
Casa do Código
Capítulo 5. Implementação do modelo para o cadastro de usuários
Existem algumas outras opções menos usadas. Para saber quais são, recomendo
olhar a documentação oficial do Rails.
O ActiveRecord ainda possui diversas outras validações que não usamos. Al-
guns exemplos, para você ter uma ideia:
• validates_format_of: Valida o formato de um texto com uma expressão re-
gular;
• validates_inclusion_of: Valida a inclusão de um elemento em um enu-
merável, ou seja, um número em um range ou um texto dentre uma lista de
opções;
• validates_numericality_of: Valida se o atributo passado é realmente
um número, com algumas opções interessantes, por exemplo, :less_than,
:greater_than, entre outros.
Além das validações citadas, ainda é possível criar validações customizadas.
Falando em validações customizadas, existe uma validação importante que deve-
mos fazer mas ainda não fizemos. Sabe qual é? O e-mail. É possível colocar qualquer
coisa, mesmo que não seja um e-mail válido.
Para isso, vamos usar uma validação simples de e-mail. Ela não é nem de longe
ideal e não é compatível com o RFC de e-mails (veja em http://bit.ly/validacao-email
uma expressão regular compatível, se estiver curioso), mas é suficiente para informar
ao usuário que algo está errado. Já que vamos enviar um email de confirmação para
o usuário poder usar o sistema, não é necessária tanta formalidade.
Para isso, vamos usar uma expressão regular extraída da biblioteca Devise
(http://github.com/plataformatec/devise), uma biblioteca complexa de autorização
de usuários. Apesar de completa, a biblioteca não é recomendada para quem está
começando com Rails, portanto não vamos usá-la no Colcho.net.
Vamos também criar uma validação de unicidade para emails, ou seja, emails
cadastrados não poderão existir previamente no site. É importante ressaltar que o
validates_uniqueness_of possui um problema: primeiro o Rails verifica a existên-
cia do email a ser cadastrado e depois cria o modelo no banco, se o resto estiver OK.
É possível que, entre a verificação e a criação, o email seja criado no banco e o Rails
tentará criar o modelo de qualquer forma. É por isso que criamos a validação de
unicidade também no banco de dados, e estamos criando essa validação no modelo
apenas para feedback ao usuário.
Por fim, temos as últimas validações:
107
5.2. Evite dados errados. Faça validações
Casa do Código
class User < ActiveRecord::Base
attr_accessible :bio, :email, :full_name, :location, :password
validates_presence_of :email, :full_name, :location, :password
validates_confirmation_of :password
validates_length_of :bio, :minimum => 30, :allow_blank => false
validates_format_of :email, :with => /\A[^@][email protected]([^@\.]+\.)+[^@\.]+\z/
validates_uniqueness_of :email
end
Sintaxe alternativa para validações
Existe uma outra sintaxe para validações. Veja as duas validações a se-
guir:
validates :email, :presence => true
validates_presence_of :email
Ambas possuem o mesmo comportamento, porém a sintaxe usando
a class macro validates foca no atributo a ser validado, de forma
que você possa adicionar diversas validações de uma vez.
O
validates_presence_of, em contrapartida, foca na validação em si, po-
dendo passar diversos atributos de uma vez. Veja outro exemplo:
validates :email, :presence => true,
:format => { :with => /\A[^@][email protected][^@\.]+\z/ },
:uniqueness => true
Ainda é possível colocar múltiplos atributos na validação:
validates :email, :full_name, :location, :presence => true
Como não há diferença de comportamento, você pode converter as vali-
dações que fizemos com a sintaxe tradicional para a sintaxe alternativa e
verificar qual você mais gosta.
108
Casa do Código
Capítulo 5. Implementação do modelo para o cadastro de usuários
Nosso modelo usuário está bom o suficiente por enquanto. Vamos voltar a tra-
balhar nessa classe logo, porém vamos começar a fazer o fluxo de cadastro.
109
Capítulo 6
Tratando as requisições Web
Eu não desanimo, pois cada tentativa incorreta descartada é mais um passo a frente.
– Thomas A. Edison
6.1
Roteie as requisições para o controle
Agora que nosso modelo está pronto, vamos começar a ligar as outras partes do sis-
tema. Vamos fazer tudo sem uso de geradores do Rails, para entendermos cada passo
do projeto, diferente do recurso quarto.
Antes de tudo, vamos criar a rota, para que possamos testar as páginas e o con-
trole conforme construímos. Lembra-se das sete rotas do Rails?
• index - Lista todos as entradas;
• show - Exibe uma entrada específica do recurso;
• new - Página para criar uma nova entrada;

Casa do Código
Capítulo 6. Tratando as requisições Web
derivar o nome do controle, de maneira que o recurso :users é mapeado para o
UsersController. No caso do :rooms, o Rails mapeia para RoomsController. Isso
é bastante conveniente e mais uma amostra do conceito de Convenção sobre Confi-
guração.
Vá para a pasta app/controllers e crie o arquivo users_controller.rb de
acordo com o seguinte:
class UsersController < ApplicationController
end
Essa classe deve herdar do ApplicationController que apesar do nome, não
possui e não deve possuir uma rota para ela. Ela serve para que você configure to-
dos os controles de sua aplicação e é um ponto de partida, portanto vamos alterá-la
com o decorrer do projeto. Veja atualmente como é esta classe, abrindo o arquivo
app/controllers/application_controller.rb:
class ApplicationController < ActionController::Base
protect_from_forgery
end
O ApplicationController herda, por sua vez, do ActionController::Base,
um componente do Rails. Nessa classe há uma class macro bastante importante,
protect_from_forgery. Essa macro faz com que todos os controles da aplicação
exijam uma chave de autenticação em ações de alteração de dados (create, update
e destroy) de modo a evitar ataques de falsificação de requisição (Request Forgery).
Portanto, é bastante importante deixá-la ativada sempre que possível.
113
6.1. Roteie as requisições para o controle
Casa do Código
Falsificação de requisição
Ataques de falsificação de requisição entre sites (ou cross-site request
forgery - CSRF) consistem em enviar uma requisição de um site a ou-
tro fazendo uma ação que o usuário não desejou. Por exemplo, ima-
gine que no colcho.net, para você deletar sua conta, basta você aces-
sar http://colcho.net/usuarios/deletar_conta (endereço hipotético). Um
usuário malicioso pode colocar em um site uma imagem com a seguinte
tag:
<img src="http://colcho.net/usuarios/deletar_conta\T1\textquotedbl />
Ao entrar nesse site, o browser irá tentar baixar o conteúdo dessa ima-
gem, que na verdade é a ação para deletar sua conta e bum!, sua conta foi
deletada. Para proteger seu site contra isso, o Rails faz um mecanismo tal
que, para cada ação de modificação de estado, é necessário enviar uma
chave de segurança gerada aleatoriamente.
Voltando ao projeto, vamos criar a nossa ação new, no UsersController
(app/controllers/users_controller.rb):
class UsersController < ApplicationController
def new
end
end
Por enquanto não vamos fazer nada, dessa forma, o método new ficará vazio.
Ao abrir novamente a página, um erro diferente: Template is missing, ou “tem-
plate ausente”. A ação é encontrada, porém o Rails não consegue encontrar a apre-
sentação daquela ação:
114


Casa do Código
Capítulo 6. Tratando as requisições Web
Figura 6.2: Erro: Template is missing - template ausente
Os templates ficam na pasta app/views. Dentro dela, existe uma hierarquia.
Lembra-se como o Rails identifica o controle baseado no nome do recurso? O
mesmo acontece aqui: todos os templates para um controle ficam em uma pasta
de mesmo nome do recurso, ou seja, para o UsersController, os templates ficam
na pasta app/views/users.
Na pasta app/views existe também a pasta layouts. Essa pasta é uma pasta
especial. Nela ficam os layouts dos templates, ou seja, a base onde os templates serão
colocados.
Observe a figura 6.3. Nela é possível ver como o Rails compõe uma página final,
baseando-se em um layout e um template de uma ação.
Figura 6.3: Composição de página usando layouts
115
6.1. Roteie as requisições para o controle
Casa do Código
O uso de layouts dessa forma é bastante prático.
Não há a necessidade
de repetir diversos comandos e tags (evitando também um tedioso trabalho de
busca/substituição quando você faz alguma alteração), basta escrever o HTML es-
pecífico da sua página.
Para que um template se comporte como layout, basta executar yield, a mesma
palavra-chave para executar blocos associados a métodos. Veja o exemplo a seguir:
application.html.erb:
<html>
<body>
<%= yield %>
</body>
</html>
Neste exemplo, quando usarmos o layout application, todo o conteúdo do tem-
plate será inserido no lugar do yield.
É possível também ter mais de um layout em um sistema. Isso é útil, por exemplo,
quando há diversas “personas” em um site, como um administrador e um usuário
comum, ou um lojista e o cliente.
Personas
Persona é um personagem de um possível usuário do seu site. É impor-
tante pensarmos em como cada persona que quisermos focar vai inte-
ragir com o sistema. Assim, é mais fácil ter ideias de como melhorar a
experiência do usuário.
No caso do colcho.net, temos duas personas: o “hospedado”, usuário que
procura lugar para dormir e o “anfitrião”, usuário que tem um lugar so-
brando.
Vamos agora criar o template para a ação new. Para isso, crie a pasta users em
app/views e crie o arquivo app/views/users/new.html.erb. O nome possui três
significados: ação new, que vai ser pré-processado pelo ERB, resultando em um ar-
quivo html:
<ul></ul>
116
Casa do Código
Capítulo 6. Tratando as requisições Web
Por enquanto esse template está muito sem graça.
Vamos usar o controle
UsersController para preparar os dados para que possamos criar o formulário de
cadastro.
6.2
Integre o controle e a apresentação
Vamos agora trabalhar no UsersController para criar a tela de cadastro de novo
usuário:
class UsersController < ApplicationController
def new
@user = User.new
end
end
Duas coisas acontecem no trecho de código anterior: a primeira é que usamos
o método .new no modelo User. Já vimos esse método anteriormente, ele cria uma
nova instância do modelo User. Em seguida, armazenamos o novo objeto em uma
variável de instância, @user. O controle compartilha todas as variáveis de instância
com o template, ou seja, toda variável com @ estará disponível em nossos templates.
Isso significa que já podemos acessar esse objeto no template. Usaremos ele para
construir o formulário. Abra o arquivo app/views/users/new.html.erb e altere
para que ele fique da seguinte forma:
<h1>Cadastro</h1>
<%= form_for @user do |f| %>
<p>
<%= f.label :full_name %> <br />
<%= f.text_field :full_name %>
</p>
<p>
<%= f.label :location %> <br />
<%= f.text_field :location %>
</p>
<p>
<%= f.label :email %> <br />
<%= f.text_field :email %>
</p>
<p>
117
6.2. Integre o controle e a apresentação
Casa do Código
<%= f.label :password %> <br />
<%= f.password_field :password %>
</p>
<p>
<%= f.label :password_confirmation %> <br />
<%= f.password_field :password_confirmation %>
</p>
<p>
<%= f.label :bio %> <br />
<%= f.text_area :bio %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
Esse código que criamos foi um exemplo de como é um template ERB: trechos
de HTML envolto de código Ruby. Em ERB, todo código Ruby que não terá seu
conteúdo impresso no HTML resultante deve ser envolto de <% %>. Caso você queira
que o código seja impresso no HTML, temos que envolver o código Ruby com <%=
%>.
Você pode ver também que este código faz uso extensivo de métodos auxiliares
do Rails. Nesse caso, utilizamos alguns para construir páginas, em especial formu-
lários (portanto devem ser chamados no objeto f), que recebem o nome do campo
do objeto que estamos editando:
• form_for - Inicia a construção de um formulário para um modelo;
• label - Label correspondente a um campo do modelo;
• text_field - Campo simples de texto;
• password_field - Campo mascarado de senha;
• text_area - Área grande de texto, para escrevermos a bio;
• submit - Botão de Submit (Enviar), para enviar os dados ao servidor;
Você pode ter notado que em nenhum lugar nós mencionamos URLs para onde
este formulário deve enviar os dados. Isso é mais uma vantagem de se usar a mode-
lagem de recursos do Rails. O Rails é capaz de saber o recurso a que este formulário
118

Casa do Código
Capítulo 6. Tratando as requisições Web
pertence. E mais, através do uso do método #new_record? (experimente no con-
sole, execute em objetos gravados no banco e objetos novos), ele é capaz de saber se
o formulário deve enviar à ação create ou update. Esperto, não?
Abra o browser na página http://localhost:3000/users/new:
Figura 6.4: Formulário de cadastro de usuário
O problema é que, ao preencher o formulário e clicar em “Create User” (criar
usuário), vamos ser apresentados ao erro Unknown action, ou ação desconhe-
cida, create. Isso significa que o formulário enviou os dados para uma ação que
ainda não existe. Pois bem, vamos criá-la: abra novamente o UsersController
(app/controllers/users_controller.rb):
119
6.2. Integre o controle e a apresentação
Casa do Código
class UsersController < ApplicationController
def new
@user = User.new
end
def create
@user = User.new(params[:user])
if @user.save
redirect_to @user,
:notice => 'Cadastro criado com sucesso!'
else
render :new
end
end
end
A ação create possui a seguinte lógica:
• Cria novo usuário baseado nos dados de parâmetros;
• Se o usuário foi salvo com sucesso (#save retorna sempre true ou false),
redireciona ele para a página de seu mais novo perfil e exibe uma mensagem
felicitando o fato;
• Caso não seja possível salvar por causa de uma validação, renderiza o formu-
lário novamente.
Vamos entrar em detalhe em cada linha:
@user = User.new(params[:user])
O método params (lembre-se que em Ruby, para chamar métodos, não precisa-
mos de parênteses) retorna um hash com todos os parâmetros enviados pelo usuário,
seja via formulário (POST) ou via query string (parâmetros pela URL, por exem-
plo http://google.com?q=hello, via GET). Quando usamos os métodos auxiliares do
Rails para formulários, o params se parece com o seguinte:
{
"authenticity_token"=>"ofiNLJQUjL/p5vX8z0cy+N5aE9htJDIAUk=",
"user"=> {
"full_name"=>"Vinicius",
"location"=>"San Francisco, CA",
120
Casa do Código
Capítulo 6. Tratando as requisições Web
"email"=>"[email protected]",
"password"=>"segredo",
"password_confirmation"=>"segredo",
"bio"=>"Olá, tudo bom? Meu nome é Vinícius."
},
"commit"=>"Create User",
"action"=>"create",
"controller"=>"users"
}
Você pode ver que todos os campos do formulário vieram dentro de uma hash
só, a hash users. É esse conteúdo que passamos para o método .new do modelo.
Chave da hash em string ou símbolo?
Algumas coisas do Rails podem ser complicadas de serem entendidas
para iniciantes, e uma delas é o hash params, que é um tipo de hash usado
pelo Rails, chamado HashWithIndifferentAccess, ou hash com acesso
indiferente. Com ela, você pode acessar chaves com símbolos ou strings,
que no final dão no mesmo resultado:
puts params['user']
{
"full_name"=>"Vinicius",
"location"=>"San Francisco, CA"
}
puts params[:user]
{
"full_name"=>"Vinicius",
"location"=>"San Francisco, CA"
}
O método .new aceita um hash contendo todos os atributos a serem associados.
Esse é um conceito chamado mass-assignment, ou seja, associação em massa. É muito
mais prático do que ficar chamando cada método individualmente dessa maneira.
121
6.2. Integre o controle e a apresentação
Casa do Código
Porém nada nessa vida é de graça e, portanto, teremos alguns problemas que vamos
ver ainda nesse capítulo.
Com o objeto todo populado de atributos que vieram do formulário, tentaremos
salvá-lo no banco de dados. Se tudo der certo, as linhas a seguir são executadas:
redirect_to @user,
:notice => 'Cadastro criado com sucesso!'
O método redirect_to envia ao browser do usuário um código de resposta
“302” que significa “Moved Temporarily”, dizendo ao navegador que ele deve ir para
outro endereço. No conteúdo da resposta, uma localização é informada, causando
o redirecionamento do browser ao novo endereço que é automaticamente determi-
nado pelo uso do recurso (@user) como parâmetro. Nesse caso, o Rails irá redireci-
onar para a ação show do objeto @user.
O segundo parâmetro do método redirect_to é um hash de opções. Nesse caso,
estamos usando um atalho para escrever uma mensagem via flash.
O flash é fundamentalmente um hash, que nesse caso estamos escrevendo na
chave :notice. Portanto a linha poderia ser reescrita da seguinte forma:
flash[:notice] = 'Cadastro criado com sucesso!'
redirect_to @user
Uma característica importante do flash é que seu conteúdo é guardado na ses-
são do usuário e o seu conteúdo é eliminado em toda requisição. Quando você es-
creve no flash, o conteúdo só estará disponível na próxima requisição. Por isso, é
normalmente utilizado em conjunto com redireções.
Finalmente, se o objeto não pôde ser salvo, executamos:
render :new
Isso porquê a ação create não possui um template/apresentação para si, então re-
aproveitamos o template do new para apresentar novamente o formulário e o usuário
ter a oportunidade de corrigir os dados.
122
Casa do Código
Capítulo 6. Tratando as requisições Web
redirect_to ou render?
É comum em ações create e update redirecionar o usuário para uma outra
página quando tudo está certo. Porém, no caso de erros no objeto não é
recomendado redirecionar. A razão é que, ao redirecionar, perdemos to-
dos os parâmetros que o usuário enviou e também as mensagens de erro
de validação quando executamos o método #save. Para essas situações,
devemos usar o #render, que não causa o redirecionamento.
6.3
Controle o mass-assignment
Agora que entendemos bem o que a ação create faz, vamos executá-la! Vá ao for-
mulário de cadastro (http://localhost:3000/users/new) e preencha o formulário. Ao
clicar em ‘Create User’...
Can't mass-assign protected attributes: password_confirmation
Erro de mass-assignment. O problema que estamos encontrando agora é na ver-
dade uma proteção para o seu site. Imagine que temos o campo admin no modelo
User, que é um campo booleano (ou seja, aceita apenas valores true ou false).
Mesmo sem o campo admin no formulário, um usuário mal intencionado pode
forjar uma requisição da seguinte forma:
{
"user" => {
"full_name"=>"Pirata Malandro",
"location"=>"Caribe",
"email"=>"[email protected]",
"password"=>"123",
"password_confirmation"=>"123",
"bio"=>"Rárr! Vou adquirir acesso de admin!",
"admin"=>"1"
}
}
Mesmo seu formulário não tendo o campo admin, o modelo User iria marcá-lo
como true e pronto, facilmente um usuário pode forjar dados em seu sistema sem
você perceber.
123
6.3. Controle o mass-assignment
Casa do Código
É por isso que, nas últimas versões do Rails, você é obrigado a criar uma
white-list, ou seja, uma lista com os atributos que podem ser associados via mass-
assignment. E, no nosso caso, o campo password_confirmation não está nessa lista.
Abra o modelo User (app/models/user.rb):
class User < ActiveRecord::Base
attr_accessible :bio, :email, :full_name, :location, :password
validates_presence_of :email, :full_name, :location, :password
validates_confirmation_of :password
validates_length_of :bio, :minimum => 30, :allow_blank => false
validates_format_of :email, :with => /\A[^@][email protected]([^@\.]+\.)+[^@\.]+\z/
end
Veja na segunda linha. A class macro attr_accessible (cuidado para não con-
fundir com attr_acessor!) aceita uma lista de atributos que podem ser associados
via mass-assignment. Vamos adicionar o atributo virtual password_confirmation à
ela:
class User < ActiveRecord::Base
# Cuidado para não esquecer a vírgula no final da linha.
attr_accessible :bio, :email, :full_name, :location,
:password, :password_confirmation
validates_presence_of :email, :full_name, :location, :password
validates_confirmation_of :password
validates_length_of :bio, :minimum => 30, :allow_blank => false
validates_format_of :email, :with => /\A[^@][email protected]([^@\.]+\.)+[^@\.]+\z/
validates_uniqueness_of :email
end
Depois dessa pequena alteração, reenvie o formulário, com todos os dados. Se
não houveram erros no formulário, um novo erro será exibido: Unknown action
(ação desconhecida), show. Isso significa que o objeto foi gravado com sucesso! Para
comprovar, vá ao console do Rails:
# Busca o último objeto no banco de dados (via id)
User.last
#<User id: 1, full_name: "Vinicius", ...>
124
Casa do Código
Capítulo 6. Tratando as requisições Web
6.4
Exibição do perfil do usuário
Agora vamos criar a ação e o template de show, para não haver mais erros. Vamos pri-
meiro ao controle UsersController (app/controllers/users_controller.rb):
class UsersController < ApplicationController
def new
@user = User.new
end
def show
@user = User.find(params[:id])
end
def create
@user = User.new(params[:user])
if @user.save
redirect_to @user, :notice => 'Cadastro criado com sucesso!'
else
render :new
end
end
end
Para a ação show, o código é simples: buscamos o objeto cujo id seja especificado
pelo parâmetro :id. Esse parâmetro vem da rota, ou seja, se acessarmos a URL http:
//localhost:3000/users/1, o parâmetro :id será 1. Em seguida, usamos o método .find
do modelo User para buscar exatamente aquele objeto. Associamos esse objeto à
variável de instância @user para acessarmos no template.
125
6.4. Exibição do perfil do usuário
Casa do Código
E quando o id não existe?
Existe a possibilidade do usuário digitar um id não existente, ou um
link estar incorreto. Quando isso acontecer, o ActiveRecord irá disparar
uma exceção chamada ActiveRecord::RecordNotFound. Em desenvol-
vimento, vemos esse erro para ficar mais claro. Porém, o Rails sabe que
não deve exibir esse erro ao usuário real, quando o sistema estiver em
produção. Nesse caso, o Rails irá exibir a página de erro 404 Not Found.
Outras exceções que resultam em erro 404 Not Found em ambiente
de produção são: ActionController::RoutingError (uma rota não
existente) e AbstractController::ActionNotFound (rota existente mas
ação não encontrada).
Vamos ao template. Crie o arquivo app/views/users/show.html.erb conforme
o código a seguir:
<p id="notice"><%= notice %> </p>
<h2>Perfil: <%= @user.full_name %> </h2>
<ul>
<li>Localização: <%= @user.location %> </li>
<li>Bio: <%= @user.bio %> </li>
</ul>
<%= link_to 'Editar Perfil', edit_user_path(@user) %>
A estrutura desse template é um pouco diferente, pois estamos usando menos
métodos auxiliares do Rails e mais HTML. Na verdade, a estrutura de templates de
aplicações tendem a ser mais dessa forma, uma mistura de HTML com pinceladas
de ERB e métodos auxiliares.
O método notice é um método auxiliar do Rails para retornar o conteúdo do
flash[:notice]. Mas o método mais importante para nós neste momento é o mé-
todo link_to.
O método link_to serve para gerar links, através de tags <a>. O primeiro atri-
buto, como pode perceber, é o texto que vai no link, e o segundo é uma URL. O mé-
todo edit_user_path(@user) é um método gerado pelo roteador de acordo com
126

Casa do Código
Capítulo 6. Tratando as requisições Web
seu arquivo de rotas. O método, por sua vez, vai gerar a rota correta para a ação de
edit do usuário @user.
Com o template pronto, quando você criar um novo usuário, você verá a seguinte
imagem:
Figura 6.5: Perfil do usuário, depois de efetuar um cadastro
6.5
Permita a edição do perfil
A imagem anterior mostra que temos uma tela bem simples, mas já estamos come-
çando a amarrar toda a funcionalidade de cadastro. Ainda faltam as ações de edit e
update. Elas são bem parecidas com as ações new e create. Vamos primeiro à ação
edit, no controle UsersControllers (app/controllers/users_controller.rb):
class UsersController < ApplicationController
# Omitindo as outras ações para não atrapalhar...
# new
# show
# create
def edit
@user = User.find(params[:id])
end
end
Buscamos o objeto cujo ID vem do parâmetro, da mesma forma que a
ação show.
Fazemos isso para que o formulário venha populado com os da-
dos que o usuário já preencheu anteriormente. Vamos ao template da ação edit
(app/views/users/edit.html.erb):
127
6.6. Reaproveite as apresentações com partials
Casa do Código
<h1>Editar perfil</h1>
<%= form_for @user do |f| %>
<p>
<%= f.label :full_name %> <br />
<%= f.text_field :full_name %>
</p>
<p>
<%= f.label :location %> <br />
<%= f.text_field :location %>
</p>
<p>
<%= f.label :email %> <br />
<%= f.text_field :email %>
</p>
<p>
<%= f.label :password %> <br />
<%= f.password_field :password %>
</p>
<p>
<%= f.label :password_confirmation %> <br />
<%= f.password_field :password_confirmation %>
</p>
<p>
<%= f.label :bio %> <br />
<%= f.text_area :bio %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
6.6
Reaproveite as apresentações com partials
Você deve estar com uma grande sensação de déjà vu nesse momento. Outra sensa-
ção que você pode ter é desgosto por ter duas páginas tão parecidas, no bom e velho
copy’n’paste, que estraga a vida de tantas pessoas. Mas não se desespere! Vamos re-
solver isso agora.
O componente de templates do Rails (ActionView) possui um recurso chamado
partials, mini-templates que podem ser incluídos em templates, ou seja, uma partial
128
Casa do Código
Capítulo 6. Tratando as requisições Web
não é usada para renderizar diretamente uma ação, mas um template pode usar uma
ou mais partials para compor o resultado final.
Para criar uma partial, basta criar um template com seu nome iniciando em
_. Ou seja, nesse caso, vamos criar a partial “form”. Portanto, crie o arquivo
_form.html.erb, na pasta app/views/users/:
<%= form_for @user do |f| %>
<p>
<%= f.label :full_name %> <br />
<%= f.text_field :full_name %>
</p>
<p>
<%= f.label :location %> <br />
<%= f.text_field :location %>
</p>
<p>
<%= f.label :email %> <br />
<%= f.text_field :email %>
</p>
<p>
<%= f.label :password %> <br />
<%= f.password_field :password %>
</p>
<p>
<%= f.label :password_confirmation %> <br />
<%= f.password_field :password_confirmation %>
</p>
<p>
<%= f.label :bio %> <br />
<%= f.text_area :bio %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
Apenas um corte do título, o resto é mantido igual.
Nas partials também
temos acesso à variável @user. Como as ações edit e new usam a mesma va-
riável, isso tornam as coisas bastante convenientes. Agora vamos aos templates
app/views/users/new.html.erb e app/views/users/edit.html.erb:
app/views/users/new.html.erb:
129
6.6. Reaproveite as apresentações com partials
Casa do Código
<h1>Cadastro</h1>
<%= render 'form' %>
Note que o nome da partial, quando fazemos o render na página, não inclui o _.
O Rails incluirá automaticamente, sabendo que é uma partial.
app/views/users/edit.html.erb:
<h1>Editar perfil</h1>
<%= render 'form' %>
Depois de editar os dois arquivos, salve-os e navegue nas páginas. Funciona per-
feitamente, não? E qualquer modificação será refletida em ambas as páginas, evi-
tando o copy’n’paste, o trabalho repetitivo e evitando erros.
Ok,
agora
que
os
templates
estão
melhorados,
vamos
implemen-
tar
a
ação
update.
Abra
o
controle
UsersController novamente
(app/controllers/users_controller.rb):
class UsersController < ApplicationController
# Omitindo as outras ações para não atrapalhar...
# new
# show
# create
def edit
@user = User.find(params[:id])
end
def update
@user = User.find(params[:id])
if @user.update_attributes(params[:user])
redirect_to @user, :notice => 'Cadastro atualizado com sucesso!'
else
render :update
end
end
end
O funcionamento do update é quase o mesmo do create. A principal diferença é
que agora fazemos a atualização de um objeto específico, identificado pelo parâmetro
130

Casa do Código
Capítulo 6. Tratando as requisições Web
:id, ao invés de criar um novo objeto na coleção. O método #update_attributes
aceita um hash de atributos de maneira similar ao método .new para criar um novo
objeto. Inclusive, o método #update_attributes também possui a proteção de
mass-assignment.
Se a atualização do perfil ocorrer com sucesso, o usuário é redirecionado para a
página do seu perfil, contendo uma mensagem felicitando o ocorrido, como é pos-
sível ver na figura 6.6
Figura 6.6: Atualização de perfil com sucesso
Se na atualização ocorrer algum erro, o formulário de edição será exibido, de
forma que o usuário possa corrigir os dados.
6.7
Mostre os erros no formulário
Falando em erros, ao enviar um formulário que contenha informações preenchidas
incorretamente, os campos ficam marcados com a cor vermelha. Isso acontece pois
o Rails automaticamente adiciona classes ao campo do formulário quando há erro e,
graças ao scaffold que fizemos no capítulo 4, temos um CSS básico que formata esse
HTML.
131


Casa do Código
Capítulo 6. Tratando as requisições Web
<%= form_for @user do |f| %>
...
<% end %>
O que esse código faz é verificar se há algum erro (o método #any? retorna true
se pelo menos um elemento de uma Array é diferente de nil). Se houver um ou mais
erros, cria-se a div com uma mensagem e em seguida lista todas as mensagens de
erro. O resultado é o seguinte:
Figura 6.8: Mensagem de erro com mensagem amigável
Agora o usuário pode ler a mensagem e tomar uma ação para corrigir seus er-
ros. O funcionamento das páginas está correto, porém vamos adicionar links para
melhorar a navegação.
6.8
Configure a ação raiz (root)
A ação raiz, ou root, é a ação que é executada quando vamos ao endereço raiz do site,
por exemplo, http://www.colcho.net, ou http://localhost:3000/. O que temos agora
133
6.8. Configure a ação raiz (root)
Casa do Código
é ainda a página de boas vindas do Rails. Vamos mudar isso.
Primeiro, remova o arquivo public/index.html, caso contrário o Rails sem-
pre irá responder com essa página, ignorando seu arquivo de rotas. Vamos editar o
routes.rb para incluir a rota root:
Colchonet::Application.routes.draw do
resources :rooms
resources :users
root :to => "home#index"
end
A notação de controles, quando mencionados manualmente, é o seu
nome (por exemplo, “users” para o UsersController), sustenido (#, ou
também ‘jogo da velha’) e o nome da ação.
Dessa forma, “home#index”
aponta para o controle HomeController,
ação index.
Crie o arquivo
app/controllers/home_controller.rb:
class HomeController < ApplicationController
def index
end
end
Boa prática: sempre declare todas as ações
Quando uma ação não faz nada, é possível simplesmente não imple-
mentar nenhum método no controle e apenas criar o template.
O
HomeController, por exemplo, não necessitaria do método index, pois
tem o template para exibir a página e não contém absolutamente nada.
Porém, é sempre interessante declarar todos os métodos que o controle
responde, para que fique claro ao leitor do código (que pode ser você
mesmo depois de algumas alguns anos, lembre-se disso!).
Agora,
crie
a
pasta
app/views/home
e
o
template
app/views/home/index.html.erb:
<h1>Colcho.net</h1>
134
Casa do Código
Capítulo 6. Tratando as requisições Web
<p>
Escolha uma das ações a seguir:
<ul>
<li><%= link_to "Cadastro", new_user_path %> </li>
<li><%= link_to "Visualizar quartos", rooms_path %> </li>
</ul>
</p>
Agora sim, temos links para as principais páginas. Vamos adicionar dois novos
links, para que o usuário possa desistir de cadastrar ou atualizar o seu perfil:
app/views/users/new.html.erb:
<h1>Cadastro</h1>
<%= render 'form' %>
<%= link_to "Voltar", root_path %>
app/views/users/edit.html.erb:
<h1>Cadastro</h1>
<%= render 'form' %>
<%= link_to "Voltar", @user %>
Note que, como as rotas são diferentes, não colocamos na partial, mas no tem-
plate em si. No edit usamos a capacidade do Rails de determinar a rota de show de
um recurso, apenas colocando o objeto como parâmetro de URL.
Por
fim,
vamos
editar
a
página
do
perfil
do
usuário,
app/views/users/show.html.erb, adicionando um link ao final:
...
<%= link_to 'Editar Perfil', edit_user_path(@user) %>
<%= link_to 'Home', root_path %>
A rota root_path é especial, e aponta para a raiz do site. Pronto, agora temos
interligação entre todas as páginas do site, inclusive à área de quartos que deixamos
intocado por enquanto. Chegaremos lá.
135
Capítulo 7
Melhore o projeto
Se longe enxerguei foi porque me apoiei no ombro de gigantes.
– Isaac Newton
7.1
Lição obrigatória: sempre aplique criptografia
para armazenar senhas
Se você abrir o console do Rails e procurar pelos cadastros, vai poder observar que
é possível ler as senhas dos seus usuários e isso não é nem um pouco profissional.
# Senha nada secreta...
User.first
# <User id
: 1,
#
full_name : "Vinicius",
#
: "[email protected]",
#
password
: "segredo",
#
location
: "San Francisco, CA",
#
bio
: "O meu perfil anterior...",
7.2. Como adicionar plugins ao projeto?
Casa do Código
#
created_at : "2012-06-21 05:37:34",
#
updated_at : "2012-06-22 06 : 36 : 59">
Vamos usar uma funcionalidade do Rails para guardar as senhas usando encrip-
tação BCrypt, chamada has_secure_password. Para isso, vamos precisar mudar a
coluna password no banco de dados para password_digest e mudar algumas coisas
no modelo.
Encriptação de senha
O algoritmo usado pelo Rails para encriptar senhas é o BCrypt, bastante
eficiente e extremamente recomendado. Nesse algoritmo, a senha em
texto puro passa por diversos cálculos matemáticos que a torna total-
mente cifrada e é um processo irreversível (por isso o nome “digest”).
Para verificar se uma senha é válida, devemos fazer os mesmos cálculos
com a senha que o usuário fornecer e comparar com o resultado final
que tivermos no banco de dados. Se forem iguais, o usuário digitou a
senha corretamente. Se alguém obter acesso ao seu banco de dados, será
impossível reverter o processo e obter o texto original.
Este é um processo completamente seguro, se o algoritmo utilizado for
forte, como é o BCrypt. Antigamente usava-se algoritmos como MD5 e
SHA1, mas já foram provados serem ruins para esse fim, pois há muita
colisão (strings diferentes que resultam no mesmo “digest”), além de ou-
tros fatores.
7.2
Como adicionar plugins ao projeto?
Para que o Rails consiga usar o algoritmo BCrypt, precisamos fazer com que o Rails
use a gem chamada bcrypt-ruby. Para isso, abra o arquivo Gemfile na raiz do seu
projeto e descomente a linha mencionando a gem:
# To use ActiveModel has_secure_password
gem 'bcrypt-ruby', '~> 3.0.0'
Em seguida, usando o terminal, vá à raiz do seu projeto e execute o comando
bundle:
138
Casa do Código
Capítulo 7. Melhore o projeto
$ bundle
Using rake (0.9.2.2)
Using i18n (0.6.0)
Using multi_json (1.3.6)
Using activesupport (3.2.8)
Using builder (3.0.0)
...
Using activeresource (3.2.8)
Installing bcrypt-ruby (3.0.1) with native extensions
Using bundler (1.1.4)
...
Your bundle is complete!
Esse comando faz com que a gem seja baixada e instalada em seu sistema. O ar-
quivo Gemfile e o comando bundle fazem parte de uma ferramenta chamada Bun-
dler (http://gembundler.com/). Ela é uma ferramenta complexa e bastante poderosa
de gerenciamento de dependências dos seus aplicativos. O Rails usa-o para carregar
todas as bibliotecas com as versões corretas. O trabalho pesado fica para a ferra-
menta, para nós programadores nos resta manter o arquivo Gemfile organizado.
Após a execução do bundle, o bundler irá gerar uma nova versão do
Gemfile.lock, que contém exatamente todas as dependências e versões de gems
que satisfazem as dependências da aplicação Rails. Se você tiver curiosidade, pode
ver as dependências de uma gem vendo o conteúdo desse arquivo.
Reiniciando o console e o servidor
Em mudanças fundamentais como essa, é necessário reiniciar tanto o
console quanto o servidor, mesmo com o uso do reload!. Isso deve-
se ao fato de que o Rails carrega suas dependências no momento que é
iniciado e o novo código sendo carregado faz referências às bibliotecas
que o Rails não carregou anteriormente.
7.3
Migração da tabela users
Uma vez a gem instalada, é necessário satisfazer a segunda exigência do
has_secure_password, que é ter a coluna password_digest. Para isso, vamos gerar
uma nova migração. Execute, na pasta raiz do projeto:
139
7.3. Migração da tabela users
Casa do Código
$ rails generate migration rename_password_on_users
invoke active_record
create
db/migrate/20120623053658_rename_password_on_users.rb
Com a migração gerada, abra o arquivo recém criado. O nome do arquivo irá
variar conforme a hora que a migração foi gerada, mas você pode usar a saída do
comando de geração da migração para saber exatamente qual arquivo editar.
Você terá o seguinte:
class RenamePasswordOnUsers < ActiveRecord::Migration
def up
end
def down
end
end
Como nossa mudança é simples, não é necessário especificar o código para #up
e #down separadamente, vamos usar o #change:
class RenamePasswordOnUsers < ActiveRecord::Migration
def change
rename_column :users, :password, :password_digest
end
end
Agora basta executar a migração com a tarefa rake rake db:migrate:
$ rake db:migrate
== RenamePasswordOnUsers: migrating ====================
-- rename_column(:users, :password, :password_digest)
-> 0.0084s
== RenamePasswordOnUsers: migrated (0.0085s) ===========
140
Casa do Código
Capítulo 7. Melhore o projeto
Por que não fizemos certo desde o início?
Você deve estar se perguntando... Por que não fizemos certo desde o iní-
cio? A resposta é simples. É raro o dia que sabemos toda a modelagem
do nosso sistema logo de início. É quase certo que um dia você precisará
alterar o modelo por causa de alguma decisão tomada ou algum conhe-
cimento adquirido. O Rails dá ferramentas para dar suporte a esse tipo
de desenvolvimento, ao invés de gastar dias e dias com modelagem que
no final pode não ser necessária. O mesmo acontece com o colcho.net!
Migração feita, agora é hora de alterar o modelo. Segundo a documentação
do has_secure_password (procure por has_secure_password no site http://api.
rubyonrails.org, se tiver interesse), essa class macro já nos dá as validações de con-
firmação de senha e a presença de senha. Além disso, ela cria dois novos atributos
virtuais, o password e password_confirmation. Portanto, o que devemos fazer no
model User é:
• Remover a validação de presença de senha;
• Remover a validação de confirmação de senha;
• Adicionar a class macro has_secure_password
Contudo, não é necessário alterar nenhum formulário, já que a ainda usaremos
o campo password nos formulários. Portanto a única alteração é o modelo User
(app/models/user.rb), que deverá ficar assim:
class User < ActiveRecord::Base
attr_accessible :bio, :email, :full_name, :location, :password,
:password_confirmation
validates_presence_of :email, :full_name, :location
validates_length_of :bio, :minimum => 30, :allow_blank => false
validates_format_of :email, :with => /\A[^@][email protected]([^@\.]+\.)+[^@\.]+\z/
validates_uniqueness_of :email
has_secure_password
end
141



Casa do Código
Capítulo 7. Melhore o projeto
Os formulários são bem agressivos visualmente:
Figura 7.2: Formulário com erro
Com um pouco de HTML e CSS, vamos deixar o site um pouco mais agradável
de ser visto:
Figura 7.3: Nova home
Vamos também melhorar a exibição de erros para que as mensagens fiquem lado
143

Casa do Código
Capítulo 7. Melhore o projeto
Conhecimentos de HTML e CSS
Infelizmente não dá para explicar detalhadamente cada parte do que va-
mos fazer em seguida, pois este não é o foco do livro. Se você gostaria
de aprender mais sobre esses assuntos (recomendado se você quiser se
tornar um profissional completo de web!), recomendo fortemente a lei-
tura do livro HTML5 e CSS 3: Domine a web do futuro, escrito pelo Lucas
Mazza.
7.5
Trabalhe com layout e templates para melho-
rar sua apresentação
O primeiro passo é alterar o layout da aplicação para incluir o cabeçalho com o menu
na direita. Para isso, abra o arquivo app/views/layouts/application.html.erb e
coloque o seguinte conteúdo:
<!DOCTYPE html>
<html>
<head>
<title>Colchonet</title>
<link href='http://fonts.googleapis.com/css?family=Pacifico'
rel='stylesheet' type='text/css'>
<%= stylesheet_link_tag
"application", :media => "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
</head>
<body>
<header>
<div id="header-wrap">
<h1><%= link_to "colcho.net", root_path %> </h1>
<nav>
<ul>
<li><%= link_to "Quartos", rooms_path %> </li>
<li><%= link_to "Cadastro", new_user_path %> </li>
</ul>
</nav>
</div>
</header>
145
7.5. Trabalhe com layout e templates para melhorar sua apresentação
Casa do Código
<div id="content">
<% if notice.present? %>
<p id="notice"><%= notice %> </p>
<% end %>
<% if alert.present? %>
<p id="alert"><%= alert %> </p>
<% end %>
<%= yield %>
</div>
</body>
</html>
As modificações nesse template foram:
• A inclusão do CSS para o uso da fonte “Pacifico”, disponível no Google Web
Fonts para o nosso logo (não se preocupe se você não tiver acesso à internet,
vamos usar fontes fallback);
• A criação da tag header e seu conteúdo, o logotipo e o menu de navegação;
• Embrulhar o yield em uma tag content, para centralizar todo o conteúdo
dos templates.
Antes de continuar, vamos tirar a referência ao flash da ação show do usuário e
alterar a tag h2 para h1:
<h1>Perfil: <%= @user.full_name %> </h1>
<ul>
<li>Localização: <%= @user.location %> </li>
<li>Bio: <%= @user.bio %> </li>
</ul>
<%= link_to 'Editar Perfil', edit_user_path(@user) %>
Como agora temos o nome do site e o menu em todas as páginas, aquela página
inicial (ou “home”) que criamos na seção 6.2 fica redundante. Vamos alterá-la para
mostrar ao usuário até no máximo três quartos que já foram cadastrados.
Antes de alterar o template,
porém,
precisamos alterar o controle
HomeController. Edite o arquivo app/controllers/home_controller.rb:
146
Casa do Código
Capítulo 7. Melhore o projeto
class HomeController < ApplicationController
def index
@rooms = Room.limit(3)
end
end
O método .limit é um método que faz uma busca no modelo Room, limitando
a três resultados. A ordem depende do banco de dados, pois a query SQL resultante
é:
SELECT "rooms".* FROM "rooms" LIMIT 3
Por enquanto isso é suficiente. Agora vamos editar o template do controle
HomeController, ação index (arquivo app/views/home/index.html.erb):
<h1>Quartos recém postados</h1>
<ul>
<% @rooms.each do |room| %>
<li><%= link_to "#{room.title}, #{room.location}", room %> </li>
<% end %>
</ul>
Se você fizer um refresh na página inicial, já vai observar a inclusão desses quar-
tos. Mas antes de continuarmos, vamos dar uma melhorada no código anterior.
Você pode estar se perguntando: “qual o problema desse código?”. E a resposta
é simples: esse é um típico caso em que estamos violando uma regra de negócio
que parece inofensiva: o nome completo de um quarto. Mas em casos como esse é
comum que os programadores acabem repetindo a composição diversas vezes em
templates espalhados. E o dia que você mudar essa lógica, vai se perder em diversos
“search and replaces”. Para isso não acontecer, vamos fazer do jeito certo: o template
deve saber o mínimo possível de como o modelo é feito.
Abra agora o então intocado modelo Room (app/models/room.rb) e adicione o
método #complete_name:
class Room < ActiveRecord::Base
attr_accessible :description, :location, :title
def complete_name
"#{title}, #{location}"
end
end
147
7.6. O que é o Asset Pipeline?
Casa do Código
Voltando ao template (app/views/home/index.html.erb):
<h1>Quartos recém postados</h1>
<ul>
<% @rooms.each do |room| %>
<li><%= link_to room.complete_name, room %> </li>
<% end %>
</ul>
O resultado visual é o mesmo, mas o código do template está mais limpo, então
podemos continuar.
7.6
O que é o Asset Pipeline?
Antes de começarmos a modificar os stylesheets (CSSs) da aplicação, vamos parar
um pouco e entender mais sobre um dos componentes mais controversos, em minha
opinião, do Rails.
O Asset Pipeline vem para resolver um problema complicado da web moderna: a
entrega de assets. O que significa isso? Não seria simplesmente entregar um arquivo
javascript ou stylesheets ao browser do usuário?
A resposta não é tão simples. Como as aplicações web tem se tornado complexas,
diversas ferramentas estão sendo utilizadas para tornar o desenvolvimento de javas-
cripts e stylesheets mais produtivo. Além disso, é necessário diminuir ao máximo o
tempo que o browser leva para baixar esses arquivos, para que as aplicações web não
passem a percepção de serem lentas.
Dessa forma, hoje em dia, é comum que aplicações web possuam uma ou mais
das seguintes fases para a entrega de um asset:
1) Pré-compilação: Transformação de um SCSS ou LESS em CSS puro, ou CoffeeS-
cript em JavaScript;
2) Concatenação: Juntar todos os arquivos CSS em um único .css, ou todos os
arquivos JavaScript em um único .js;
3) “Minificação": usar técnicas para diminuir o tamanho dos arquivos, como reno-
mear variáveis (de “essaVariavelEhGrande” para “a”);
4) Compressão: Usar compressão GZip para diminuir ainda mais o tamanho do
arquivo, caso o browser tenha suporte (a maioria dos browsers modernos possui
suporte a GZip).
148
Casa do Código
Capítulo 7. Melhore o projeto
SCSS e CoffeeScript são opcionais
Por padrão, o Rails já instala SASS (e por consequência, SCSS) e Coffe-
eScript em seu projeto. Porém, o uso de SCSS e CoffeeScript é opcional,
ou seja, você pode continuar escrevendo JavaScript e CSS puro e ainda
aproveitar dos outros benefícios do Assets Pipeline.
Em conjunto com tudo isso, existe outra preocupação bastante importante, que
é o caching. Além do seu browser, existem diversos elementos de rede que podem
fazer caching de assets (arquivos estáticos), como seu provedor de internet, firewalls,
CDNs (Content Delivery Network, ou rede de entrega de conteúdo), equipamentos
de rede, entre outros. O problema é que, quando você atualiza um asset no servidor,
o conteúdo desse arquivo pode estar em cache e portanto seus usuários podem ter
problemas ao acessar o site.
Todos os problemas e processos são bastante complicados, e é o objetivo do Asset
Pipeline tornar isso tudo o mais simples possível. Como isso é feito?
Primeiro, o Rails, através de uma biblioteca chamada sprockets, lê cada mani-
festo da sua aplicação. Um manifesto nada mais é do que um arquivo que contém
diretivas declarando quais são os arquivos que são dependência do seu aplicativo.
Com essa lista de arquivos, o Rails compila cada um de acordo com as extensões
utilizadas. Por exemplo, se o nome do seu stylesheet for users.css.scss.erb, o
stylesheet vai primeiro ser pré-processado em ERB e, em seguida, SASS (SASS e SCSS
são pré-processados pelo próprio SASS), gerando um arquivo CSS final.
Após obter todos os arquivos pré-compilados, o Rails irá juntar todos os arqui-
vos de um manifesto e gerará um arquivo cujo nome é o nome desse manifesto.
Por exemplo, se o manifesto application.css mencionar os arquivos footer.css
e reset.css e o manifesto admin.css mencionar apenas o reset.css, o resultado
final desse processo serão dois únicos arquivos, o application.css, contendo o
footer.css e o reset.css concatenados e o admin.css, apenas o reset.css.
No final, o browser do usuário terá que baixar muito menos arquivos, tornando
a carga da página mais rápida.
Após a concatenação, o que acontece é a minificação. O Rails (ou mais especifi-
camente, o sprockets) gera uma versão minificada de cada arquivo gerado no passo
anterior, usando ferramentas bastante eficazes. O processo de minificação gera um
149
7.7. Criando os novos stylesheets
Casa do Código
arquivo javascript ou stylesheet completamente válido, mas praticamente ilegível. O
único objetivo desse passo é diminuir o tamanho do texto a ser baixado pelo usuário.
Chegando ao final, o Rails calcula o digest MD5 do arquivo e adiciona ao nome
do arquivo. Por exemplo, se o arquivo chama-se application.css, ele se tornará
algo do tipo:
application-e049a640704156e412f6ee79daabc7f6.css
Esse número gerado depende do conteúdo do arquivo, portanto se uma nova
versão desse asset for gerada, esse número será alterado. Isso fará com que o meca-
nismo de caching não seja acionado caso uma nova versão do asset seja compilada.
Por fim, há a compressão desses assets com o algoritmo GZip, gerando:
application-e049a640704156e412f6ee79daabc7f6.css.gz
Com o suporte a GZip de servidores como Apache (http://httpd.apache.org/) ou
nginx (pronuncia-se “engine x”, ou ênginéx) (http://http://nginx.com/) e dos brow-
sers, a entrega desses assets se torna muito mais eficiente e diminuindo o tempo de
carga da página.
Note que, para esse mecanismo funcionar, é estritamente necessário que execu-
temos a pré-compilação desses assets nos servidores:
rake assets:precompile
Isso irá gerar os arquivos de assets, que não serão entregues pelo Rails, mas sim
pelo seu servidor web.
No modo de desenvolvimento de aplicações Rails, que é o modo que estamos
executando, as coisas são um pouco mais simples. Ao invés de serem concatena-
dos e compactados, os assets são apenas pré-compilados automaticamente por cada
página.
Agora que entendemos um pouco do que é o Asset Pipeline, vamos usá-lo para
criar stylesheets para o Colcho.net.
7.7
Criando os novos stylesheets
Abra o arquivo app/assets/stylesheets/application.css. Você pode ver que
não há nenhum código CSS de fato, mas há um conjunto de comentários importantes
que estão lá para compor o que chamamos de “manifesto”, que são:
150
Casa do Código
Capítulo 7. Melhore o projeto
/*
*= require_self
*= require_tree .
*/
Esses dois comentários são diretivas do sprockets (note o “=” na linha). Eles
possuem um significado importante. O require_self faz com que qualquer CSS
que esteja no arquivo do manifesto seja incluído antes do restante, e o require_tree
varre toda a árvore de diretórios a partir do diretório especificado (no caso, “.”, ou
pasta atual) e inclui os stylesheets no manifesto.
Isso nos diz em termos práticos que qualquer CSS que incluirmos na pasta
app/assets/stylesheets/ será incluído no manifesto final, e isso é suficiente para
nós.
Não vamos mais utilizar o estilo gerado pelo scaffold, então vamos deletá-lo. Para
isso, basta apagar o arquivo app/assets/stylesheets/scaffolds.css.scss. Você
pode apagar também o CSS gerado pelo scaffold para o controle RoomsController:
app/assets/stylesheets/rooms.css.scss.
Vamos começar a criar o nosso próprio stylesheet. Como a maioria dos brow-
sers não possuem padrões razoáveis, é comum usar um stylesheet chamado “reset”,
que deixa todos os estilos em um mesmo padrão para que, a partir daí, possamos
construir o nosso próprio.
Normalmente usamos o famoso “Eric Meyer’s Reset” (http://meyerweb.
com/eric/tools/css/reset/), porém não precisamos de toda a cobertura do “re-
set” do Meyer.
Portanto, vamos criar o nosso próprio.
Crie o arquivo
app/assets/stylesheets/reset.css:
* {
margin: 0;
padding: 0;
text-decoration: none;
}
li { list-style: none; }
Esse CSS simples deixa o padding e a margin em valores zerados. Agora vamos
ao estilo do site em geral. Como o CSS possui algumas dezenas de linhas, vou mos-
trar pouco a pouco e você deve ir colocando cada trecho um após o outro.
Crie o arquivo app/assets/stylesheets/default.css.scss. Note que esse
não é um arquivo de CSS comum e portando deve ter a extensão .scss. Isso sig-
nifica que esse CSS é um SCSS, uma extensão de CSS que adiciona funcionalidades
151
7.7. Criando os novos stylesheets
Casa do Código
como regras aninhadas, variáveis, mixins e outras várias coisas úteis. Para entender
melhor como funciona, veja o site oficial do projeto SASS (http://sass-lang.com).
A primeira parte é a declaração de variáveis em SCSS, de forma que se quiser-
mos alterar, por exemplo, a largura do conteúdo, mudamos em apenas um lugar e a
alteração se reflete onde a variável estiver sendo usada:
$header-height: 55px;
$content-width: 700px;
$serif-families: "Pacifico", "Georgia", serif;
$sans-serif-families: "Helvetica", sans-serif;
$error-text-color: #B94A48;
$success-text-color: #468847;
Em seguida, declaramos o estilo geral da página, como cor do fundo, tamanho e
família da fonte do texto do site (usando a variável $sans-serif-families):
* {
font-family: $sans-serif-families;
font-size: 14px;
}
body { background-color: #f5f5f5; }
Declaramos um mixin para facilitar a declaração de box-shadow, colocando to-
dos os vendor prefixes quando necessário:
@mixin shadow($color, $x, $y, $radius) {
-moz-box-shadow:
$color $x $y $radius;
-webkit-box-shadow: $color $x $y $radius;
box-shadow:
$color $x $y $radius;
}
Em seguida, vamos criar a barra de navegação, que fica no topo. Criamos a cor de
fundo da barra e uma sombra na parte inferior. Criamos também o #header_wrap,
que é responsável pela centralização do conteúdo da barra de navegação em conjunto
com o conteúdo que ficará posteriormente. Por fim, estilizamos o logotipo. Note que
vamos usar o mixin criado anteriormente via @include, evitando ter que repetir três
regras para cada vendor prefix.
152
Casa do Código
Capítulo 7. Melhore o projeto
header {
@include shadow(#ccc, 0, 3px, 6px);
border-bottom: 1px solid #686;
margin-bottom: 15px;
#header-wrap {
width: $content-width;
margin: 0 auto;
}
height: $header-height;
background-color: #9ECE71;
h1 {
float: left;
a {
color: #333;
font-family: $serif-families;
font-weight: 400;
font-size: 2.5em;
&:hover {
color: #000;
}
}
}
}
A segunda parte da barra superior, agora é o estilo do menu de navegação.
header nav {
line-height: $header-height;
float: right;
li {
display: inline;
background-color: #546f3c;
padding: 7px 10px;
-moz-border-radius: 5px;
border-radius: 5px;
a {
font-size: 12px;
153
7.7. Criando os novos stylesheets
Casa do Código
font-weight: 600;
color: #fff;
}
}
}
Em seguida temos o estilo geral do conteúdo do site, por isso fica
dentro da div com id content, como deixamos no layout da aplicação
(app/views/layouts/application.html.erb). Nesse trecho é interessante obser-
var como podemos fazer referência a um seletor pai usando &.
#content {
text-align: left;
width: $content-width;
margin: 0 auto;
h1 { font-size: 1.5em; }
a, a:visited, a:hover {
color: #242;
&:hover { text-decoration: underline; }
}
ul, form, p {
margin: 10px 0;
}
}
Para o formulário, temos o seguinte CSS, sem segredos:
form {
label {
display: block;
margin: 5px 0;
color: #444;
}
input[type=text], input[type=password], textarea {
color: #444;
font-size: 12px;
border: 1px solid #ccc;
padding: 5px;
154
Casa do Código
Capítulo 7. Melhore o projeto
width: 200px;
outline: 0;
@include shadow(rgba(0,0,0, 0.1), 0px, 0px, 8px);
&:focus { border: 1px solid #c9c9c9; }
}
textarea {
width: 400px;
height: 200px;
}
}
Por fim, temos as classes relacionadas com a informação de erros e flash:
.field_with_errors {
display: inline;
label { color: $error-text-color; }
input[type=text], input[type=password], textarea {
border: 1px solid rgba(189,74,72, 0.5);
@include shadow(rgba(189,74,72, 0.2), 0px, 0px, 8px);
&:focus { border: 1px solid rgba(189,74,72, 0.6); }
}
}
.error_message {
margin-left: 5px;
display: inline;
color: $error-text-color;
}
.padded_flash {
padding: 10px;
margin: 10px 0;
font-weight: bold;
width: 500px;
}
#error_explanation {
155
7.8. Feedback em erros de formulário
Casa do Código
border: 1px solid $error-text-color;
color: $error-text-color;
background-color: #F2DEDE;
@extend .padded_flash;
}
#notice {
color: $success-text-color;
border: 1px solid $success-text-color;
@extend .padded_flash;
background-color: #DFF0D8;
}
#alert {
color: $error-text-color;
border: 1px solid $error-text-color;
@extend .padded_flash;
background-color: #F2DEDE;
}
Pronto, esse é todo o estilo que vamos aplicar ao site por enquanto. Salve o ar-
quivo e dê uma navegada. Bem melhor, não? Mas ainda não estamos 100% concluí-
dos. Precisamos corrigir o formulário de erros, e é o que vamos fazer agora.
7.8
Feedback em erros de formulário
Abra o arquivo app/views/users/_form.html.erb. Removeremos as tags <br/>,
pois nossos labels estão com display:
block e simplificaremos a parte superior
com a notificação de erros antiga. O problema com essa notificação é que fica difícil
para o usuário corrigir facilmente cada campo. Agora, vamos colocar a notificação
de erro imediatamente ao lado do campo. Veja o exemplo para o campo “nome
completo":
<p>
<%= f.label :full_name %>
<%= f.text_field :full_name %>
<% if @user.errors.has_key? :full_name %>
<div class="error_message">
<%= @user.errors[:full_name].first %>
</div>
156

Casa do Código
Capítulo 7. Melhore o projeto
<% end %>
</p>
O que fizemos no código anterior, apesar de parecer complicado, é simplesmente
verificar se há erro em um atributo específico (neste exemplo, o nome completo).
Caso haja erros, vamos criar uma tag div com classe error_message e mostramos
o primeiro erro, para não confundir o usuário com muitas mensagens de erro. O
resultado é o seguinte:
Figura 7.5: Campo com notificação de erro
7.9
Duplicação de lógica na apresentação nunca
mais. Use os Helpers
Se você continuou e colocou o código para mostrar a mensagem de erro no formu-
lário para todos os campos, deve ter percebido como ficou poluído e repetitivo. Isso
é um alarme! Primeiro que, para cada campo do formulário, repetimos uma lógica
(um if) e segundo que é bastante trabalhoso. Imagine se quisermos colocar feedback
de erro também para o formulário de quartos?
Mas não faz sentido nenhum colocar esse tipo de lógica em qualquer uma das
camadas que vimos, ou seja, esse código não pertence nem a controles e nem a mo-
delos. É por isso que o Rails disponibiliza um local para você colocar esse tipo de
lógica: view helpers, ou mais conhecido apenas como helpers.
Os helpers são lugares adequados para este tipo de lógica, com o objetivo de tor-
nar os templates mais simples e elegantes.
157
7.9. Duplicação de lógica na apresentação nunca mais. Use os Helpers
Casa do Código
Cuidado com código complexo em helpers!
Lembre-se que código complexo nunca é bom. E pior ainda quando eles
estão em helpers. Se você estiver escrevendo helpers com muitas linhas
de código, ou muito if, tome isso como um alerta. Você pode estar es-
crevendo regras de negócio na camada errada, ou talvez você possa estar
tentando fazer muita coisa em um lugar só. Tente colocar as regras den-
tro do modelo ou, se pertinente, crie classes para te ajudar a estruturar
os dados de uma forma mais interessante para o template.
Vamos então criar o nosso primeiro helper.
Os geradores do Rails
acabam criando um por controle, e é uma forma bastante interessante de
organizá-los.
Porém, como vamos usar o helper em outros controles, va-
mos colocar no helper geral, o ApplicationHelper.
Portanto, abra o arquivo
app/helpers/application_helper.rb:
module ApplicationHelper
def error_tag(model, attribute)
if model.errors.has_key? attribute
content_tag :div, model.errors[attribute].first,
:class => 'error_message'
end
end
end
Os helpers são incluídos em cada template, portanto temos acesso aos modelos
via variáveis de instância, porém isso não é uma boa prática. Passamos o modelo
como parâmetro, junto com o atributo que queremos verificar por erros. O restante
é o mesmo que fizemos quando direto no template. A diferença é que ao invés de
gerar o HTML, usamos um helper do Rails para criar a tag para nós.
Você pode pensar, por que não retornamos simplesmente o HTML como texto
e imprimimos o resultado no template? A razão é que o Rails, por padrão, faz o
escaping de todo o conteúdo que não é marcado como seguro, evitando injeção de
código malicioso. Portanto, é mais seguro e mais simples usar os métodos do próprio
Rails.
O resultado visual é o mesmo, mas o template fica muito mais limpo:
158
Casa do Código
Capítulo 7. Melhore o projeto
<p>
<%= f.label :full_name %>
<%= f.text_field :full_name %>
<%= error_tag @user, :full_name %>
</p>
Por fim, o template app/views/users/_form.html.erb fica assim:
<% if @user.errors.any? %>
<div id="error_explanation">
Há erros no formulário, por favor verifique.
</div>
<% end %>
<%= form_for @user do |f| %>
<p>
<%= f.label :full_name %>
<%= f.text_field :full_name %>
<%= error_tag @user, :full_name %>
</p>
<p>
<%= f.label :location %>
<%= f.text_field :location %>
<%= error_tag @user, :location %>
</p>
<p>
<%= f.label :email %>
<%= f.text_field :email %>
<%= error_tag @user, :email %>
</p>
<p>
<%= f.label :password %>
<%= f.password_field :password %>
<%= error_tag @user, :password %>
</p>
<p>
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation %>
<%= error_tag @user, :password_confirmation %>
</p>
<p>
<%= f.label :bio %>
159
7.9. Duplicação de lógica na apresentação nunca mais. Use os Helpers
Casa do Código
<%= f.text_area :bio %>
<%= error_tag @user, :bio %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
Salve o arquivo e envie o formulário, com erros. Ficou legal, né? Como exer-
cício para você leitor, faça o mesmo para o formulário de quartos, que foi gerado
pelo scaffold. Aproveite e adicione algumas validações no modelo Room, pois não há
nenhuma, e teste os erros de formulário.
160
Capítulo 8
Faça sua aplicação falar várias
línguas
Lembrar-se que morreremos um dia é a melhor forma que conheço para evitar a
armadilha de pensar que temos algo a perder.
– Steve Jobs
8.1
O processo de internacionalização (I18n)
Por enquanto, todo o nosso formulário está em inglês, inclusive os erros. Fizemos
dessa forma de propósito, pois vamos usar o sistema de internacionalização (apesar
de, em português, a palavra “internacionalização” possuir 19 letras, em inglês ela
possui 20, internationalization, e portanto é normalmente abreviada por I + 18 letras
+ n, ou i18n) do próprio Rails.
O sistema de I18n do Rails é bastante simples. Ele funciona com um conjunto de
pares chave valor, sendo que uma chave é um identificador de uma mensagem (por
8.1. O processo de internacionalização (I18n)
Casa do Código
exemplo users.sign_up.success) e o valor é o seu texto (por exemplo “Cadastro
realizado com sucesso”). Todo o conjunto possui uma chave raiz que é o idioma (en
para inglês e pt-BR, para o português brasileiro).
Meu site só vai ser em português, preciso usar o I18n?
É sempre uma boa ideia deixar seu sistema sempre traduzido com o I18n.
A grande vantagem é que os templates ficarão muito mais limpos, mais
fáceis de serem trabalhados e revistos por pessoas não técnicas.
Na pasta config/locales ficam os arquivos contendo essas traduções, no for-
mato YAML. Se você abrir o arquivo config/locales/en.yml, que já vem com o
Rails, você observará a seguinte estrutura:
en:
hello: "Hello world"
Isso significa que, quando o sistema estiver usando o locale “en” (inglês), quando
traduzirmos a chave hello, o resultado será “Hello world”.
Vamos começar colocando as mensagens já traduzidas do Rails, e depois vamos
traduzir os nossos atributos.
Os contribuidores do Rails já fizeram a tradução dos erros de validação e diver-
sas outras coisas para português! Portanto o nosso trabalho fica bastante facilitado,
basta baixar o arquivo de tradução (vá para http://colcho.net/rails-i18n e clique em
“Raw”) e salvar como config/locales/rails.pt-BR.yml. O nome do arquivo não
importa, mas vamos separar nossas mensagens das mensagens do Rails para fins de
organização.
Após salvar o arquivo, vamos alterar o idioma padrão do Colcho.net para por-
tuguês brasileiro. Para isso, abra o arquivo config/application.rb e procure a se-
guinte linha:
# The default locale is :en and all translations from
#
config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path +=
#
Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
Descomente e altere o parâmetro para :'pt-BR':
162

Casa do Código
Capítulo 8. Faça sua aplicação falar várias línguas
# The default locale is :en and all translations from
#
config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path +=
#
Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
config.i18n.default_locale = :'pt-BR'
Aproveite também e acerte o fuso horário:
# Set Time.zone default to the specified zone
#
and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks
#
for finding time zone names. Default is UTC.
config.time_zone = 'Brasilia'
Em seguida, reinicie o servidor. Vá a um dos formulários e tente enviar os dados
em branco. Et voilà! Erros em português:
Figura 8.1: Erros traduzidos com o sistema de i18n
Certo, agora temos que traduzir os atributos do modelo User.
Para
fazer isso é, basta seguirmos as convenções do Rails.
Crie o arquivo
config/locales/pt-BR.yml:
pt-BR:
activerecord:
models:
user: Usuário
attributes:
163
8.1. O processo de internacionalização (I18n)
Casa do Código
user:
bio: Biografia
email: Email
full_name: Nome completo
location: Localização
password: Senha
password_confirmation: Confirme sua senha
Em seguida, reinicie o servidor do Rails e atualize a página. Agora sim, o formu-
lário faz sentido. Vamos colocar os atributos também para quartos:
pt-BR:
activerecord:
models:
room: Quarto
user: Usuário
attributes:
user:
bio: Biografia
email: Email
full_name: Nome completo
location: Localização
password: Senha
password_confirmation: Confirme sua senha
room:
description: Descrição
location: Localização
title: Título
Quando o arquivo YAML já existe, basta atualizar a página, não precisa reiniciar
o servidor. Assim, vá ao formulário de novos quartos e verifique se os campos foram
traduzidos corretamente.
164

Casa do Código
Capítulo 8. Faça sua aplicação falar várias línguas
Figura 8.2: Formulário com atributos traduzidos
8.2
Traduza os templates
Agora que temos a integração de I18n com o ActiveRecord, vamos traduzir os tem-
plates. Para isso, vamos ter que chamar o mecanismo de I18n quando for necessário.
Isso é feito com helpers t (ou translate), para tradução das chaves que já vimos, e l
(localize) para a localização de datas, ou seja, mostrar uma data no formato mais
adequado ao idioma corrente.
Vamos traduzir a página de cadastro,
que possui apenas um título
(app/views/users/new.html.erb).
Trocamos o texto “Cadastro” pela tradu-
ção da chave users.new.title:
165
8.2. Traduza os templates
Casa do Código
<h1><%= t 'users.new.title' %> </h1>
<%= render 'form' %>
<%= link_to t('links.back'), root_path %>
Sem criar a tradução no YAML, vá à página e veja o resultado. O Rails acaba im-
primindo a última chave numa tentativa de não deixar a página em branco. Porém,
o Rails gera um HTML específico para chaves não traduzidas:
<h1>
<span class="translation_missing"
title="translation missing: pt-BR.users.new.title">
Title
</span>
</h1>
...
Para corrigir isso, vamos ao YAML de tradução (config/locale/pt-BR.yml) e
vamos adicionar as chaves de acordo com o que criamos:
pt-BR:
users:
new:
title: Cadastro
links:
back: Voltar
activerecord:
# continua ...
Ao recarregar a página, veremos o título e o link traduzidos corretamente.
166
Casa do Código
Capítulo 8. Faça sua aplicação falar várias línguas
Dica: estilo para a classe CSS “translation_missing”
No ambiente de desenvolvimento, pode ser interessante colocar um CSS
específico para te ajudar a identificar onde faltam elementos para se-
rem traduzidos. Uma maneira de se fazer isso é criar um novo CSS,
app/assets/stylesheets/translation_missing.css.erb, com o se-
guinte conteúdo:
<% if Rails.env.development? %>
.translation_missing {
border: 3px dashed red;
}
<% end %>
Assim, o estilo só será aplicado no ambiente de desenvolvimento. Em
ambientes de teste e produção, nada será gerado, sem impacto ao CSS
final.
Há uma pequena melhoria que podemos fazer. O Rails automaticamente coloca
o nome do controle e da ação no “escopo” das chaves de tradução caso você queira,
bastando iniciar a chave com “.”. Por exemplo, se mudarmos o título para .title, a
chave utilizada será users.new.title, portanto vamos mudar a chave de tradução:
<h1><%= t '.title' %> </h1>
<%= render 'form' %>
<%= link_to t('links.back'), root_path %>
Aplicando essa mesma lógica na ação edit (app/views/users/edit.html.erb),
teremos:
<h1><%= t '.title' %> </h1>
<%= render 'form' %>
<%= link_to t('links.back'), @user %>
167
8.2. Traduza os templates
Casa do Código
Traduções para quartos
Os templates de quarto podem ser traduzidos da mesma maneira.
Aproveite para fazer o mesmo nos outros templates de quarto:
app/views/rooms/new.html.erb e app/views/rooms/edit.html.erb.
A ação show será um pouco diferente. Como no título temos o nome do usuário,
precisaremos descobrir como passar parâmetros para o sistema de I18n. Isso é feito
usando uma notação usando %{}. Veja como fica o título dessa página:
pt-BR:
users:
new:
title: Cadastro
edit:
title: Editar perfil
show:
title: "Perfil: %{user_name}"
edit: 'Editar perfil'
location: "Localização: %{location}"
bio: "Bio: %{bio}"
# ...
Veja que o uso de aspas é obrigatório para esse caso. Para usar no template, basta
passar um hash mapeando cada atributo a ser interpolado:
<h1><%= t '.title', :user_name => @user.full_name %> </h1>
<ul>
<li><%= t '.location', :location => @user.location %> </li>
<li><%= t '.bio', :bio => @user.bio %> </li>
</ul>
<%= link_to t('.edit'), edit_user_path(@user) %>
Estamos quase prontos, falta pouco!
Vamos à partial de formulário (app/views/users/_form.html.erb):
168
Casa do Código
Capítulo 8. Faça sua aplicação falar várias línguas
<% if @user.errors.any? %>
<div id="error_explanation">
<%= t 'general.form_error' %>
</div>
<% end %>
...
O layout (app/layouts/application.html.erb):
...
<nav>
<ul>
<li><%= link_to t('layout.rooms'), rooms_path %> </li>
<li><%= link_to t('layout.signup'), new_user_path %> </li>
</ul>
</nav>
...
E no YAML (config/locales/pt-BR.yml):
# ...
links:
back: Voltar
layout:
rooms: Quartos
signup: Cadastro
general:
form_error: Há erros no formulário, por favor verifique.
activerecord:
# ...
Pronto, todos os templates estão prontos para receberem outros idiomas! No
próximo capítulo, iremos trabalhar na última parte da funcionalidade de cadastro: o
envio de e-mail de boas vindas e confirmação de conta. Porém, antes de continuar,
um pequeno adendo.
169
8.3. Extra: alterar o idioma do site
Casa do Código
8.3
Extra: alterar o idioma do site
Vimos durante este capítulo como usar o mecanismo de internacionalização do Rails
e configuramos o idioma padrão como português brasileiro, mas não vimos ne-
nhuma maneira de usar outro idioma a qualquer momento. Vamos deixar o Col-
cho.net apenas em português, mas é importante para você leitor saber qual é a me-
lhor forma de fazer seu site suportar vários idiomas.
Então, qual é a melhor forma? Sem dúvidas, a melhor maneira de suportar idio-
mas diferentes no seu site é especificando o idioma via URL. Por exemplo, para o idi-
oma inglês, seria interessante colocarmos a URL www.colcho.net/en/users/new.
A principal razão é o caching: Como o caching é feito por URL, ter páginas dife-
rentes (uma para cada idioma) em uma mesma URL pode tornar o cache mais difícil
de ser construído e entregue. Quando o idioma faz parte da URL, não há necessidade
de alterações no mecanismo de caching.
Portanto, vamos ter que adicionar algum mecanismo nas rotas para que conte-
nha o idioma, sem afetar o que já construímos. Isso é possível através do uso de
scope na construção das rotas:
Colchonet::Application.routes.draw do
scope ":locale" do
resources :rooms
resources :users
resource :user_confirmation, :only => [:show]
end
root :to => "home#index"
end
A notação ":locale", como se fosse um símbolo, significa que todo o conteúdo
naquele segmento de caminho na verdade é um parâmetro (ou seja, acessível via o
hash params). Se você usar os helpers de rota do Rails, esse parâmetro será automa-
ticamente incluído sempre que pertinente, portanto não é necessário alterar nenhum
outro código.
170

Casa do Código
Capítulo 8. Faça sua aplicação falar várias línguas
O que é um segmento de caminho?
Segmento de caminho (path segments) é uma parte da URL que é sepa-
rada por “/” (como se fossem “pastas”). Por exemplo, na imagem a seguir,
o caminho (path) é /users/new, sendo users e new segmentos distintos:
Figura 8.3: Algumas partes de uma URL
Isso significa que, em toda requisição, o que estiver na URL no seg-
mento :locale será passado como parâmetro.
Por isso,
nos contro-
les
conseguimos
acesso
a
essa
variável.
No
ApplicationController
(app/controllers/application_controller.rb), podemos fazer o seguinte:
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :set_locale
def set_locale
I18n.locale = params[:locale]
end
end
Algumas coisas importantes aconteceram nesse código. Como o ActiveRecord,
os controles também possuem callbacks. No caso de controles, podemos criar fil-
tros através de class macros: before_filter para métodos executados antes da ação,
after_filter para depois, ou em volta, via around_filter. O filtro mais útil e mais
usado é o before_filter.
A class macro before_filter aceita como parâmetro um símbolo represen-
tando um nome de método ou um lambda, igual aos callbacks do ActiveRecord
171

Casa do Código
Capítulo 8. Faça sua aplicação falar várias línguas
ver um site incompleto ou até mesmo não funcional. Portanto, podemos criar uma
constraint na rota, ou seja, uma restrição, fazendo com que a rota não seja detectada
no caso do idioma não ser suportado.
Podemos usar expressões regulares para criar restrições. Usaremos uma expres-
são regular que apenas detecta se o :locale é en ou pt-BR:
Colchonet::Application.routes.draw do
scope ":locale", :locale => /en|pt\-BR/ do
resources :rooms
resources :users
resource :user_confirmation, :only => [:show]
end
root :to => "home#index"
end
Isso fará com que toda vez que a requisição chegar na aplicação, o Rails irá avaliar
a expressão regular e vai verificar se o match foi feito. Se não for feito, a rota será
ignorada. Com essa alteração, se o usuário tentar acessar o site com o :locale como
jp, ele vai receber o erro “404 Não encontrado”, pertinente para essa situação.
O esquema das rotas está quase completo. Existem porém um problema compli-
cado: se o usuário tentar acessar a home, ou rota raiz do site? O Rails não conseguirá
criar as rotas para os quartos na listagem, e o motivo é simples: as rotas para os
controles RoomsController e UsersController só existem se o parâmetro :locale
existir e, no caso da home, ela não existe.
Segmentos opcionais nas rotas
Para solucionar o problema, vamos tornar a presença do :locale opcional, tor-
nando o idioma padrão do site como português. Assim, no caso da rota de cadastro,
teremos as seguintes possibilidades:
• /users/new - cadastro em português;
• /pt-BR/users/new - cadastro em português;
• /en/users/new - cadastro em inglês;
• /jp/users/new - erro “página não encontrada” (jp, es, ou qualquer outro idi-
oma que não seja pt-BR ou en)
173
8.3. Extra: alterar o idioma do site
Casa do Código
Para segmentos opcionais, o Rails usa a mesma notação de campos opcionais em
expressões regulares:
Colchonet::Application.routes.draw do
scope "(:locale)", :locale => /en|pt\-BR/ do
resources :rooms
resources :users
resource :user_confirmation, :only => [:show]
end
root :to => "home#index"
end
Ainda falta a rota na home que responde a diferentes idiomas. No caso, se você
acessar a home, ela será tratada como se não houvesse nenhum idioma, indo para o
padrão português. Para que a raiz também responda a outros idiomas, temos que
adicionar uma nova rota:
Colchonet::Application.routes.draw do
LOCALES = /en|pt\-BR/
scope "(:locale)", :locale => LOCALES do
resources :rooms
resources :users
resource :user_confirmation, :only => [:show]
end
match '/:locale' => 'home#index', :locale => LOCALES
root :to => "home#index"
end
A rota do tipo match é o tipo de rota mais simples. Você pode desenhar qualquer
rota e ainda usar a notação ":segmento" para usar segmentos dentro do params.
Tome cuidado, porém, pois rotas assim podem deixar suas rotas mais complexas e
ainda podem ocasionar em bugs chatos de entender quando elas entram em conflito.
Adicionamos também a restrição de idiomas, extraindo a expressão regular para uma
constante.
Um problema que essa rota opcional gera é que todos os links da sua aplicação
teriam que passar a opção :locale => I18n.locale para todos os helpers de rota.
174
Casa do Código
Capítulo 8. Faça sua aplicação falar várias línguas
O Rails consegue centralizar essa opção se implementarmos um método chamado
default_url_options.
Precisamos também atualizar o filtro para que, caso o :locale não es-
teja definido, vamos usar o idioma configurado como padrão no arquivo
config/application.rb.
Por fim, o ApplicationController (app/controllers/application_controller.rb)
deverá ficar da seguinte forma:
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :set_locale
def set_locale
I18n.locale = params[:locale] || I18n.default_locale
end
def default_url_options
{ :locale => I18n.locale }
end
end
Se você achou complicado, não se assuste. Esse tipo de funcionalidade é uma
funcionalidade considerada avançada, com diversas ressalvas e pegadinhas. Con-
tudo, usamos as funcionalidades do Rails para tornar esse processo menos doloroso.
Note também que existem outras maneiras consideráveis de se alterar o idioma,
como por exemplo via diferentes domínios, via headers HTTP ou até GeoIP, um
banco de dados que, através do IP do usuário, determina-se o país e por consequên-
cia, seu idioma. Essas opções, apesar de interessantes, não serão abordadas. Se você
quiser saber como implementar essas e mais opções, verifique o guia oficial do Rails
no assunto: http://guides.rubyonrails.org/i18n.html.
Agora vamos começar a trabalhar no login do usuário!
175
Capítulo 9
O cadastro do usuário e a
confirmação da identidade
“Suor mais sacrifício é igual a sucesso”
– Charles Finley
9.1
Entenda o ActionMailer e use o MailCatcher
O Rails nos dá uma ferramenta de envio de emails chamada ActionMailer. Ela
funciona como se fosse um controle, ou seja, possui diversos pontos de entrada e
por fim resulta em um template que será enviado por email para o usuário.
Para fins de teste, iremos utilizar uma ferramenta excelente para enviar e-mails.
Ela chama-se mailcatcher, uma gem Ruby que disponibiliza dois serviços: um ser-
vidor de envio de emails SMTP, que será usado pelo Rails, e um servidor Web, no
qual podemos observar os emails que foram enviados, como uma caixa de entrada.
Para instalá-la, basta executar:
9.1. Entenda o ActionMailer e use o MailCatcher
Casa do Código
$ gem install mailcatcher
...
Successfully installed mailcatcher-0.5.7.1
8 gems installed
Ao executar o comando mailcatcher, temos:
$ mailcatcher
Starting MailCatcher
==> smtp://127.0.0.1:1025
==> http://127.0.0.1:1080
*** MailCatcher now runs as a daemon by default. Go to the web interface
to quit.
Uma vez em execução,
vamos voltar ao Rails.
Crie o arquivo
app/mailers/signup_mailer.rb da seguinte forma:
class SignupMailer < ActionMailer::Base
default :from => '[email protected]'
def confirm_email(user)
@user = user
@confirmation_link = root_url # Mudaremos no futuro
mail({
:to => user.email,
:bcc => ['sign ups <[email protected]>'],
:subject => I18n.t('signup_mailer.confirm_email.subject')
})
end
end
A classe SignupMailer herda de ActionMailer::Base, que contém todo o fer-
ramental necessário para envio de emails. Na primeira linha usamos a class macro
para criar valores default para todos os emails a serem enviados.
Em seguida, definimos a nossa “action” para o mailer, chamado de
confirm_email. Associamos duas variáveis de instância, para serem usados
no template, da mesma forma que os controles fazem. Por fim, criamos o email,
colocando os campos “to:”, “bcc:” e o assunto (subject) do email. Note que, para
termos acesso aos métodos de I18n fora dos templates, precisamos mencionar o
módulo I18n.
178
Casa do Código
Capítulo 9. O cadastro do usuário e a confirmação da identidade
Emails via BCC
É sempre uma boa ideia enviar emails para você mesmo usando o BCC.
Dessa forma, você pode ver exatamente o que o usuário recebeu, para
tentar ajudá-lo caso haja algum problema, como template quebrado ou
campos em branco.
Outro uso importante do BCC é para envio de emails em massa, para
evitar que os emails de todos os usuários fiquem à mostra.
9.2
Templates de email, eu preciso deles?
Vamos agora criar os templates para essa “ação”. Sim, templates no plural. Podemos
criar templates com HTML normalmente, mas é uma boa prática sempre entregar
o email com um template em texto puro também, de forma que leitores de emails
possam escolher a representação mais adequada. Se criarmos os dois templates da
maneira correta, o Rails já faz o trabalho de criar um email que contém as duas
representações (chamado de multipart) e enviar para o destinatário.
Crie a pasta signup_mailer na pasta app/views e lá vamos criar o template
confirm_email.html.erb:
<h1><%= t '.title' %> </h1>
<p>
<%= t '.body', :full_name => @user.full_name %>
</p>
<p>
<%= t '.confirm_link_html',
:link => link_to(@confirmation_link, @confirmation_link) %>
</p>
<p>
<%= t '.thanks_html', :link => link_to('Colcho.net', root_url) %>
</p>
Agora vamos fazer o template para o email em texto puro. Crie o arquivo
app/views/signup_mailer/confirm_email.text.erb com o seguinte conteúdo:
<%= t '.title' %>
179
9.2. Templates de email, eu preciso deles?
Casa do Código
<%= t '.body', :full_name => @user.full_name %>
<%= t '.confirm_link_html', :link => @confirmation_link %>
<%= t '.thanks_html', :link => root_url %>
Atenção aos links nos mailers
Preste atenção nos links que estamos criando nos templates de mailer.
Estamos usando o sufixo _url e não o _path, já que quando o usuário
receber o email, ele precisará receber o link completo, e não apenas o
caminho. Parece óbvio, mas como sempre usamos _path nos templates
em geral, é comum cometer essa confusão.
Para essas traduções, adicione as seguintes linhas no YAML do idioma português
(config/locales/pt-BR.yml), tomando o cuidado para colocar no nível correto, já
que a identação importa:
pt-BR:
# ...
signup_mailer:
confirm_email:
subject: 'Colcho.net - Confirme seu email'
title: 'Seja bem vindo ao Colcho.net!'
body: |
Seja bem vindo ao Colcho.net, %{full_name}.
O Colcho.net é o lugar ideal para você alugar aquele quarto
sobrando na sua casa e ainda conhecer gente do mundo inteiro.
confirm_link_html: 'Para você começar a usar o site, acesse o
link: %{link}'
thanks_html: 'Obrigado por se cadastrar no %{link}.'
links:
# ...
180
Casa do Código
Capítulo 9. O cadastro do usuário e a confirmação da identidade
Chaves com sufixo _html
Na seção 7.8 falamos de escaping de tags HTML e injeção de código ma-
licioso. O sistema de I18n também se preocupa com isso. Então, para
evitar que todos os seus links virem texto, é necessário colocar o sufixo
_html nas chaves de tradução, para que o texto final seja um HTML vá-
lido.
Vamos testar! Assumindo que temos um usuário cadastrado no banco de dados,
vamos tentar enviar um email para ele:
SignupMailer.confirm_email(User.first)
# ArgumentError: Missing host to link to!
#
Please provide the :host parameter,
#
set default_url_options[:host], or set :only_path to true
O problema está na hora de gerar links. Pode parecer simples, mas para o ser-
vidor Web saber em que endereço ele está não é uma tarefa trivial. Na verdade, ele
não tem como saber, dadas as maneiras que um servidor pode ser configurado. Por
isso, o que o Rails faz é confiar nos cabeçalhos HTTP (vários deles, dependendo de
onde veio a requisição) para montar a URL completa.
O problema é que, frequentemente, os mailers são executados fora do contexto
de requisição web, ou seja, em uma tarefa Rake, ou em jobs em segundo plano, e,
portanto, impossível saber o host do servidor. Dessa forma, o Rails requer que você,
manualmente, defina o host para essas situações. Essa configuração pode ser definida
por cada ambiente (desenvolvimento, teste e produção). Vamos corrigir para o nosso
ambiente de desenvolvimento.
Para isso, abra o arquivo config/environments/development.rb e adicione a
linha relacionado a default_url_options, antes do end no final:
# ...
# Expands the lines which load the assets
config.assets.debug = true
# Aponta o host para o ambiente de desenvolvimento
config.action_mailer.default_url_options = {
:host => "localhost:3000"
}
end # Não adicione esse end, é contexto apenas
181
9.2. Templates de email, eu preciso deles?
Casa do Código
Após reiniciar o console, conseguimos enviar o email:
SignupMailer.confirm_email(User.first).deliver
# => #<Mail::Message:70211024451560, Multipart: true ... >
Objetos criados e email teoricamente enviado, mas isso não significa muita coisa
para nós, não é mesmo? Vamos configurar o Rails para que ele possa enviar emails
de verdade, usando o serviço de SMTP do mailcatcher.
Para isso, abra novamente o config/environments/development.rb e coloque
as seguintes linhas abaixo da linha que você adicionou anteriormente:
# Aponta o host para o ambiente de desenvolvimento
config.action_mailer.default_url_options = {
:host => "localhost:3000"
}
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
:address => "localhost",
:port => 1025
}
end
Reinicie o console do Rails e tente novamente:
SignupMailer.confirm_email(User.first).deliver
# => #<Mail::Message:70316580535500, Multipart: true, ...>
Aponte o seu browser para o endereço do cliente do mailcatcher (http:
//localhost:1080) e veja seus emails.
Note que você pode ver também a ver-
são puro texto.
Excelente ferramenta, não é mesmo?
Agora, precisamos en-
tregar o email no momento do cadastro.
Abra o controle UsersController
(app/controllers/users_controller.rb):
class UsersController < ApplicationController
# ...
def create
@user = User.new(params[:user])
if @user.save
SignupMailer.confirm_email(@user).deliver
182
Casa do Código
Capítulo 9. O cadastro do usuário e a confirmação da identidade
redirect_to @user, :notice => 'Cadastro criado com sucesso!'
else
render :new
end
end
# ...
end
Lembre-se de reiniciar o servidor do Rails, caso não o tenha feito após as altera-
ções no arquivo config/environments/development.rb. Ao fazer um cadastro, o
email será disparado e capturado pelo mailcatcher.
Com isso, terminamos o envio de emails. O que nos resta agora é colocar o link
de verdade para a confirmação, ao invés de apontar para a página principal.
9.3
Mais emails e a confirmação da conta de usuá-
rio
Vamos agora criar a última parte que resta para terminar a funcionalidade de cadas-
tro, que é a confirmação de email. O que devemos fazer é: enviar o email ao usuário
e, quando ele clicar em um link único para a conta dele, a conta estará confirmada.
Ainda não temos login, porém iremos limitar o acesso do usuário enquanto sua conta
não for confirmada. Vamos deixar tudo pronto para que, no próximo capítulo, pos-
samos focar na lógica do login.
A ideia dessa funcionalidade é ter dois novos campos no modelo Usuário: con-
firmation_token e confirmed_at. O primeiro campo, confirmation_token, vamos
gerar no momento do cadastro do usuário e enviar o link contendo este token para o
email dele. O token tem que ser grande e aleatório, de maneira que seja praticamente
impossível ser adivinhado e com grande probabilidade de ser único por usuário. O
segundo, o confirmed_at é o campo que vamos marcar quando o usuário seguir o
link recebido no email, tornando o cadastro do usuário totalmente válido.
Para isso, basta criarmos uma nova migração:
$ rails generate migration add_confirmation_fields_to_users \
confirmed_at:datetime confirmation_token:string
invoke active_record
183
9.4. Um pouquinho de callbacks para realizar tarefas pontuais
Casa do Código
create db/migrate/
20120701050227_add_confirmation_fields_to_users.rb
Uma coisa interessante é que, quando vamos adicionar campos, se terminarmos
o nome da migração com o padrão add_*_to_* e em seguida colocar os campos que
quisermos, o Rails automaticamente criará o conteúdo da migração:
class AddConfirmationFieldsToUsers < ActiveRecord::Migration
def change
add_column :users, :confirmed_at, :datetime
add_column :users, :confirmation_token, :string
end
end
Execute as migrações:
$ rake db:migrate
== AddConfirmationFieldsToUsers: migrating ============
-- add_column(:users, :confirmed_at, :datetime)
-> 0.0014s
-- add_column(:users, :confirmation_token, :string)
-> 0.0004s
== AddConfirmationFieldsToUsers: migrated (0.0020s) ===
9.4
Um pouquinho de callbacks para realizar tare-
fas pontuais
Agora vamos gerar o token. Para isso, vamos usar o mecanismo de callbacks do
ActiveRecord. O ActiveRecord possui uma série de eventos durante o processo
de salvar um objeto. Por exemplo, ao criar um objeto, a ordem de chamada é a se-
guinte:
1) before_validation - Antes da validação
2) validate - Executa as validações no modelo
3) after_validation - Após todas as validações
4) before_save - Antes de salvar
5) before_create - Antes de criar
184
Casa do Código
Capítulo 9. O cadastro do usuário e a confirmação da identidade
6) create - Executa a criação do modelo
7) after_create - Depois de criar
8) after_save - Depois de salvar
9) after_commit - Depois de finalizar a transação no banco de dados
O mesmo acontece para atualização de um modelo, a diferença é que, ao invés de
usar a palavra-chave create, usamos update. Em cada um desses eventos é possível
plugar código para que possamos executar alguma operação de nosso interesse.
Vamos usar esse mecanismo e plugar um código no evento before_create, ou
seja, antes da criação do modelo no banco, para preencher o token automaticamente.
Cuidado com os callbacks
É muito conveniente usar esses callbacks, mas é muito importante ter
cautela por inúmeras razões. A primeira e mais óbvia é que, se você fizer
muitas operações, salvar um modelo torna-se algo imprevisível e demo-
rado. A segunda é que você pode acabar gerando loops infinitos e situa-
ções complicadas de depurar. Tome bastante cuidado e faça coisas muito
simples nesses callbacks. Se precisar de lógicas complexas, crie métodos
e chame-os quando adequado.
No modelo User (app/models/user.rb), vamos adicionar o código responsável
para gerar o token:
class User < ActiveRecord::Base
# Omitindo conteúdo anterior...
has_secure_password
before_create :generate_token
def generate_token
self.confirmation_token = SecureRandom.urlsafe_base64
end
end
185
9.4. Um pouquinho de callbacks para realizar tarefas pontuais
Casa do Código
O before_create aceita símbolo com o nome do método a ser chamado
ou um lambda. Criamos o método generate_token e nele usamos a biblioteca
SecureRandom, que gera números aleatórios o suficiente para nosso uso. Vamos usar
também uma forma que seja possível ser passado via URLs e o SafeRandom já possui
um método para isso.
Para testar, crie um novo usuário via o browser e depois vá ao console do Rails:
User.last.confirmation_token
# => "o8iIOCdUzIXjivrLsfUA8g"
Não
adicione
o
confirmation_token
ao
attr_accessible
Este é um campo que, se permitirmos ser atualizado via formulário,
mesmo que de forma indireta, podemos prejudicar a segurança da apli-
cação, portanto não permita que o campo confirmation_token seja pas-
sível de mass assignment.
Vamos também criar o método #confirm!, que marca a data e a hora da confir-
mação e limpa o token do usuário, de forma que o link de confirmação só funcione
uma única vez:
class User < ActiveRecord::Base
# ...
def generate_token
self.confirmation_token = SecureRandom.urlsafe_base64
end
def confirm!
return if confirmed?
self.confirmed_at = Time.current
self.confirmation_token = ''
save!
end
def confirmed?
186
Casa do Código
Capítulo 9. O cadastro do usuário e a confirmação da identidade
confirmed_at.present?
end
end
O método confirm! marca o campo confirmed_at com a hora corrente, limpa
o token e salva o modelo. Experimente no console:
user = User.last
# => #<User id: 1, full_name: "Vinicius Baggio Fuentes", ...>
user.confirm!
# => true
user.confirmed?
# => true
user.confirmation_token
# => ""
Com este código, estamos quase prontos! Agora temos que criar a rota, o con-
trole e corrigir o link no mailer.
9.5
Roteamento com restrições
Vamos adicionar uma nova rota no arquivo config/routes.rb:
Colchonet::Application.routes.draw do
resources :rooms
resources :users
resource :confirmation, :only => [:show]
root :to => "home#index"
end
Note que não estou usando resources, mas sim resource, no singular. O motivo
é que, essencialmente para quem está navegando (ou o cliente de uma API), não
existe mais de uma confirmação (ou seja, não faz sentido existir uma ação index) no
sistema. Isso é chamado de recurso singleton. Em seguida, passamos uma opção, que
é :only => [:show]. Isso significa que só queremos que a ação show seja criada,
que é a ação que mais se aproxima do que queremos fazer.
187
9.6. Métodos espertos e os finders dinâmicos
Casa do Código
Para entender o que essa rota gera, podemos executar o comando rake routes
na raiz do projeto:
$ rake routes
...
confirmation GET /confirmation(.:format) confirmations#show
Vamos
criar
o
controle
ConfirmationsController.
Note
que
ainda temos que usar o plural no nome.
Então criemos o arquivo
app/controllers/confirmations_controller.rb, com o seguinte conteúdo:
class ConfirmationsController < ApplicationController
def show
user = User.find_by_confirmation_token(params[:token])
if user.present?
user.confirm!
redirect_to user,
:notice => I18n.t('users.confirmations.success')
else
redirect_to root_path
end
end
end
9.6
Métodos espertos e os finders dinâmicos
O código desse controle não é complicado e nada de diferente, exceto pela terceira
linha:
user = User.find_by_confirmation_token(params[:token])
Nessa linha estamos usando o que muitas pessoas consideram uma das partes
mágicas do Rails, os dynamic finders, ou “buscadores” dinâmicos. Para cada atributo
do seu modelo, o ActiveRecord pode gerar um método que faz uma busca por aquele
atributo, sem a necessidade de declararmos esses métodos em lugar algum. Nesse
caso, estamos buscando pelo primeiro modelo cujo confirmation_token vêm do
parâmetro token.
Lembrando de adicionar a chave user.confirmations.success no YAML de
traduções (config/locales/pt-BR.yml):
188
Casa do Código
Capítulo 9. O cadastro do usuário e a confirmação da identidade
pt-BR:
users:
confirmations:
success: Email confirmado com sucesso, obrigado!
# ...
E
finalmente,
vamos
corrigir
o
link
no
mailer
do
cadastro
(app/mailers/signup_mailer.rb):
class SignupMailer < ActionMailer::Base
default :from => '[email protected]'
def confirm_email(user)
@user = user
@confirmation_link = confirmation_url({
:token => @user.confirmation_token
})
mail({
:to => user.email,
:bcc => ['sign ups <[email protected]>'],
:subject => I18n.t('signup_mailer.confirm_email.subject')
})
end
end
Pronto, terminamos!
Ao fazer um cadastro, recebemos um e-mail no
mailcatcher:
189

Casa do Código
Capítulo 9. O cadastro do usuário e a confirmação da identidade
Pronto, o cadastro está funcionando por completo! Cansou? Vai tomar um café
e dar uma volta no quarteirão, que em seguida vamos fazer o login.
9.7
Em resumo
Passamos os últimos capítulos exclusivamente desenvolvendo a funcionalidade de
cadastro. Veja o que você aprendeu:
• Comandos - Comandos básicos do Rails e do rake, como geradores, visualizar
rotas, executar servidor e console;
• Migrações - Aprendeu como gerar migrações que criam tabelas, adicionam
índices e colunas;
• Modelos - Como criar modelos, incluindo validações e callbacks;
• Segurança - Como evitar injeção de código malicioso via helpers, como evitar
ataques via mass assignment, como encriptar senha dos usuários e o que é Cross
Site Request Forgery;
• Emails - Envio de emails usando multipart, enviando HTML e texto puro ao
mesmo tempo;
• Rotas - Como construir rotas simples;
• Asset Pipeline - Aprendeu como um dos componentes mais complexos do
Rails funciona e como trabalhar com ele;
• Controle - Controle de fluxo de navegação e mensagens flash;
• I18n - Internacionalização das mensagens da aplicação;
• Templates e Helpers - Criação de templates, layouts, partials, view helpers e
deixar seus templates elegantes;
A lista é enorme, parabéns! Vamos agora à funcionalidade de login.
191
Capítulo 10
Login do usuário
Foco é uma questão de decidir as coisas que você não irá fazer.
– John Carmack, cofundador da idSoftware
Agora é possível com que o usuário se cadastre, confirmando sua conta através
do link enviado no email de boas vindas. Porém, ainda não temos nenhum controle
de permissões, portanto qualquer visitante pode mudar o perfil de outros usuários,
o que não é algo bom!
Vamos então fazer o recurso “sessão”, que será criado toda vez que um usuá-
rio realizar o login e dura o tempo de navegação do usuário ou quando ele fizer o
“logout” (ou seja, destruir o recurso sessão). Criaremos os templates e o controle
necessário para responder a esse fluxo e vamos usar a autenticação que o método
has_secure_password nos dá.
Depois, vamos modificar o modelo usuário, para que ele considere a confirmação
de conta.
Por fim, vamos modificar o cadastro de quartos para que ele esteja associado ao
dono e criar as permissões, de forma que apenas o dono do quarto possa remover
10.1. Trabalhe com a sessão
Casa do Código
ou atualizar um quarto.
10.1
Trabalhe com a sessão
A partir do momento em que um usuário entra na aplicação através do login e manda
novas requisições, precisamos saber quem é o usuário sem perguntá-lo a cada ins-
tante. Dessa maneira, precisamos guardar essa informação do usuário logado em
algum lugar, que é o que chamamos de sessão.
A sessão, apesar de não ser mapeada a uma tabela no banco de dados, pode ser
criada ou destruída. Uma vez criada, vamos colocar a informação de login de usuário
em seus cookies, então, em cada requisição, podemos saber a que usuário pertence.
Quando destruirmos a sessão, o que fazemos é remover a informação de login dos
cookies e as requisições não mais serão tratadas como ações de um usuário específico,
o que geralmente é feito quando o usuário realiza seu logout.
Vamos começar criando o modelo UserSession. Esse modelo vai possuir as
seguintes responsabilidades:
• Tradução de atributos (via I18n);
• Validações em formulário;
• Verificar as credenciais do usuário (email e senha);
• Gravar a sessão do usuário nos cookies.
Para tradução de atributos e validações de usuários, vamos usar um componente
do Rails chamado ActiveModel. Ele é responsável por toda a lógica de callbacks,
validações e traduções do ActiveRecord. Não precisaremos gravar nada em banco
de dados, mas ainda assim, podemos ter as vantagens de um modelo que se integra
bem com o Rails. O ActiveModel possui uma lista grande de componentes úteis,
alguns exemplos interessantes são:
• ActiveModel::Callbacks - Callbacks no ciclo de vida, na forma que vimos
com o ActiveRecord;
• ActiveModel::Conversion - Usado para detectar templates e rotas;
• ActiveModel::Dirty - Suporte a atributos “sujos”, ou seja, detectar quando
um atributo foi alterado e guardar o valor antigo;
194
Casa do Código
Capítulo 10. Login do usuário
• ActiveModel::Naming - Também usado para detectar templates e rotas a par-
tir do modelo (tal como fizemos com o form_for);
• ActiveModel::Translation - Usado para traduzir atributos com o I18n;
• ActiveModel::Validations - Validação de atributos (unicidade, presença,
etc.).
Alguns componentes são mais focados na integração com o Rails (como o
ActiveModel::AttributeMethods), útil para desenvolvedores que estão fazendo bi-
bliotecas e querem maior integração com o Rails (como por exemplo, suporte ao
MongoDB (http://mongodb.org), uma forma diferente de armazenar dados). Na
documentação de cada componente existe como você deve usá-lo e os benefícios.
Veja o exemplo do componente ActiveModel::Observer: http://api.rubyonrails.
org/classes/ActiveModel/Observer.html
Os componentes que nos interessam são os ActiveModel::Naming, para
podermos usar os formulários e rotas com o objeto de maneira simples,
ActiveModel::Translation para tradução dos atributos (email e senha) e final-
mente o ActiveModel::Validations, para mostrar erros de email e senha em
branco.
Crie então o arquivo app/models/user_session.rb:
class UserSession
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
extend ActiveModel::Translation
attr_accessor :email, :password
validates_presence_of :email, :password
def persisted?
false
end
end
O ActiveModel::Conversion exige que implementemos um método chamado
#persisted?, que é usado para saber se o modelo tem uma chave primária (para
195
10.2. Controles e rotas para o novo recurso
Casa do Código
compor rotas como /users/1, por exemplo). Como não vamos gravar no banco de
dados, sempre retornamos false.
Vamos testar essa nova classe. No console do Rails (ou fazer reload caso já esteja
com o console aberto):
session = UserSession.new
# => #<UserSession:0x007fee1d3b72a8>
session.valid?
# => false
session.errors.full_messages
# => ["Email não pode ficar em branco",
"Password não pode ficar em branco"]
Muito útil, não? Com poucas linhas de código temos um comportamento bas-
tante complexo, graças a modularidade do Rails. Ainda há muito o que se trabalhar
nessa classe, porém primeiro vamos amarrar todas as camadas juntas, de forma que
possamos testar a parte de autenticação.
O que vem por aí no Rails 4: melhorias no ActiveModel
O ActiveModel é relativamente novo, e portanto, mesmo que seja bas-
tante comum criar modelos como o UserSession para diversos fins,
ainda não é um processo tão simples e elegante como poderia ser. Sa-
bendo disso, os desenvolvedores do Rails já facilitaram esse processo para
nós desenvolvedores. No Rails 4, ainda não lançado, vamos poder subs-
tituir tudo isso para apenas include ActiveModel::Model. Enquanto a
nova versão não for lançada, porém, é necessário fazer dessa maneira.
10.2
Controles e rotas para o novo recurso
Vamos então criar as rotas do recurso, que haverão apenas as ações create, new e
destroy. Para isso, basta criar mais uma entrada no arquivo config/routes.rb:
Colchonet::Application.routes.draw do
scope "(:locale)", :locale => /en|pt\-BR/ do
resources :rooms
196
Casa do Código
Capítulo 10. Login do usuário
resources :users
resource :confirmation, :only => [:show]
resource :user_sessions, :only => [:create, :new, :destroy]
end
root :to => "home#index"
end
Em
seguida,
criamos
o
controle
UserSessionsController
(app/controllers/user_sessions_controller.rb).
class UserSessionsController < ApplicationController
def new
@session = UserSession.new
end
def create
# Ainda não :-)
end
def destroy
# Ainda não :-)
end
end
Vamos
criar
o
template
para
a
ação
new.
Para
isso,
pre-
cisamos
criar
a
pasta
app/views/user_sessions
e
o
template
app/views/user_sessions/new.html.erb:
<h1><%= t '.title' %> </h1>
<% if @session.errors.has_key? :base %>
<div id="error_explanation">
<%= @session.errors[:base].join(', ') %>
</div>
<% end %>
<%= form_for @session do |f| %>
<p>
<%= f.label :email %>
197

Casa do Código
Capítulo 10. Login do usuário
location: "Localização: %{location}"
bio: "Bio: %{bio}"
# Nova parte:
user_sessions:
new:
title: 'Login'
sign_up: 'Cadastre-se'
signup_mailer:
confirm_email:
#...
No fim do arquivo, vamos criar mais duas “seções”, contendo as traduções do
ActiveModel e a tradução específica do botão de login:
room:
description: Descrição
location: Localização
title: Título
# Adicione:
activemodel:
attributes:
user_session:
email: Email
password: Senha
errors:
messages:
invalid_login: 'Usuário ou senha inválidos'
helpers:
submit:
user_session:
create: 'Entrar'
Note a porção final do I18n, um trecho especial para os formulários. Você
pode criar uma tradução específica para cada modelo como fizemos, bas-
tando criar as chaves necessárias.
O padrão que abrange todos os modelos
ActiveRecord é o seguinte, extraído da tradução padrão do Rails que baixamos
(config/locales/rails.pt-BR.yml):
199
10.3. Sessões e cookies
Casa do Código
# Não coloque no seu pt-BR.yml,
# você já tem isso no rails.pt-BR.yml
pt-BR:
# ...
helpers:
submit:
create: Criar %{model}
submit: Salvar %{model}
update: Atualizar %{model}
É interessante dar uma olhada no I18n padrão do Rails caso você tenha interesse
em customizar uma mensagem para um modelo específico, para não ficar mensagens
muitos genéricas e sem personalidade. Brincando com o nome do modelo como
escopo de chaves, você pode personalizar a mensagem sem ter que deixar o template
poluído.
Agora vamos para a estrela desse capítulo, criando a sessão do usuário!
10.3
Sessões e cookies
Vamos nos lembrar de uma lição muito importante: não é interessante colocar a ló-
gica de negócio nos controles. O que seria lógica de negócio? Nesse caso, é tudo a ver
com o que não é roteamento, mensagem de erro ou decidir a melhor representação
para o recurso.
Dessa forma, o que vamos fazer no controle é apenas tentar criar a sessão via
o modelo UserSession. Se algum erro acontecer, simplesmente vamos mostrar ao
usuário o template com os erros, e se ocorrer sucesso, direcionaremos o usuário para
a página principal com uma bela mensagem de sucesso.
Porém, essa ação não será exatamente igual às ações que relacionam-se com o
ActiveRecord. O controle é a entidade que possui o controle de cookies, e portanto
teremos que passar ao modelo em qual objeto iremos gravar a sessão do usuário (mas
não como e quando, isso é muito importante!).
Nos controles do Rails, temos duas maneiras de acessar os cookies dos usuários,
via o método session e via o método cookies. Ambos os métodos se comportam
muito parecido com os já famosos params e flash - na forma de hashes.
Os cookies são simples formas de guardar dados entre requisições HTTP que são
passados entre o usuário e o servidor em cada requisição, dando uma sensação de
estado. Por exemplo, se tivermos um controle cuja ação execute o código seguinte,
200

Casa do Código
Capítulo 10. Login do usuário
o Rails irá criar uma resposta com o cookie informado que, por padrão, irá durar
apenas a sessão do usuário, ou seja, ao fechar o browser, o cookie será removido pelo
browser.
def create
cookies[:pergunta] = 'Biscoito ou bolacha?'
end
O cabeçalho da resposta HTTP será algo parecido com o seguinte, note, na pe-
núltima linha, o comando para guardar o cookie com o que configuramos:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
X-Ua-Compatible: IE=Edge
Etag: "91c95050746190ff27eb34a00497d91b"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 6194eccc0a2d00f4f472d0a79f286dd4
X-Runtime: 0.020849
Content-Length: 2155
Server: WEBrick/1.3.1 (Ruby/1.9.3/2012-04-20)
Date: Tue, 10 Jul 2012 05:57:59 GMT
Connection: Keep-Alive
Set-Cookie: pergunta=Biscoito+ou+bolacha%3F; path=/
Set-Cookie: _colchonet_session=BAh7.....8857; path=/; HttpOnly
Por fim, podemos ver no console do browser (Google Chrome, nesse caso) o
resultado do cookie guardado:
Figura 10.2: Cookie com a chave ’pergunta’
Note que também temos outra chave de cookie, a _colchonet_session. Essa é
gerenciada automaticamente pelo Rails para controlar o CSRF (Cross-Site Request
Forgery, visto na seção 6.1).
201
10.3. Sessões e cookies
Casa do Código
O que queremos, no final das contas é armazenar o ID do usuário autenticado
no cookie. Portanto, se o email e a senha estiverem ok e a conta estiver confirmada,
salvamos o ID do usuário no cookie e resgatamos o objeto User em cada requisição.
Porém, não é inteligente da nossa parte simplesmente gravar o ID do usuá-
rio no cookie e sentar no sofá esperando a bonança.
Isso porquê, ao fazer
cookies[:user_id] = 1, um usuário mal intencionado pode muito bem pegar o
cookie local e colocar um outro ID e começar a agir como um outro usuário. Esse
ataque é conhecido como Session Hijacking/Theft ou sequestro ou roubo de sessão.
O Rails já sabe disso e sabe a melhor maneira de prever ataques como esse. E é
justamente essa a grande diferença entre o cookie e o session - o session é encrip-
tado usando uma chave “mágica”, de forma que o usuário nunca saberá o conteúdo
do cookie e, portanto, dificilmente conseguirá forjar uma sessão.
O Rails, para fazer essa encriptação (lembre-se que, diferente das senhas que são
apenas uma via, a sessão precisa ser lida no servidor), usa uma chave privada. Essa
chave privada fica em config/initializers/secret_token.rb:
# String loooooooonga....
Colchonet::Application.config.secret_token = 'a49......'
É muito importante que você nunca divulgue de forma alguma o secret_token
de sua aplicação. Deixar em controle de versão não tem problema, mas não deixe
isso disponível para seus usuários, pois eles podem ter suas contas roubadas!
Se por algum motivo você suspeitar que o secret_token deixou de ser segredo
ou que você tem usuários com sessões roubadas, você pode substituir o código atual
gerando um novo via:
$ rake secret
513b43f26ce....
Isso tornarão todas as sessões do site inválidas e os usuários terão que fazer um
novo login, mas é melhor garantir a segurança das informações.
202
Casa do Código
Capítulo 10. Login do usuário
Sessões e serialização
É importante lembrar que cookies, apesar de não houver limite em sua
especificação (RFC 2965), cookies possuem uma limitação prática de 4kB,
portanto tome cuidado com o que você vai gravar.
Outro problema que pode pegar você de surpresa é a serialização. Sem-
pre guarde IDs para que objetos sempre estejam em sua versão mais atual
quando usados. Há objetos também que não podem ser serializados (por
exemplo, possuem referências a IO/arquivos) e podem causar exceções
na hora de retornar a resposta ao cliente.
Agora que você já sabe tudo de cookies e sessões, vamos à nossa implementação
das sessões de usuário. Para relembrar, devemos usar o session para gravar o ID do
usuário caso ele seja autenticado com sucesso. Dessa forma, vamos deixar o controle
UserSessionsController (app/controllers/user_sessions_controller.rb) da
seguinte forma:
class UserSessionsController < ApplicationController
def new
@session = UserSession.new(session)
end
def create
@session = UserSession.new(session, params[:user_session])
if @session.authenticate
# Não esqueça de adicionar a chave no i18n!
redirect_to root_path, :notice => t('flash.notice.signed_in')
else
render :new
end
end
def destroy
end
end
Nesse código, atualizamos a ação new para incluir a sessão no UserSession. Na
ação create, não fazemos nada de diferente nos controles que fizemos até agora, a
203
10.3. Sessões e cookies
Casa do Código
não ser chamar o método authenticate, ao invés do tradicional save. Vamos agora
à parte mais interessante dessa lógica, o modelo UserSession.
Primeiro, precisamos criar o método construtor para aceitar os parâmetros do
formulário e a sessão que vem do controle:
def initialize(session, attributes={})
@session = session
@email = attributes[:email]
@password = attributes[:password]
end
O método authenticate é o método que verifica os dados entrados pelo usuário.
Se tudo estiver correto, guarda a sessão do usuário, caso contrário, adiciona um erro
a ser exibido no formulário.
def authenticate
user = User.authenticate(@email, @password)
if user.present?
store(user)
else
errors.add(:base, :invalid_login)
false
end
end
Veja que nesse caso estamos delegando a lógica de verificar se o usuário tem o
email e senha válido no modelo User. Ou seja, é o modelo usuário que deve saber
como a autenticação deve ser feita. É importante centralizar esse tipo de lógica para
que, se um dia mudarmos a lógica de como o login é feito, basta alterar em apenas
um lugar e isso se reflete em todo lugar que usado. Em poucos parágrafos entraremos
em detalhe na implementação desse método.
Usando a API de erros do ActiveModel é fácil criar um erro customizado. Nessa
situação, o erro não é no atributo email e nem no atributo password. Por essa razão,
adicionamos o erro no base, que é o objeto como um todo. Como vimos no template,
esse erro irá ser exibido em um lugar diferenciado.
Por fim, temos o método store, que grava o usuário na sessão:
def store(user)
@session[:user_id] = user.id
end
204
Casa do Código
Capítulo 10. Login do usuário
Por fim, o resultado do modelo UserSession (app/models/user_session.rb)
é:
class UserSession
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Translation
extend ActiveModel::Naming
attr_accessor :email, :password
validates_presence_of :email, :password
def initialize(session, attributes={})
@session = session
@email = attributes[:email]
@password = attributes[:password]
end
def authenticate
user = User.authenticate(@email, @password)
if user.present?
store(user)
else
errors.add(:base, :invalid_login)
false
end
end
def store(user)
@session[:user_id] = user.id
end
def persisted?
false
end
end
Vamos agora ao modelo usuário.
205
10.4. Consultas no banco de dados
Casa do Código
10.4
Consultas no banco de dados
Você se lembra que o modelo usuário (classe User) possui um mecanismo de con-
firmação de contas, não é mesmo? Então, a primeira regra que temos que verificar
para saber se um usuário é válido é verificar se o confirmed_at está preenchido.
Para fazer buscas dessa forma, vamos entender como funciona o rebuscado me-
canismo de busca do ActiveRecord, que facilita e muito a nossa vida, sem ter que
se perder em um oceano de comandos SQL. É importante entender, porém, que o
ActiveRecord faz o possível, mas não é milagroso, e portanto algumas vezes é ne-
cessário escrever SQL e entender a consulta SQL gerada.
Para fazer buscas diretas no banco de dados, o ActiveRecord possui uma lista de
métodos que lembra muito as cláusulas SQL. Por exemplo, já vimos anteriormente
que, para limitar o número de objetos em uma consulta, basta usar o método .limit.
Para fazer condições de busca, usamos o método .where, e assim por diante. Veja a
lista de métodos a seguir:
• where - Mapeia cláusulas WHERE;
• select - Especifica o que será retornado no SELECT, ao invés de *;
• group - Mapeia cláusulas GROUP BY;
• order - Mapeia cláusulas ORDER BY;
• reorder - Sobrescreve cláusulas de ordem de default_scope (veremos o que
é default_scope ainda nesse capítulo);
• reverse_order - Inverte a ordem especificada (crescente ou decrescente);
• limit - Mapeia cláusula LIMIT;
• offset - Mapeia cláusula OFFSET;
• joins - Usado para inner joins ou quaisquer outer joins;
• includes - Faz joins automaticamente com modelos relacionados, veremos
relacionamentos no capítulo 11;
• lock - Trava atualizações de objetos para atualização;
• readonly - Torna os objetos retornados marcados como apenas leitura;
206
Casa do Código
Capítulo 10. Login do usuário
• from - Mapeia cláusula FROM;
• having - Mapeia cláusula HAVING%;
Escopos
User.limit 1
# User Load (0.1ms) SELECT "users".* FROM "users" LIMIT 1
# => [#<User id: 11, full_name: "Vinicius Baggio Fuentes", ... >]
User.where :email => '[email protected]'
# User Load (0.1ms) SELECT "users".* FROM "users"
#
WHERE "users"."email" = '[email protected]'
# => [#<User id: 11, full_name: "Vinicius Baggio Fuentes", ... >]
Dica: Comandos SQL no console
Para facilitar o seu aprendizado e também tirar muitas dúvidas, é possível
fazer com que o ActiveRecord imprima o comando SQL gerado no IRB.
Para fazer isso, crie o arquivo .irbrc na sua pasta home com o seguinte
conteúdo:
if ENV.include?('RAILS_ENV')
require 'logger'
Rails.logger = Logger.new(STDOUT)
end
Lembre-se de reiniciar o console do Rails, reload! não é suficiente.
Mas isso não é tudo. A parte mais interessante é a composição de consultas.
Quando chamamos algum dos métodos de consulta (.limit e .where, por exem-
plo), o método retorna um objeto especial chamado ActiveRecord::Relation. Esse
objeto pode, por sua vez, receber chamadas de métodos de consulta, que irá agregar
comandos que já foram chamados anteriormente. Veja o exemplo a seguir:
207
10.4. Consultas no banco de dados
Casa do Código
User.where(:email => '[email protected]').limit(2)
# User Load (0.3ms) SELECT "users".* FROM "users"
#
WHERE "users"."email" = '[email protected]'
#
LIMIT 2
# => [...]
most_recent = User.order('created_at DESC')
most_recent.limit(1)
# SELECT "users".* FROM "users" ORDER BY created_at DESC LIMIT 1
# Note que a chamada ao .limit anterior não altera
# o objeto most_recent
most_recent.where(:email => '[email protected]')
# SELECT "users".* FROM "users"
# WHERE "users"."email" = '[email protected]'
# ORDER BY created_at DESC
Essa composição de métodos é o que chamamos de escopo. Por exemplo, a va-
riável most_recent que criamos é um escopo em User que sempre irá retornar os
usuários em ordem decrescente pela data de cadastro (created_at).
Às vezes é bom entender como o query planner (componente que planeja como
será a melhor forma de executar a consulta SQL) do banco de dados vai executar
uma consulta para que possamos fazer otimizações. Em banco de dados tradici-
onais como MySQL e PostgreSQL, basta executar o EXPLAIN na consulta. Com o
ActiveRecord, conseguimos fazer isso chamando o método .explain:
User.where(:email => '[email protected]').
order('created_at DESC').
explain
# SELECT "users".* FROM "users"
#
WHERE "users"."email" = '[email protected]'
#
ORDER BY created_at DESC
# EXPLAIN QUERY PLAN SELECT "users".* FROM "users"
#
WHERE "users"."email" = '[email protected]'
#
ORDER BY created_at DESC
# => "EXPLAIN for: SELECT \"users\".* FROM \"users\"
#
WHERE \"users\".\"email\" = '[email protected]'
#
ORDER BY created_at DESC
208
Casa do Código
Capítulo 10. Login do usuário
#
0|0|0|SEARCH TABLE users
#
USING INDEX index_users_on_email (email=?) (~10 rows)
#
0|0|0|USE TEMP B-TREE FOR ORDER BY"
O ActiveRecord também permite criar os famosos named scopes, ou escopos
nomeados, de modo que você chame .most_recent, ao invés de .order('created
DESC'), tornando as chamadas de métodos bastante legíveis. Imagine que a classe
User tenha o seguinte código:
class User < ActiveRecord::Base
scope :most_recent, order('created_at DESC')
scope :from_sampa, where(:location => 'São Paulo')
# ...
end
Encoding
Se você tentar executar esse exemplo e esbarrar com o seguinte problema:
SyntaxError: user.rb:3: invalid multibyte char (US-ASCII)
user.rb:3: invalid multibyte char (US-ASCII)
/user.rb:3: syntax error, unexpected $end, expecting ')'
scope :from_sampa, where(:location => 'São Paulo')
Isso deve-se ao fato de que o Ruby não reconheceu esse caractere no
código-fonte. Para que o caractere seja reconhecido, coloque o seguinte
conteúdo na primeira linha do arquivo:
# encoding: utf-8
Dessa forma, podemos chamar os named scopes da mesma maneira que chama-
mos os outros métodos de busca:
User.most_recent.limit(5)
# SELECT "users".* FROM "users"
209
10.4. Consultas no banco de dados
Casa do Código
#
ORDER BY created_at DESC
#
LIMIT 5
User.most_recent.from_sampa
# SELECT "users".* FROM "users"
#
WHERE "users"."location" = 'São Paulo'
#
ORDER BY created_at DESC
Ainda é possível criar named_scopes com parâmetros. Para isso, você precisa
criar um lambda:
class User < ActiveRecord::Base
scope :most_recent, order('created_at DESC')
scope :from_sampa, where(:location => 'São Paulo')
scope :from, ->(location) { where(:location => location) }
# ...
end
User.from('San Francisco, CA').most_recent
# SELECT "users".* FROM "users"
#
WHERE "users"."location" = 'San Francisco, CA'
#
ORDER BY created_at DESC
# Se condições se repetirem, apenas a última será mantida:
User.from('San Francisco, CA').from_sampa
# SELECT "users".* FROM "users"
#
WHERE "users"."location" = 'São Paulo'
Por fim, ainda é possível criar um escopo padrão, ou seja, um escopo que será
aplicado mesmo se nenhum for definido, via o default_scope. Esse escopo não é
sobrescrito e se você definir outros default_scope, eles são acumulados.
class User < ActiveRecord::Base
default_scope where('confirmed_at IS NOT NULL')
# ...
end
User.all
# SELECT "users".* FROM "users" WHERE (confirmed_at IS NOT NULL)
210
Casa do Código
Capítulo 10. Login do usuário
User.where(:location => 'San Francisco, CA')
# SELECT "users".* FROM "users"
#
WHERE "users"."location" = 'San Francisco, CA'
#
AND (confirmed_at IS NOT NULL)
Cuidados com o default_scope
O default_scope tem utilidades muito interessantes, como implemen-
tar soft delete, ou seja, destruir objetos nada mais é do que marcá-lo como
destruído e não ser incluso nas buscas. Portanto, um default_scope cuja
busca filtre esses objetos é uma ótima ideia.
Porém, tome cuidado. Uma das boas práticas da programação sugere
que você deve evitar surpresas desagradáveis, e fazer filtros complexos
no default_scope é uma surpresa bastante infeliz. A razão disso é que
o comportamento torna-se implícito e dificilmente o programador que
estiver lendo o código (inclusive você mesmo) lembrará ou saberá da
existência de um default_scope. Se necessário, peque pelo excesso de
clareza ao declarar um escopo nomeado e não o contrário.
Antes de voltarmos ao colcho.net, vamos entrar em um pouco mais de detalhes
no where, pois ele tem algumas funcionalidades imprescindíveis ao desenvolvedor
Rails.
Buscas tradicionais
As buscas mais simples são as buscas que já vimos, para buscar com valores exa-
tos. Para isso, basta passar um hash com a chave sendo a coluna e o valor sendo o
valor da busca:
User.where(:location => 'San Francisco, CA')
# SELECT "users".* FROM "users"
# WHERE "users"."location" = 'San Francisco, CA'
Buscas manuais
Infelizmente não é sempre que o ActiveRecord nos ajuda e precisamos fazer
uma busca manual. Para isso, basta colocarmos a string com o conteúdo da busca
211
10.4. Consultas no banco de dados
Casa do Código
diretamente. Vimos isso também recentemente:
User.where('confirmed_at IS NOT NULL')
# SELECT "users".* FROM "users" WHERE (confirmed_at IS NOT NULL)
É bastante importante você notar que, nesse caso, não usamos nenhuma entrada
do usuário. Dessa forma, não precisamos nos preocupar com SQL Injection (ou
injeção de SQL) e podemos fazer a busca de maneira mais direta, se necessário. Isso
é bastante útil para usarmos funcionalidades específicas do banco de dados.
O que é SQL injection?
SQL Injection, ou injeção de SQL é um ataque a um site de forma que
um usuário injete códigos SQL e consiga poder de administrador ou rou-
bar dados importantes. Parece um erro simples, mas ainda é um ataque
bastante utilizado e testado. Inclusive, a fatídica queda da Playstation
Network® em 2011, que afetou o sistema durante meses e milhões de usuá-
rios prejudicados, foi uma única injeção de SQL. Veja o exemplo a seguir:
email = '[email protected]'
User.where("email = '#{email}'")
# SELECT "users".* FROM "users"
#
WHERE (email = '[email protected]')
#
# => [#<User id: 11, full_name: "Vinicius ... >]
email = "' OR 1=1) --"
User.where("email = '#{email}'")
# SELECT "users".* FROM "users"
#
WHERE (email = '' OR 1=1) --')
#
# => [#<User id: 11, ... >]
Esse é um exemplo bastante simples mas prova o ponto que, se você não
tomar cuidado com os parâmetros, é possível que um usuário mal inten-
cionado ganhe acesso irrestrito ao seu site.
212
Casa do Código
Capítulo 10. Login do usuário
Buscas parametrizadas via Array
A forma mais simples de busca parametrizada é a forma clássica de consultas
que evitam SQL Injection. Para esse tipo de parametrização, usamos o símbolo ? na
consulta e o ActiveRecord irá sanitizar os parâmetros e substituí-los de acordo. Para
isso, passe uma Array cujo primeiro valor é a string SQL e o restante serão os valores
substituídos.
email = "[email protected]"
User.where(["email = ?", email])
# SELECT "users".* FROM "users"
#
WHERE (email = '[email protected]')
# => [#<User :id: 11, ...>]
email = "' OR 1=1) --"
User.where(["email = ?", email])
# SELECT "users".* FROM "users"
#
WHERE (email = ''' OR 1=1) --')
# => []
Buscas parametrizadas via hash
Se você tiver muitos parâmetros para substituir, usar o ? acaba ficando ruim pois
fica difícil ler a string SQL e entender o que é cada parâmetro. Por isso, é possível
usar chaves e símbolos para substituição de parâmetros:
User.where(["location LIKE :location AND email = :email", {
:location => '%CA%',
:email => '[email protected]'
}])
# SELECT "users".* FROM "users"
#
WHERE (
#
location LIKE '%CA%'
#
AND email = '[email protected]'
#
)
Buscas em listas e intervalos
Com o where ainda é possível fazer buscas em listas e intervalos via o comando
SQL IN ou BETWEEN. Para fazer esse tipo de busca, basta usar a busca tradicional,
usando hashes e um Array:
213
10.5. Escopo de usuário confirmado
Casa do Código
User.where(:id => [1, 10, 11, 20])
# SELECT "users".* FROM "users"
#
WHERE "users"."id" IN (1, 10, 11, 20)
User.where(:id => 1..10)
# SELECT "users".* FROM "users"
#
WHERE ("users"."id" BETWEEN 1 AND 10)
Essas buscas são extremamente úteis em datas, por exemplo:
User.where(:confirmed_at => 1.week.ago..Time.now)
# SELECT "users".* FROM "users"
#
WHERE (
#
"users"."confirmed_at"
#
BETWEEN '2012-07-05 07:37:39.902321'
#
AND '2012-07-12 07:37:39.991792'
#
)
10.5
Escopo de usuário confirmado
Agora que temos o conhecimento de como fazer buscas e escopos, temos que imple-
mentar um método chamado .authenticate no modelo usuário. Ele recebe dois
parâmetros, email e senha. Com eles, precisamos verificar, em todos os usuários vá-
lidos do sistema (ou seja, usuários que confirmaram seu email), qual possui o email
e a senha digitados.
Para isso, primeiro vamos criar um escopo nomeado que retorna todos os
usuários que confirmaram sua conta. Adicione o seguinte scope no modelo User
(app/models/user.rb):
class User
# ...
scope :confirmed, where('confirmed_at IS NOT NULL')
# ...
end
Em seguida, vamos criar o método .authenticate, que faz a verificação do
email e senha. Se o usuário existir e for válido, ele será retornado, caso contrário,
a busca retornará nil:
def self.authenticate(email, password)
confirmed.
214
Casa do Código
Capítulo 10. Login do usuário
find_by_email(email).
try(:authenticate, password)
end
Nesse trecho de código usamos um método do Ruby chamado #try. Ele é usado
quando não sabemos se um objeto é nil, podendo chamar métodos de maneira se-
gura. Dessa forma, se o objeto for nil, nada é executado e nil é retornado. Se o
objeto existir, o método cujo nome é o primeiro parâmetro é executado, com todos
os parâmetros em seguida repassados. Veja os exemplos a seguir:
string = "oba"
# => "oba"
string.try(:upcase) # => "OBA"
string = nil
# => nil
string.try(:upcase) # => nil
A grande utilidade do #try é evitar criar if e manter a legibilidade do código
simples. Portanto no método .authenticate, buscamos um único usuário cujo
email é o email do parâmetro e que sua conta foi confirmada. Se ele existir, tenta-
mos autenticar a senha. Se a senha for válida, retornamos o usuário, caso contrário,
temos nil.
Voltemos ao modelo UserSession (app/models/user_session.rb):
def authenticate
user = User.authenticate(@email, @password)
if user.present?
store(user)
else
errors.add(:base, :invalid_login)
false
end
end
Nesse método, que já fizemos, o usuário só será retornado caso o email e senha
sejam válidos e o usuário esteja confirmado. Essa parte é muito importante: a classe
UserSession não precisa saber que existe a lógica de confirmação de usuários, e
portanto, delegamos essa lógica apenas ao modelo que deve saber disso.
Com todos os objetos interligados, é possível testar o fluxo completo. Ao digitar
as credenciais corretamente, temos o seguinte resultado:
215


Capítulo 11
Controle de acesso
Formação em Ciência da Computação consegue tornar qualquer pessoa um excelente
programador tanto quanto estudar pincéis e pigmentos torna qualquer pessoa um
excelente pintor.
– Eric Raymond, autor de ‘A catedral e o bazar’
Cadastro e login prontos, agora precisamos focar no controle de acesso. Pri-
meiro, vamos alterar nosso template para exibir uma barra de navegação diferenci-
ada para quando o usuário estiver logado. Atualmente, a barra é assim:
Figura 11.1: Barra de navegação sem informações de sessao
Na versão em que não há usuário logado, deverá ficar assim:

Casa do Código
Capítulo 11. Controle de acesso
• require_no_authentication - Filtro para evitar que usuários cadastrados
tentem acessar páginas que só deverão ser acessadas quando o usuário não
tiver um login (como páginas de cadastro e de login).
Vamos lá! Para criar essa lógica, vamos ter que melhorar um pouco a classe que
nos dá a lógica de login, UserSession (app/models/user_session.rb):
class UserSession
#...
def current_user
User.find(@session[:user_id])
end
def user_signed_in?
@session[:user_id].present?
end
end
Pronto, com esses dois métodos no UserSession conseguimos fazer tudo o que
precisamos no ApplicationController:
class ApplicationController < ActionController::Base
delegate :current_user, :user_signed_in?, :to => :user_session
# ...
def user_session
UserSession.new(session)
end
end
Opa!
O que aconteceu?
Como o UserSession possui toda a lógica que
queremos, apenas delegamos as chamadas de método, usando o delegate do
ActiveSupport, ou seja, ao chamar o método #current_user no controle, a cha-
mada será repassada para o objeto resultante do método user_session. A princípio
parece ser muito trabalho e muita abstração desnecessária, mas é importante lembrar
das responsabilidades de cada componente e fazer tudo de acordo.
Agora, para os filtros:
class ApplicationController < ActionController::Base
# ...
219
11.1. Helpers de sessão
Casa do Código
def require_authentication
unless user_signed_in?
redirect_to new_user_sessions_path,
:alert => t('flash.alert.needs_login')
end
end
def require_no_authentication
redirect_to root_path if user_signed_in?
end
end
Este código não é complicado e é bem legível por conta própria. É importante
ressaltar um comportamento de filtros: se o filtro executar uma redireção ou al-
guma renderização de template (via redirect_to ou render), a ação e/ou filtros
seguintes não serão executados. Isso é bastante conveniente para a situação dos fil-
tros require_authentication e require_no_authentication, mas é importante
prestar atenção em filtros que você criar no futuro.
Isso é quase tudo que precisamos.
Os métodos current_user e
user_signed_in? podem ser usados em controles, mas é importante usá-los
também nos templates. Para disponibilizar um método do controle nos templates,
usamos a class macro helper_method. Basta fazer, logo após o delegate:
class ApplicationController < ActionController::Base
delegate :current_user, :user_signed_in?, :to => :user_session
helper_method :current_user, :user_signed_in?
# ...
end
Templates da barra
Agora que temos os métodos de controle de sessão do usuário, já é possí-
vel aprimorar nossa barra de navegação. Para isso, vamos ao layout da aplicação
(app/views/layouts/application.html.erb) e vamos extrair a barra de navega-
ção em uma partial:
...
<header>
220

Casa do Código
Capítulo 11. Controle de acesso
<div id="header-wrap">
<h1><%= link_to "colcho.net", root_path %> </h1>
<% if user_signed_in? %>
<%= render 'layouts/user_navbar' %>
<% else %>
<%= render 'layouts/visitor_navbar' %>
<% end %>
</div>
</header>
...
Agora vamos criar o template app/views/layouts/_visitor_navbar.html.erb.
Note que tivemos que especificar o caminho da partial. Isso deve-se ao fato de que,
como o layout é executado em todas as ações, o caminho de busca de templates fica
relativo àquela ação. Especificando o caminho, garantimos que, não importa a ação
que estivermos executando, a partial sempre será achada.
<nav>
<ul>
<li><%= link_to t('layout.rooms'), rooms_path %> </li>
<li><%= link_to t('layout.signup'), new_user_path %> </li>
<li><%= link_to t('layout.signin'), new_user_sessions_path %> </li>
</ul>
</nav>
O resultado é o seguinte:
Figura 11.4: Barra de navegação sem login
A barra de navegação para usuários logados fica da seguinte forma
(app/views/layouts/_user_navbar.html.erb):
<nav>
<ul>
<li><%= link_to t('layout.new_room'), new_room_path %> </li>
<li><%= link_to t('layout.rooms'), rooms_path %> </li>
<li>
221
11.1. Helpers de sessão
Casa do Código
<%= link_to t('layout.my_profile'),
user_path(current_user) %>
</li>
<li>
<%= link_to t('layout.signout'), user_sessions_path,
:method => :delete %>
</li>
</ul>
</nav>
Quando queremos fazer o logout, o que realmente precisamos é apagar a sessão
do usuário, via a ação destroy. De acordo com a nossa rota, para executar essa ação,
precisamos fazer DELETE /user_sessions. O problema é que essa ação HTTP não é
suportada por todos os browsers, portanto o Rails tem um mecanismo para executar
esse tipo de operação:
<a href="/pt-BR/user_sessions" data-method="delete"
rel="nofollow">
Logout
</a>
O código anterior possui um atributo data, válido no HTML 5. Ele é usado
para colocar, em templates HTML, dados que possam ser usados de outra forma. O
Rails então usa o data-method via Javascript. A forma que isso é feito é através da
criação de um formulário (usando POST), e incluindo um campo chamado _method.
Isso tudo é usado para simular o DELETE. Bastante trabalhoso para o Rails, para você
basta incluir o :method => :delete.
Por fim, adicionamos novas chaves na seção “layout” do arquivo de I18n
(config/locales/pt-BR.yml):
layout:
rooms: Quartos
new_room: Cadastre seu quarto!
signup: Cadastro
signin: Login
my_profile: Meu perfil
signout: Logout
O resultado é:
222

Casa do Código
Capítulo 11. Controle de acesso
Figura 11.5: Barra de navegação com perfil do usuário
Todos os links estarão funcionais e traduzidos, porém ainda não implementamos
a ação de destruir a sessão do usuário, e é isso que vamos fazer agora.
Logout
Vamos primeiro à classe UserSession (app/models/user_session.rb). Para
removermos um item da sessão, basta associar nil ao item:
class UserSession
# ...
def destroy
@session[:user_id] = nil
end
end
Isso é suficiente para que todo o login seja desfeito.
Agora pre-
cisamos
chamar
esse
método
no
controle
UserSessionsController
(app/controllers/user_sessions_controller.rb):
class UserSessionsController < ApplicationController
# ...
def destroy
user_session.destroy
redirect_to root_path, :notice => t('flash.notice.signed_out')
end
end
O método user_session foi criado no ApplicationController e vamos usá-lo
para tornar o uso desse objeto mais fácil. Basta agora incluir essa mensagem no I18n
(config/locales/pt-BR.yml) e acabamos a barra!
flash:
notice:
signed_in: 'Login efetuado com sucesso!'
223

Casa do Código
Capítulo 11. Controle de acesso
end
end
Nesse código, criamos um filtro chamado can_change que se aplica apenas às
ações edit e update. No método can_change, redirecionamos o usuário à página
do perfil que tentamos atualizar a não ser que o usuário esteja logado e que o perfil
a ser atualizado é o dele mesmo.
Usamos também nesse filtro uma expressão idiomática de Ruby chamado me-
moization (ou “memorização”). Nessa expressão, apenas associamos a variável (e
por consequência, fazemos a busca no banco de dados) caso ela nunca tenha sido
iniciada. Você pode pensar nisso como uma espécia de cache de variável.
Aproveitando que estamos no controle de usuários, vamos forçar que, para a
página de cadastro, é necessário não estar logado. Para isso, basta usar o filtro que
criamos, require_no_authentication:
class UsersController < ApplicationController
before_filter :require_no_authentication, :only => [:new, :create]
before_filter :can_change, :only => [:edit, :update]
#...
end
Por fim, vamos adicionar o mesmo filtro na ação de login (tanto para o
formulário quanto para a ação create), no controle UserSessionsController
(app/controllers/user_sessions_controller.rb). Vamos aproveitar e filtrar a
ação destroy para apenas usuários logados (com o único intuito de evitar ver o
flash mesmo não tendo feito ação alguma):
class UserSessionsController < ApplicationController
before_filter :require_no_authentication, :only => [:new, :create]
before_filter :require_authentication, :only => :destroy
# ...
end
Por fim, vamos criar esses filtros para o cadastro e atualização de quartos
(aproveitamos também para fazer uma limpeza, tal como colocar I18n, remover
os comentários e remover o tratamento para respostas em JSON), no controle
RoomsController (app/controllers/rooms_controller.rb):
225
11.2. Não permita edição do perfil alheio
Casa do Código
class RoomsController < ApplicationController
before_filter :require_authentication,
:only => [:new, :edit, :create, :update, :destroy]
def index
@rooms = Room.all
end
def show
@room = Room.find(params[:id])
end
def new
@room = Room.new
end
def edit
@room = Room.find(params[:id])
end
def create
@room = Room.new(params[:room])
if @room.save
redirect_to @room, :notice => t('flash.notice.room_created')
else
render action: "new"
end
end
def update
@room = Room.find(params[:id])
if @room.update_attributes(params[:room])
redirect_to @room, :notice => t('flash.notice.room_updated')
else
render :action => "edit"
end
end
def destroy
226
Casa do Código
Capítulo 11. Controle de acesso
@room = Room.find(params[:id])
@room.destroy
redirect_to rooms_url
end
end
Por que declarar todas as ações nos filtros?
Para todas as opções de segurança, tenha preferência a ser explícito do
que implícito. No caso anterior, poderíamos fazer:
before_filter :require_authentication,
:except => [:index, :show]
Porém, se um dia criarmos novas ações, elas automaticamente terão o
filtro require_authentication aplicado e não necessariamente isso é o
que nós queremos. Além disso, ao ser explícitos, é bem mais fácil perce-
ber o que está acontecendo e ajuda a nos lembrar a configurar as permis-
sões corretas.
Com isso, concluímos a parte de filtros, fazendo com que o usuário tenha que
estar logado (ou não) em algumas situações. Ainda temos que fazer o controle de
permissões, ou seja, não permitir que um usuário não possa editar um quarto que
não pertença a ele. Porém, ainda não temos relacionamentos no nosso sistema. A
funcionalidade de relacionamentos entre objetos no ActiveRecord é uma das me-
lhores coisas dele! Você não está empolgado? Você verá como é fácil.
11.3
Relacionando seus objetos
Uma aplicação Web não pode ser completa sem haver uma modelagem em que ob-
jetos se relacionam. Relacionamentos podem ser simples, com um objeto se relacio-
nando diretamente a outro (uma conta de usuário relacionando a sua foto de perfil,
por exemplo), também conhecido como relacionamentos um-para-um (ou, em in-
glês, one-to-one), ou ainda como 1:1.
227


Casa do Código
Capítulo 11. Controle de acesso
para-muitos, *:* (ou many-to-many, em inglês). Para essa associação existir, é ne-
cessário uma tabela intermediária, chamada tabela de ligação. Imagine uma situação
em que um usuário possa participar de vários projetos e que um projeto possua vá-
rios membros. É necessário criar uma tabela que contenha duas chaves-estrangeiras,
uma apontando ao usuário e outra apontando ao projeto.
É natural que as tabelas de ligação acabem ganhando vida própria como um con-
ceito real dentro do sistema. Ou seja, ao invés de serem uma tabela contendo algu-
mas chaves-estrangeiras, elas são mais que isso. Para o caso do exemplo anterior, é
natural chamar a tabela de ligação de “participante”.
Figura 11.9: Diagrama do relacionamento muitos-para-muitos
O ActiveRecord possui facilidades para os três tipos de associações. Vamos
aprender na prática como vamos criar uma associação um-para-muitos. Tenho que
dizer que as associações um-para-um são muito sem graça e não vamos usar no site.
Brincadeiras a parte, associações um-para-um são bem simples de serem criadas e
são muito parecidas com o que vamos fazer, que é a associação um-para-muitos.
11.4
Relacione quartos a usuários
O que vamos fazer é criar a associação usuário ao quarto. Um usuário poderá ter ne-
nhum, um, ou vários quartos. Para isso, precisamos adicionar uma chave-estrangeira
no modelo Room para apontar para User. Criamos então uma migração:
$ rails g migration add_user_id_to_rooms user_id:integer
invoke active_record
create
db/migrate/20120718060441_add_user_id_to_rooms.rb
Como nomeamos a migração no padrão add_<columns>_to_<table>, o
ActiveRecord já gerou a migração com o código de inserção da coluna para nós.
Vamos apenas adicionar um índice para facilitar joins:
229
11.4. Relacione quartos a usuários
Casa do Código
class AddUserIdToRooms < ActiveRecord::Migration
def change
add_column :rooms, :user_id, :integer
add_index :rooms, :user_id
end
end
Convenção sobre chaves-estrangeiras
O ActiveRecord espera que você nomeie sua chave-estrangeira como
nome_do_relacionamento + _id, nesse caso, user_id. Fazendo dessa
maneira, o ActiveRecord é capaz de derivar automaticamente o nome
da coluna para fazer join. Seguir as convenções do Rails às vezes pode ser
chato se você está acostumado com outras modelagens, porém a conve-
niência é bastante grande quando você estiver programando seu sistema.
Vamos entender melhor em seguida.
Chaves-estrangeiras são apenas ids no banco, portanto não precisamos criar ne-
nhum tipo especial, apenas usaremos inteiros. Note que o ActiveRecord não cria
nenhum mecanismo de chaves-estrangeiras no banco de dados, portanto se você ti-
ver interesse em fazer isso, deverá executar SQL manualmente, usando o método
execute, lembrando que isso resultará no acoplamento de suas migrações em um
banco de dados específico.
230
Casa do Código
Capítulo 11. Controle de acesso
Criando modelos com associações
Nesse caso, estamos criando uma associação após a criação do modelo
quarto, portanto estamos criando a chave-estrangeira de um modo mais
“baixo nível”, adicionando uma coluna de inteiros a uma tabela. Porém,
se estivéssemos criando o modelo já com a referência, podemos usar o
tipo references, que automaticamente teremos a notação de id e tam-
bém o índice. Veja:
$ rails g model comment title body:text user:references
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.string :title
t.text :body
t.references :user
t.timestamps
end
add_index :comments, :user_id
end
end
Se você está pensando:
“ué, cadê a consistência?”, eu concordo
com você, deveria ser mais simples, como um add_references ou
add_belongs_to. Mas não é só nós que pensamos assim. No Rails 4,
ainda não lançado, vamos ter esses dois métodos.
Após executar rake db:migrate, teremos a mais nova coluna no nosso banco
de dados. Agora podemos criar as associações nos modelos em si e a parte divertida
vai começar!
Vamos editar o modelo Room (app/models/room.rb) e colocar uma class macro
para indicar que o modelo quarto pertence a um usuário:
231
11.4. Relacione quartos a usuários
Casa do Código
class Room < ActiveRecord::Base
belongs_to :user
attr_accessible :description, :location, :title
# ...
end
Com a class macro belongs_to, já é possível associar os objetos na relação quarto
-> usuário. O ActiveRecord já sabe qual objeto deve criar devido ao nome do rela-
cionamento (:user) e também já sabe qual campo usar para buscar o objeto devido
(user_id):
user = User.first
# => #<User id: 11, ... >
room = Room.first
# => #<Room id: 3, ..., user_id: nil>
room.user = user
# => #<User id: 11, ... >
room.save
# => true
room.user
# => #<User id: 11, ...>
Com esse relacionamento, já é possível mostrar na página de um quarto, o seu
dono. Mas antes de chegar lá, ainda não é possível saber quais quartos um usuário
possui. Seguindo todas as convenções do Rails, fazer isso é muito simples. Basta
adicionar uma única linha no modelo User (app/models/user.rb):
class User < ActiveRecord::Base
has_many :rooms
attr_accessible :bio, :email, :full_name, :location, :password,
:password_confirmation
# ...
end
232
Casa do Código
Capítulo 11. Controle de acesso
A class macro has_many faz várias coisas para descobrir o relacionamento. Pri-
meiro, como é um relacionamento um-para-muitos (has many significa, literal-
mente, “tem muitos”), o nome do relacionamento deverá estar no plural e portanto,
o modelo é o Room. Dada a natureza do relacionamento, o ActiveRecord sabe tam-
bém que o modelo room deverá ter um campo para o próprio modelo user e então
finalmente, consegue buscar todos os quartos que pertencem a um usuário. Muito
conveniente!
user = User.first
# => #<User id: 11, ... >
user.rooms
# => [#<Room id: 3, ...>]
Note no exemplo anterior como o relacionamento rooms retorna algo parecido
com um Array. Mas, na verdade, esse objeto nada mais é que um escopo. Isso signi-
fica que ainda é possível aplicar qualquer método de busca que vimos na seção “10.4
Consultas no banco de dados”, inclusive escopos nomeados!
user.rooms.where('title like ?', '%big%')
# SELECT "rooms".* FROM "rooms"
#
WHERE "rooms"."user_id" = 11 AND (title like '%big%')
#
# => [#<Room id: 3, title: "Big bedroom", ...>]
Viu como é fácil criar relacionamento de objetos com Rails? Veremos mais para
frente como criar relacionamentos muitos-para-muitos, no capítulo 12, “Avaliação
de quartos, relacionamentos muitos para muitos e organização do código”.
E o relacionamento um-para-um?
O relacionamento um-para-um é quase igual ao relacionamento um-
para-muitos. A diferença é que, ao invés de usar a class macro has_many,
você usa o has_one (deixando o nome no singular). É importante que o
belongs_to sempre fique no modelo que possui a chave-estrangeira.
Relacionamentos prontos, vamos dar início ao tratamento de segurança de dados
no controle de quartos.
233
11.5. Limite o acesso usando relacionamentos
Casa do Código
11.5
Limite o acesso usando relacionamentos
Os escopos são ótimos para controlar permissões de acesso e permitir apenas que
dados interessantes sejam exibidos. Como os relacionamentos também caracterizam
um escopo, vamos usá-lo para limitar o que o usuário pode editar.
Para isso, vamos usar uma técnica interessante: ao invés de fazer a busca (com o
.find) direto no modelo, sempre que formos criar, atualizar ou deletar um objeto do
banco, vamos limitar o escopo de acesso ao usuário logado. Dessa forma, se o usuário
logado não tiver permissão para acessar aquele objeto, o usuário irá deparar-se com
um erro de Página não encontrada (404).
Usar a associação possui outra vantagem: construir novos objetos usando a asso-
ciação fará com que o objeto criado já tenha a outra parte do relacionamento ligada
corretamente. Veja o exemplo:
user = User.first
# => #<User id: 11, ... >
user.rooms.build :title => 'Quarto aconchegante',
:description => 'Quarto grande com muita luz natural.'
# => #<Room id: nil,
#
title: "Quarto aconchegante",
#
location: nil,
# description: "Quarto grande com bastante luz natural.",
#
created_at: nil,
#
updated_at: nil,
#
user_id: 11>
room.user
# => #<User id: 11, ...>
Isso é bastante útil para não termos que associar o usuário, neste exemplo, no
controle de forma manual, ou ainda pior, no formulário. Vamos aplicar essas ideias
no controle RoomsController, mas antes vamos fazer uma pausa para uma lição
importante: segurança de dados.
O Diaspora (http://joindiaspora.com) é uma alternativa livre ao Facebook (http:
//facebook.com). Sua proposta é que, ao contrário do Facebook, os dados dos usuá-
rios são realmente privados e a plataforma possui código-fonte livre para ser inves-
tigado. Você pode, por exemplo, instalar o Diaspora em um servidor privado seu e
possuir sua própria rede social. Por causa de sua causa nobre, fez bastante barulho
nos Estados Unidos no seu lançamento.
234
Casa do Código
Capítulo 11. Controle de acesso
Porém, também foi um grande fiasco técnico no lançamento. A plataforma que
deveria ser segura e privada possuía grandes falhas de segurança de forma que qual-
quer usuário poderia ver, criar e editar fotos e outros conteúdos de qualquer usuário.
Obviamente os desenvolvedores do Diaspora aprenderam com os erros e já melho-
raram a plataforma.
O problema estava nos controles, e temos exatamente a mesma situação ocor-
rendo no controle do recurso quarto:
class RoomsController < ApplicationController
before_filter :require_authentication,
:only => [:new, :edit, :create, :update, :destroy]
# ...
def update
@room = Room.find(params[:id])
respond_to do |format|
if @room.update_attributes(params[:room])
format.html { redirect_to @room,
notice: 'Room was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: @room.errors,
status: :unprocessable_entity }
end
end
end
end
O que acontece é que temos o filtro para impedir que um usuário não logado
faça atualizações (e outras ações também, como remover) em qualquer objeto, mas
isso não implica que um usuário não pode atualizar um quarto que não pertence a
ele.
Para simular essa falha de segurança é bastante simples. Um usuário pode abrir
um formulário de edição de um quarto que pertence a ele, alterar o ID do quarto na
URL, e enviar. O código irá buscar pelo quarto, que existe e é valido, e irá atualizar
o objeto, de maneira indevida.
A melhor forma de impedir isso é sempre usar escopos quando fizermos bus-
cas de objetos. No nosso caso, o usuário atual possui quartos, então vamos usar o
235
11.5. Limite o acesso usando relacionamentos
Casa do Código
has_many a nosso favor:
1 class RoomsController < ApplicationController
2
before_filter :require_authentication,
3
:only => [:new, :edit, :create, :update, :destroy]
4
5
# ...
6
def update
7
@room = current_user.rooms.find(params[:id])
8
9
respond_to do |format|
10
if @room.update_attributes(params[:room])
11
format.html { redirect_to @room,
12
notice: 'Room was successfully updated.' }
13
format.json { head :no_content }
14
else
15
format.html { render action: "edit" }
16
format.json { render json: @room.errors,
17
status: :unprocessable_entity }
18
end
19
end
20
end
21 end
A única alteração foi a linha 7, ao invés de buscarmos no recurso todo, buscamos
pelos quartos que o usuário tem acesso. Dessa forma, mesmo se um usuário mal
intencionado alterar o ID do recurso no formulário, a atualização do modelo não irá
ocorrer. Isso acontece porque, ao tentar buscar em seus quartos, o objeto não será en-
contrado, o ActiveRecord irá disparar a exceção ActiveRecord::RecordNotFound
e o controle irá retornar um erro 404 Not found (não encontrado) para o usuário,
o comportamento correto.
Aplicando essa simples mas preciosa lição de segurança, vamos aplicar esse
conceito (e aproveitando para fazer umas limpezas no código) no controle
RoomsController (app/controllers/rooms_controller.rb) por completo:
class RoomsController < ApplicationController
def index
# Exercício pra você! Crie um escopo para ordenar
# os quartos dos mais recentes aos mais antigos.
@rooms = Room.most_recent
end
236
Casa do Código
Capítulo 11. Controle de acesso
def show
@room = Room.find(params[:id])
end
def new
@room = current_user.rooms.build
end
def edit
@room = current_user.rooms.find(params[:id])
end
def create
@room = current_user.rooms.build(params[:room])
if @room.save
redirect_to @room, :notice => t('flash.notice.room_created')
else
render action: "new"
end
end
def update
@room = current_user.rooms.find(params[:id])
if @room.update_attributes(params[:room])
redirect_to @room, :notice => t('flash.notice.room_updated')
else
render :action => "edit"
end
end
def destroy
@room = current_user.rooms.find(params[:id])
@room.destroy
redirect_to rooms_url
end
end
237


Casa do Código
Capítulo 11. Controle de acesso
Figura 11.11: Listagem de quartos com estilo
Antes de criar o template, vamos precisar de um helper. Crie o RoomsHelper
(app/helpers/rooms_helper):
module RoomsHelper
def belongs_to_user(room)
user_signed_in? && room.user == current_user
end
end
Esse código verifica se o usuário está logado e se o quarto pertence a ele. Vamos
usar isso para exibir os links de edição e remoção do modelo.
Em
seguida,
vamos
alterar
o
template
da
ação
index
(app/views/rooms/index.html.erb):
<h1><%= t '.title' %> </h1>
<% @rooms.each do |room| %>
<article class="room">
<h2><%= link_to room.title, room %> </h2>
<span class="created">
239
11.6. Exibição e listagem de quartos
Casa do Código
<%= t '.owner_html',
:owner => room.user.full_name,
:when => l(room.created_at, :format => :short) %>
</span>
<p>
<span class="location">
<%= link_to room.location,
"https://maps.google.com/?q=#{room.location}",
:target => :blank %>
</span>
</p>
<p><%= room.description %> </p>
<% if belongs_to_user(room) %>
<ul>
<li><%= link_to t('.edit'), edit_room_path(room) %> </li>
<li><%= link_to t('.destroy'), room_path(room),
:method => :delete, :data =>
{:confirm => t('dialogs.destroy')}
%> </li>
</ul>
<% end %>
</article>
<% end %>
No template anterior temos uma novidade: o uso do l. O l, atalho para
localize, faz parte do sistema de internacionalização, mas com um papel diferente,
o de “traduzir” datas e horário, usando o formato adequado para cada idioma. Por
exemplo, no Brasil usamos datas no formato dia, mês e ano. Porém, nos Estados
Unidos, o mais comum é usar mês, dia e ano. Mas não se preocupe com essas coisas,
o I18n trabalha para você, desde que você tenha o arquivo de I18n do idioma.
O localize ainda aceita alguns formatos de data, tal como :short, :long e o pa-
drão (:default, no YAML). Você pode ainda criar os formatos que quiser, seguindo
o padrão strftime (padrão de formatação de hora e data de sistemas POSIX). Veja
os formatos que já vem com o Rails:
formats:
default: ! '%A, %d de %B de %Y, %H:%M h'
long: ! '%A, %d de %B de %Y, %H:%M h'
short: ! '%d/%m, %H:%M h'
Para saber o que é cada símbolo, você pode consultar a documentação do Ruby
240
Casa do Código
Capítulo 11. Controle de acesso
no método strftime, ou digitar man strftime, caso você esteja em OS X ou Linux.
Partials de modelos
Uma outra convenção muito útil do Rails são as partials de modelos. Veja o
exemplo a seguir:
<%= render @room %>
Se o objeto @room for uma instância do modelo Room, o Rails irá buscar pela
partial _room.html.erb, e é exatamente o que vamos fazer para deixar o template
mais limpo:
<% @rooms.each do |room| %>
<%= render room %>
<% end %>
Além disso, o Rails é capaz de renderizar coleções, ou seja, se você passar um
Array, por exemplo, o Rails irá renderizar cada elemento da lista. Com ambas as
alterações, a listagem de quartos (app/views/rooms/index.html.erb) fica da se-
guinte maneira:
<h1><%= t '.title' %> </h1>
<%= render @rooms %>
E a partial (app/views/rooms/_room.html.erb):
<article class="room">
<h2><%= link_to room.title, room %> </h2>
<span class="created">
<%= t '.owner_html',
:owner => room.user.full_name,
:when => l(room.created_at, :format => :short) %>
</span>
<p>
<span class="location">
<%= link_to room.location,
"https://maps.google.com/?q=#{room.location}",
:target => :blank %>
</span>
</p>
241
11.6. Exibição e listagem de quartos
Casa do Código
<p><%= room.description %> </p>
<% if belongs_to_user(room) %>
<ul>
<li><%= link_to t('.edit'), edit_room_path(room) %> </li>
<li><%= link_to t('.destroy'), room_path(room),
:method => :delete,
:data => {:confirm => t('dialogs.destroy')}
%> </li>
</ul>
<% end %>
</article>
Note que na partial fazemos referência ao quarto usando a variável room. Isso
acontece porque, quando usamos render em um objeto, o Rails mapeia o nome do
objeto a ser renderizado pelo nome da partial. Tome cuidado, pois isso é implícito
e fica difícil de descobrir o que está acontecendo em algumas situações. Se você
preferir, podemos ser explícito em qual objeto devemos mapear para a partial
<%= render :partial => 'room', :object => current_user.rooms.first %>
O nome do objeto na partial ainda será room, por causa do nome da partial, mas
o objeto que estamos renderizando é o current_user.rooms.first.
Agora vamos trabalhar no CSS. O que temos que fazer é extrair o mixin shadow
do default.css.scss para um arquivo separado para que possamos usar tanto no
default.css.scss quanto no novo CSS. Para isso, vamos criar um novo CSS para
a função de sombra (app/assets/stylesheets/shadow.css.scss):
@mixin shadow($color, $x, $y, $radius) {
-moz-box-shadow:
$color $x $y $radius;
-webkit-box-shadow: $color $x $y $radius;
box-shadow:
$color $x $y $radius;
}
Depois de apagar o mixin do default.css.scss, temos que incluir a função.
Fazemos isso com o uso do @import:
@import "shadow";
$header-height: 55px;
$content-width: 700px;
...
242
Casa do Código
Capítulo 11. Controle de acesso
E
para
finalizar
o
CSS,
vamos
criar
estilo
para
quartos
(app/assets/stylesheets/room.css.scss):
@import "shadow";
.room {
background-color: white;
padding: 20px 25px;
margin-top: 10px;
@include shadow(#ccc, 0, 3px, 6px);
}
.room h2 {
display: inline;
a { font-size: 1.3em; }
}
Pronto. A próxima parte é o I18n (config/locales/pt-BR.yml):
pt-BR:
# ...
dialogs:
destroy: 'Você tem certeza que quer remover?'
rooms:
index:
title: 'Quartos disponíveis'
room:
owner_html: '— %{owner} (%{when})'
edit: 'Editar'
destroy: 'Remover'
# ...
Lembre-se que o _html é necessário para que o I18n retorne entidades HTML
(mdash;).
Por fim, a última alteração que precisamos fazer é atualizar o template da ação
show (app/views/users/show.html.erb):
<%= render @room %>
243
11.6. Exibição e listagem de quartos
Casa do Código
Pronto, terminamos as regras de acesso! No próximo capítulo, vamos aprender
como fazer avaliação de quartos, dando uma pontuação de 1 a 5 para um quarto,
usando AJAX e associações um pouco mais complicadas do que vimos agora.
244
Capítulo 12
Avaliação de quartos,
relacionamentos muitos para muitos
e organização do código
Ruby foi construído para tornar os programadores mais felizes.
– Yukihiro “Matz” Matsumoto
A estrutura de dados e templates já está bem completa. O que queremos fazer
nesse capítulo é possibilitar a avaliação de quartos, assim novos usuários do col-
cho.net podem observar a opinião dos outros.
Primeiro, precisamos criar o modelo de avaliação e os relacionamentos entre os
modelos Usuario - User e Quarto - Room, com algumas validações. Com esse relacio-
namento, vamos em seguida criar as ações de controle para alterar e criar avaliações.
No front-end, vamos alterar o template do quarto para incluir as opções de como
fazer a avaliação, via AJAX. Para isso funcionar, vamos criar o controle com ações
um pouco diferentes, para responder AJAX. O resultado final será o seguinte:

Casa do Código
Capítulo 12. Avaliação de quartos, relacionamentos muitos para muitos e. . .
12.1
Relacionamentos muitos-para-muitos
Uma avaliação é um modelo que pertence a um quarto e um usuário ao mesmo
tempo. Isso significa que para uma avaliação ser única, ela depende de duas chaves
estrangeiras: uma para o quarto e outra para o usuário avaliador.
Vamos precisar acessar as avaliações através de um quarto. Por isso, vamos pre-
cisar criar o relacionamento no modelo quarto. Isso não será complicado. O outro
modelo que precisará ser alterado, como esperado, é o usuário. Precisamos criar o
relacionamento de modo que possamos acessar facilmente todos os quartos avalia-
dos pelo usuário.
Para ter esse resultado, vamos:
1) Criar modelo Review com chaves estrangeiras user_id e room_id, além de outros
campos;
2) Criar índice para garantir unicidade do par user_id e room_id, ou seja, um usuá-
rio não pode avaliar um mesmo quarto mais de uma vez;
3) Criar validações no novo modelo, como por exemplo, não permitir que o usuário
avalie o seu próprio quarto;
4) Criar o relacionamento no modelo quarto;
5) Criar o relacionamento no modelo usuário.
Criando chaves estrangeiras
Vamos usar o gerador para gerar o modelo Review, já com os relacionamentos:
$ rails g model review user:references room:references points:integer
invoke active_record
create
db/migrate/20120726071529_create_reviews.rb
create
app/models/review.rb
invoke
test_unit
create
test/unit/review_test.rb
create
test/fixtures/reviews.yml
A migração gerada será parecida com:
247
12.1. Relacionamentos muitos-para-muitos
Casa do Código
class CreateReviews < ActiveRecord::Migration
def change
create_table :reviews do |t|
t.references :user
t.references :room
t.integer :points
t.timestamps
end
add_index :reviews, :user_id
add_index :reviews, :room_id
end
end
A única coisa que precisamos adicionar nessa migração, é o índice de unicidade
no par [user_id, room_id]:
class CreateReviews < ActiveRecord::Migration
def change
create_table :reviews do |t|
t.references :user
t.references :room
t.integer :points
t.timestamps
end
add_index :reviews, :user_id
add_index :reviews, :room_id
add_index :reviews, [:user_id, :room_id], :unique => true
end
end
Para efetivar essas alterações no banco de dados, executamos o comando rake
db:migrate:
$ rake db:migrate
== CreateReviews: migrating ===================
-- create_table(:reviews)
-> 0.0332s
-- add_index(:reviews, :user_id)
-> 0.0011s
248
Casa do Código
Capítulo 12. Avaliação de quartos, relacionamentos muitos para muitos e. . .
-- add_index(:reviews, :room_id)
-> 0.0007s
-- add_index(:reviews, [:user_id, :room_id], {:unique=>true})
-> 0.0013s
== CreateReviews: migrated (0.0366s) ==========
Nenhum segredo até então. Nada de complicado também no modelo Review
(app/models/review.rb). O Rails até já criou o modelo com os belongs_to neces-
sários, sem contar o attr_accessible:
class Review < ActiveRecord::Base
belongs_to :user
belongs_to :room
attr_accessible :points
end
Vamos usar uma funcionalidade do Rails chamada counter cache, ou seja, ca-
che de contadores. Como vamos sempre calcular o número de avaliações, o Rails
já guarda esse valor pré-calculado em uma coluna do banco de dados automatica-
mente, desde que usemos a opção :counter_cache => true no belongs_to:
class Review < ActiveRecord::Base
belongs_to :user
belongs_to :room, :counter_cache => true
attr_accessible :points
end
Precisamos criar uma nova coluna na tabela de quartos de modo a guardar essa
contagem:
$ rails g migration add_counter_cache_to_rooms reviews_count:integer
invoke active_record
create
db/migrate/20120812051945_add_counter_cache_to_rooms.rb
$ rake db:migrate
== AddCounterCacheToRooms: migrating =================
-- add_column(:rooms, :reviews_count, :integer)
-> 0.0536s
== AddCounterCacheToRooms: migrated (0.0537s) ========
Porém, isso não é suficiente. Precisamos colocar algumas validações no modelo
de avaliações:
249
12.1. Relacionamentos muitos-para-muitos
Casa do Código
class Review < ActiveRecord::Base
# Criamos um Array de 5 elementos, ao invés de range.
POINTS = (1..5).to_a
belongs_to :user
belongs_to :room, :counter_cache => true
attr_accessible :points
validates_uniqueness_of :user_id, :scope => :room_id
validates_presence_of :points, :user_id, :room_id
validates_inclusion_of :points, :in => POINTS
end
Essas validações são bem similares com as que já vimos, com exceção da opção
scope aplicado à validação uniqueness. Ela limita o escopo em que a verificação
de unicidade ocorre, ou seja, neste exemplo, o user_id pode repetir caso o room_id
seja diferente.
Agora vamos ao modelo Room (app/models/room.rb). Nele vamos criar o rela-
cionamento um-para-muitos: um quarto possui muitas avaliações.
class Room < ActiveRecord::Base
has_many :reviews
belongs_to :user
#...
end
250
Casa do Código
Capítulo 12. Avaliação de quartos, relacionamentos muitos para muitos e. . .
Relacionamentos de ponta-a-ponta
Em relacionamentos muitos-para-muitos, é comum precisarmos acessar
o modelo da outra ponta da tabela de ligação, ou seja, no Colcho.net,
pode ser interessante um usuário saber todos os quartos que ele tem al-
gum voto. A ideia é juntar todos os registros de avaliação que o usuário
possui e, através desses registros, buscar os quartos.
O ActiveRecord possui uma maneira de te ajudar com este problema.
Uma vez definido o relacionamento has_many :reviews, podemos criar
o seguinte relacionamento:
class Room < ActiveRecord::Base
# É necessário definir o has_many primeiro!
has_many :reviews
has_many :reviewed_rooms, :through => :reviews, :source => :room
# ...
end
Esse exemplo cria o relacionamento reviewed_rooms que, através de
todas as avaliações que um usuário tem (especificado pela opção
:through), irá buscar, nesse modelo de ligação, o relacionamento :room,
retornando todos os quartos que satisfazem estes relacionamentos.
Por fim, vamos criar o relacionamento no modelo User (app/models/user.rb),
para que possamos ter acesso às avaliações daquele usuário:
class User < ActiveRecord::Base
has_many :rooms
has_many :reviews
# ...
end
Pronto! Todos os relacionamentos foram criados. Antes de continuar, veja o
exemplo a seguir:
251
12.1. Relacionamentos muitos-para-muitos
Casa do Código
# Lembre-se do sandbox para evitar a perda de dados!
# Para usar o sandbox, basta fazer "rails c --sandbox"
room = Room.last
# => #<Room id: 5, ... >
review = room.reviews.build :points => 3
review.user = User.last
review.save
# => #<Review id: 1, ... >
Review.all
# => [#<Review id: 1, ... >]
room.destroy
# => #<Room id: 5, >
r = Review.first
# => #<Review id: 1, >
r.room
# => nil
Diferença entre #destroy e #delete
O ActiveRecord possui dois métodos para destruir objetos no banco de
dados: o #destroy e o #delete. É muito importante lembrar que eles
possuem comportamentos diferentes.
O #destroy é o método que normalmente deve ser usado. Ele executa
todos os callbacks e deleta o objeto no banco de dados. O #delete, por
sua vez, apenas executa o DELETE no banco de dados.
O que acontece é que removemos o objeto room, porém ainda temos referências
inválidas no banco de dados. Para arrumar isso, poderíamos criar um callback no
momento que um objeto está sendo destruído (after_delete) e remover os objetos
referenciados. Essa solução funciona, porém, o ActiveRecord facilita a nossa vida.
252
Casa do Código
Capítulo 12. Avaliação de quartos, relacionamentos muitos para muitos e. . .
12.2
Removendo objetos sem deixar rastros
No momento que vamos destruir um objeto, é possível destruir também objetos rela-
cionados. Para isso, adicionamos uma opção nas associações que queremos remover
quando o objeto for destruído, chamada dependent. Seu comportamento é muito
parecido com a opção CASCADE do SQL:
• destroy - Executa #destroy em todos os objetos associados, executando os
callbacks de cada um;
• delete_all - Deleta os objetos associados via SQL apenas, sem execução de
callbacks;
• nullify - Apenas marca as chaves estrangeiras dos objetos relacionados com
NULL;
• restrict - Impede a remoção do objeto se houver objetos relacionados.
Nesse caso, queremos usar a opção :destroy, para que os callbacks sejam de fato
chamados, precisamos destruir todas as avaliações associadas ao usuário quando ele
é destruído. Para isso, basta colocar uma opção no has_many. Veja como deve ficar
o modelo User (app/models/user.rb):
class User < ActiveRecord::Base
# Aproveite a oportunidade para atualizar o outro
# relacionamento:
has_many :rooms, :dependent => :destroy
has_many :reviews, :dependent => :destroy
# ...
end
E, por fim, no modelo Room (app/models/room.rb):
class Room < ActiveRecord::Base
has_many :reviews, :dependent => :destroy
belongs_to :user
# ...
end
253
12.3. Criando avaliações com pitadas de AJAX
Casa do Código
Cuidado com o :dependent => :destroy
Apesar da grande conveniência, o :dependent => :destroy pode ser
perigoso. Como o ActiveRecord tem que instanciar cada objeto e cha-
mar o método #destroy, que por sua vez, pode ter seus relacionamentos,
que vai instanciar todos os objetos, e... Deu pra entender onde isso vai
parar, né?
Além de lento, esse procedimento pode ser perigoso. Se o seu banco
de dados tiver muitos registros, o Rails irá instanciar um objeto
ActiveRecord para cada, resultando em muita alocação de memória e
muitas interrupções do garbage collector para limpar objetos não usados.
Em situações assim, você pode pensar em uma das seguintes soluções:
• Marcar os objetos a serem removidos por um serviço que executa
de tempos em tempos;
• Usar dependent => :delete e usar chaves estrangeiras com on
delete cascade, no próprio banco, fazendo com que o banco de
dados fique responsável pela limpeza dos dados.
12.3
Criando avaliações com pitadas de AJAX
Pronto, agora que temos o modelo de avaliação, vamos implementar o front-end,
usar uma nova fonte e incluir o JavaScript e chamadas AJAX.
Para isso, primeiro vamos criar o ponto de entrada para o recurso Review.
Ele fará parte do recurso Room, ou seja, a URL será montada da seguinte forma:
/rooms/:room_id/reviews. Isso dará ao nosso projeto uma noção de que um quarto
possui uma ou diversas avaliações, que é o que estamos procurando.
Criaremos a rota da seguinte forma (config/routes.rb):
Colchonet::Application.routes.draw do
#...
resources :rooms do
254
Casa do Código
Capítulo 12. Avaliação de quartos, relacionamentos muitos para muitos e. . .
resources :reviews, :only => [:create, :update]
end
# ...
end
Ao aninhar o recurso :reviews no recurso :rooms na rota, alcançamos o ani-
nhamento também na rota. Podemos ver o resultado executando rake routes:
room_reviews POST (/:locale)/rooms/:room_id/reviews(.:format)
reviews#create
{:locale=>/en|pt\-BR/}
room_review PUT
(/:locale)/rooms/:room_id/reviews/:id(.:format)
rooms/reviews#update
{:locale=>/en|pt\-BR/}
O Rails irá buscar o controle como ReviewsController e, como de costume, fi-
caria em app/controllers/reviews_controller.rb. Porém, depois de alguns me-
ses ou anos de projetos, deixar todos os controles nessa pasta fica uma bagunça sem
tamanho. Imagine uma pasta com mais de 100 controles, cada um em uma rota
diferente...
Por essa razão, organizaremos controles que possuem aninhamento em módu-
los. As principais vantagens dessa prática são: extrair comportamento comum, como
buscar o elemento a qual o recurso pertence (por exemplo, buscar o objeto room no
controle de avaliações) ou filtros e a organização dos arquivos.
Portanto, vamos customizar a nossa rota de forma que o Rails bus-
que o controle na pasta app/controllers/rooms/ e a classe do controle seja
Rooms::ReviewsController:
Colchonet::Application.routes.draw do
#...
resources :rooms do
resources :reviews, :only => [:create, :update], :module => :rooms
end
# ...
end
255
12.3. Criando avaliações com pitadas de AJAX
Casa do Código
Agora
vamos
criar
o
controle
Rooms::ReviewsController
(app/controllers/rooms/reviews_controller.rb):
class Rooms::ReviewsController < ApplicationController
before_filter :require_authentication
def create
review = room.reviews.
find_or_initialize_by_user_id(current_user.id)
review.update_attributes!(params[:review])
head :ok
end
def update
create
end
private
def room
@room ||= Room.find(params[:room_id])
end
end
Usamos nesse controle um método muito útil do ActiveRecord:
o dy-
namic finder .find_or_initialize_by_.... Em modelos ou associações, o
.find_or_initialize_by_... irá fazer uma busca pelos atributos mencionados no
nome do método. Se não encontrar, um novo objeto será instanciado (mas não salvo
no banco de dados) com os atributos passados. No nosso exemplo, faremos a busca
por user_id e, se não encontrado, o ActiveRecord irá criar uma nova instância de
Review já com o user_id marcado como current_user.id.
256
Casa do Código
Capítulo 12. Avaliação de quartos, relacionamentos muitos para muitos e. . .
Método similar: find_or_create_by_...
O
ActiveRecord
ainda
possui
outro
método,
chamado
find_or_create_by_....
O funcionamento dele é bem parecido
com o %find_or_initialize_by_.... A diferença é que, ao invés de
apenas instanciar o objeto, o ActiveRecord cria uma entrada no banco
de dados.
Como vamos responder apenas a requisições AJAX, não precisamos renderi-
zar nenhum conteúdo. Portanto, apenas respondemos com o código HTTP 201
Created quando há sucesso. Nesse caso, como o único input do usuário é um
valor pré-selecionado, não devemos encontrar erros. Se por um acaso encontrar-
mos, há algum problema em nosso projeto e portanto usamos o método bang no
#update_attributes!, de forma a disparar exceções e ficar mais fácil de vermos
que há algo errado.
Lembramos que só existe uma única avaliação de um usuário a um quarto. Dessa
forma, o comportamento do update é o mesmo do create, só que sempre iremos
encontrar o objeto no find_or_initialize_.... O restante é o mesmo e, portanto,
vamos apenas delegar um método ao outro.
Falha silenciosa vs. falha barulhenta
Quando desenvolvemos aplicações, temos a tendência de tentar tratar
ou silenciar erros, evitando que usuários sejam presenciados com uma
página de erro.
Porém, é importante salientar que, quando fazemos isso, fica mais difí-
cil descobrir que algo está errado e tentar caçar algum bug misterioso.
Quando algo inesperado acontece, é melhor ver um stack trace do que
tentar descobrir, fazendo debug por horas para descobrir porque uma
variável está nil, por exemplo.
257
12.3. Criando avaliações com pitadas de AJAX
Casa do Código
Dica: Reuso de código em controles
Não precisamos criar abstrações no nosso exemplo. Porém, na neces-
sidade de compartilhar código entre vários controles em um mesmo
módulo, podemos criar uma classe chamada Rooms::BaseController
e nela colocar filtros e outros métodos interessantes:
class Rooms::BaseController < ApplicationController
before_filter :require_authentication
private
def room
@room ||= Room.find(params[:id])
end
end
Em seguida, basta herdar desse controle e o comportamento será com-
partilhado:
class Rooms::ReviewsController < Rooms::BaseController
def create
review = room.reviews.
find_or_initialize_by_user_id(current_user.id)
# ...
end
end
Por
fim,
vamos
acertar
o
controle
RoomsController
(app/controllers/rooms_controller.rb) para construir um objeto de avali-
ação a ser usado no template de quarto:
class RoomsController < ApplicationController
# ...
def show
258

Casa do Código
Capítulo 12. Avaliação de quartos, relacionamentos muitos para muitos e. . .
@room = Room.find(params[:id])
if user_signed_in?
@user_review = @room.reviews.
find_or_initialize_by_user_id(current_user.id)
end
end
#...
end
Controles prontos, vamos aos templates. Vamos colocar as tradicionais estrelas
de avaliação, mas a priori vamos focar na funcionalidade. Vamos usar radio buttons
para que o usuário escolha a pontuação, de 1 a 5:
Figura 12.3: Avaliação de quartos com radio buttons
Para fazer isso,
vamos criar um formulário na partial de quartos
(app/views/rooms/_room.html.erb):
<article class="room">
<h2><%= link_to room.title, room %> </h2>
<%= render :partial => 'review', :object => @user_review %>
<span class="created">
<%= t '.owner_html',
:owner => room.user.full_name,
:when => l(room.created_at, :format => :short) %>
</span>
259
12.3. Criando avaliações com pitadas de AJAX
Casa do Código
...
</article>
Na partial review (app/views/rooms/_review.html.erb), teremos:
<section class="review">
<% if user_signed_in? %>
<%= form_for [review.room, review] do |f| %>
<% Review::POINTS.each do |point| %>
<%= f.radio_button :points, point
%>
<%= f.label :points, point, :value => point %>
<% end %>
<%= f.submit %>
<% end %>
<% else %>
<span class="login_required">
<%= t('.login_to_review') %>
</span>
<% end %>
</section>
A primeira diferença que você vai notar é a construção da rota para o form_for.
Como o recurso “avaliação” é aninhado ao recurso “quarto”, é necessário identificar
a qual quarto pertence a nova avaliação que criamos no controle de quartos. Usando
a notação de Array, dizemos ao Rails todas as dependências da rota.
Note que isso causa um problema com a listagem de quartos (ação index). Va-
mos resolver esse problema ainda neste capítulo. Por agora, vamos focar na exibição
de quartos.
260
Casa do Código
Capítulo 12. Avaliação de quartos, relacionamentos muitos para muitos e. . .
Rotas com namespaces
É possível criar rotas com namespaces, ou seja, um nome que na verdade
não representa um recurso, mas que divide a aplicação em “módulos”.
Por exemplo, uma área de administração (ou “admin”) pode ser um na-
mespace. Para declará-los nas rotas, basta fazer:
namespace :admin do
resources :products
end
Para você identificar namespaces nos formulários, você deve também
usar a notação de Array:
<%= form_for [:admin, @product] do |f| %>
...
Quando criamos formulários em HTML, os campos de label possuem um atri-
buto chamado for, que deve possuir o name ou id do elemento à que este label se
associa. Fazendo isso, clicar no texto do label irá selecionar o campo de texto, se
associado com um campo de texto, selecionar um elemento de um grupo de radio
buttons e assim por diante.
Para tornar radio buttons em um mesmo grupo (ou seja, selecionando um irá
desmarcar o outro), é necessário usar um mesmo name. Isso quebra com a forma
de que o label funciona, ou seja, todos os labels de um mesmo grupo, se usando
os helpers do Rails, iriam apontar para um mesmo botão, deixando de funcionar da
maneira esperada.
É aí que entra a opção :value do helper label. Você deve usar essa opção tanto
para labels em elementos do tipo radio button ou check boxes. O helper irá construir
o atributo for apontando para o elemento correto.
Nesse
template,
usamos
uma
nova
entrada
no
I18n
(config/locales/pt-BR.yml):
pt-BR:
# ...
261
12.3. Criando avaliações com pitadas de AJAX
Casa do Código
rooms:
# ...
review:
login_to_review: 'Faça o login para avaliar quartos'
Por fim, adicione as seguintes regras CSS para os botões alinharem da forma
correta e estilizar o texto de login (app/assets/stylesheets/room.css.scss):
// Usamos o #content para aumentar a especificidade do seletor,
// ou seja, tornar essa regra mais importante que outras.
#content .review form {
margin: 0;
}
#content .review {
margin: 0;
padding: 0;
float: right;
border: none;
}
.review label {
display: inline;
}
.review .login_required {
font-size: 0.8em;
color: #666;
font-variant: small-caps;
}
262

Casa do Código
Capítulo 12. Avaliação de quartos, relacionamentos muitos para muitos e. . .
Figura 12.4: Texto de login necessário
E traduzimos o modelo Review:
pt-BR:
# ...
activerecord:
models:
room: Quarto
user: Usuário
review: Avaliação
12.4
Diga adeus a regras complexas de apresenta-
ção: use presenters
Tem algo que muito me incomoda nesse código, para ser sincero. Temos uma regra
de template repetida tanto no RoomsController quanto no template.
No RoomsController:
if user_signed_in?
@user_review = @room.reviews.
find_or_initialize_by_user_id(current_user.id)
end
E no template:
<% if user_signed_in? %>
<%= form_for [review.room, review] do |f| %>
<%# ... %>
263
12.4. Diga adeus a regras complexas de apresentação: use presenters
Casa do Código
<% else %>
<%# ... %>
<% end %>
Para resolver esse problema, vamos criar uma classe que vamos usar tanto nos
templates quanto no controle.
Os presenters, ou apresentadores, são responsáveis por fazer essa ligação de uma
maneira descomplicada e resolvendo o problema de templates ou controles comple-
xos.
Vamos criar então o nosso RoomPresenter.
Crie o arquivo e a pasta
app/presenters/room_presenter.rb.
A ideia dessa classe é a seguinte: podemos passar um quarto a qual a avaliação irá
pertencer e o contexto que o presenter se aplica: pode ser tanto um template quanto
o próprio controle. Precisamos deste contexto para saber se o usuário está logado,
por exemplo. O restante serve apenas para tornar o template mais elegante.
class RoomPresenter
delegate :user, :created_at, :description, :location, :title,
:to => :@room
def initialize(room, context, show_form=true)
@context = context
@room = room
@show_form = show_form
end
def can_review?
@context.user_signed_in?
end
def show_form?
@show_form
end
def review
@review ||= @room.reviews.
find_or_initialize_by_user_id(@context.current_user.id)
end
def review_route
264
Casa do Código
Capítulo 12. Avaliação de quartos, relacionamentos muitos para muitos e. . .
[@room, review]
end
def route
@room
end
def review_points
Review::POINTS
end
# render @room resulta na renderização da partial 'room'
def to_partial_path
'room'
end
end
As
ações
show
e
index
do
controle
RoomsController
(app/controllers/rooms_controller) ficam da seguinte forma:
class RoomsController < ApplicationController
# ...
def index
@rooms = Room.most_recent.map do |room|
# Não exibiremos o formulário na listagem
RoomPresenter.new(room, self, false)
end
end
def show
room_model = Room.find(params[:id])
@room = RoomPresenter.new(room_model, self)
end
end
A partial room (app/views/rooms/_room.html.erb), por sua vez, fica:
<article class="room">
<h2><%= link_to room.title, room.route %> </h2>
<%= render :partial => 'review', :locals => {:room => room} %>
265
12.4. Diga adeus a regras complexas de apresentação: use presenters
Casa do Código
<span class="created">
<%# ... $>
<% if belongs_to_user(room) %>
<ul>
<li><%= link_to t('.edit'), edit_room_path(room.route) %> </li>
<li><%= link_to t('.destroy'), room_path(room.route),
:method => :delete,
:data => {:confirm => t('dialogs.destroy')} %>
</li>
</ul>
<% end %>
</article>
Por
fim,
vamos
extrair
o
formulário
de
review
(app/views/rooms/_review.html.erb), em uma nova partial, review_form
(app/views/rooms/_review_form.html.erb).
O resultado é que a partial app/views/rooms/_review.html.erb ficará como:
<section class="review">
<% if room.show_form? %>
<%= render :partial => 'review_form', :locals => {:room => room} %>
<% end %>
</section>
E a partial app/views/rooms/_review_form.html.erb ficará da seguinte ma-
neira:
<% if room.can_review? %>
<%= form_for room.review_route do |f| %>
<% room.review_points.each do |point| %>
<%= f.radio_button :points, point
%>
<%= f.label :points, point, :value => point %>
<% end %>
<%= f.submit %>
<% end %>
<% else %>
<span class="login_required">
<%= t('.login_to_review') %>
266
Casa do Código
Capítulo 12. Avaliação de quartos, relacionamentos muitos para muitos e. . .
</span>
<% end %>
12.5
jQuery e Rails: fazer requisições AJAX ficou
muito fácil
O código que temos atualmente cria avaliações, porém clicar no botão ‘Criar Avali-
ação’ irá nos exibir uma página em branco. A ação foi projetada para que façamos a
requisição via AJAX, e é isso que vamos fazer agora.
Você deve estar pensando: “bom, vamos primeiro instalar nosso framework Ja-
vascript preferido (*aham* jQuery) e usá-lo para facilitar nossa vida”. Certíssimo!
Atualmente é difícil o site que use JavaScript e não possua o jQuery, para o bem ou
para o mal.
O Rails, como um framework que quer te ajudar a colocar o seu site no ar, já
instala o jQuery (http://jquery.com/) em sua aplicação. Melhor que isso: o Rails
ainda integra o jQuery em muito dos helpers, incluindo formulários via AJAX.
Portanto, para fazer nosso formulário usar AJAX e enviar as requisições assin-
cronamente, basta alterar uma linha.
Altere
o
form_for
na
partial
do
formulário
de
review
(app/views/rooms/_review_form.html.erb) para incluir a opção :remote
=> true:
<% if room.can_review? %>
<%= form_for room.review_route, :remote => true do |f| %>
<%# ... %>
<% end %>
<% else %>
<%# ... %>
<% end %>
Pronto! Ao usar o :remote => true, o Rails irá criar um atributo data chamado
data-remote. A partir daí, um script chamado “Unobtrusive scripting adapter”, ou
adaptador para script não-intrusivo, irá observar o evento submit do formulário,
capturá-lo e enviar todos os dados via AJAX, ao invés da forma tradicional.
Esse adaptador está intimamente ligado ao jQuery, mas existem outras imple-
mentações dele, fazendo essa ligação a outros frameworks, como o prototype.js
(http://prototypejs.org/).
267
12.5. jQuery e Rails: fazer requisições AJAX ficou muito fácil
Casa do Código
Se você ainda desejar usar outro framework de sua preferência, não é difícil
criar o seu próprio, basta remover a entrada gem 'jquery-rails' do Gemfile, que
é quem habilita o jQuery no projeto.
O adaptador também disponibiliza para nós alguns eventos para que possa-
mos registrar callbacks e fazer alguma customização de comportamento. Vamos
usar esses eventos para travar o botão do formulário durante o envio da requisi-
ção (ajax:beforeSend) e sinalizar sucesso (ajax:success) ou erro (ajax:error)
ao usuário. A lista completa de eventos é:
• ajax:before - Antes de preparar a requisição AJAX (ou seja, antes de capturar
os campos do formulário e preparar as opções de envio da requisição);
• ajax:beforeSend - Chamado antes de enviar a requisição, porém com tudo
já pronto para envio;
• ajax:success - Depois do término da requisição e a resposta foi bem suce-
dida;
• ajax:error - Depois do término da requisição e a resposta foi mal sucedida;
• ajax:complete - Chamado depois do término da requisição, não importando
o resultado.
Desenvolvendo JavaScript manualmente
Se você preferir, é possível não usar o adaptador do Rails e fazer tudo “na
mão”. O que temos, por fim, é a própria biblioteca jQuery, então podemos
usá-la da forma que quisermos.
Por fim, se você quiser, pode usar CoffeeScript, bastando adicionar a ex-
tensão .coffee e escrever o código equivalente.
Crie o arquivo app/assets/javascripts/room.js:
$(function() {
var $review = $('.review');
$review.bind('ajax:beforeSend', function() {
$(this).find('input').attr('disabled', true);
268
Casa do Código
Capítulo 12. Avaliação de quartos, relacionamentos muitos para muitos e. . .
});
$review.bind('ajax:error', function() {
replaceButton(this, 'icon-remove', '#B94A48');
});
$review.bind('ajax:success', function() {
replaceButton(this, 'icon-ok', '#468847');
});
function replaceButton(container, icon_class, color) {
$(container).find('input:submit').
replaceWith($('<i/>').
addClass(icon_class).
css('color', color));
};
});
Nesse código, registramos callbacks para três eventos: ajax:beforeSend (e não
ajax:before), ajax:error e ajax:success. No evento ajax:beforeSend, desati-
vamos todos os inputs, pois eles não serão mais operacionais (não podemos votar
mais de uma vez). Se tudo der certo, substituímos o botão por um ícone de sucesso
(“v”) e se der errado, por um “x”.
12.6
Média de avaliações usando agregações
Estamos quase terminando a funcionalidade de avaliações! A última coisa que va-
mos fazer antes de algumas mudanças visuais é exibir a nota média de avaliações do
quarto.
Para isso, vamos criar um método no modelo de avaliações para retornar o valor
da média de pontos, usando uma função de cálculo do ActiveRecord. As funções
de cálculo são:
• average - média;
• minimum - mínimo;
• maximum - máximo;
• count - contagem;
269
12.6. Média de avaliações usando agregações
Casa do Código
• sum - soma.
Nesses métodos, você deverá passar o nome do campo cujos valores serão calcu-
lados. Poderá passar também algumas opções interessantes. Veja alguns exemplos:
Review.average('points')
# SELECT AVG("reviews"."points") AS avg_id FROM "reviews"
# => #<BigDecimal:7f8b34bd8ed8,'0.21E1',18(45)>
Review.average('points').to_f
# => 2.1
# Funciona com escopos!
Room.first.reviews.average('points').to_f
# SELECT AVG("reviews"."points") AS avg_id
#
FROM "reviews" WHERE "reviews"."room_id" = 4
#
# => 2.25
Review.average(:points, :group => :room)
# SELECT AVG("reviews"."points") AS average_points,
#
room_id AS room_id FROM "reviews" GROUP BY room_id
#
# => {#<Room id: 4, ...>=>#<BigDecimal:7f8b349ab318,'0.225E1',18(45)>,
#
#<Room id: 5, ...>=>#<BigDecimal:7f8b349aa7b0,'0.2E1',9(45)>}
Review.maximum(:points)
# SELECT MAX("reviews"."points") AS max_id FROM "reviews"
# => 5
Review.minimum(:points)
# SELECT MIN("reviews"."points") AS min_id FROM "reviews"
# => 1
Review.count
# SELECT COUNT(*) FROM "reviews"
# => 10
Uma grande vantagem desses métodos de cálculo é que, como você pôde obser-
var, eles são todos feitos via SQL e portanto podem ser beneficiados pelos índices do
banco de dados e não há o custo de alocar um objeto na memória por cada registro.
270
Casa do Código
Capítulo 12. Avaliação de quartos, relacionamentos muitos para muitos e. . .
Usando o método .average do ActiveRecord, vamos calcular o número de es-
trelas a partir de uma coleção de avaliações. Usando o método #round de números,
podemos arrendondar as estrelas para um número inteiro. Juntando esses dois méto-
dos, vamos criar um método de classe no modelo Review (app/models/review.rb):
eles também são aplicados a escopos!
class Review < ActiveRecord::Base
# ...
def self.stars
(average(:points) || 0).round
end
end
Veja exemplos de uso:
Room.first.reviews.stars
# => 2
Review.stars
# => 2
Vamos
então
usar
esse
método
em
nosso
presenter para quartos
(app/presenters/room_presenter.rb),
criando
os
métodos
#stars e
total_reviews:
class RoomPresenter
# ...
def review_points
Review::POINTS
end
def stars
@room.reviews.stars
end
def total_reviews
@room.reviews.size
end
271
12.6. Média de avaliações usando agregações
Casa do Código
# ...
end
Dica: métodos de contagem
Uma vantagem de escopos acaba se tornando uma desvantagem: eles se
comportam e se parecem muito como Arrays. Arrays possuem três mé-
todos que contam o número de objetos: #length, #count e #size. Todos
eles funcionam da mesma maneira.
Porém, isso não se reflete em escopos e modelos ActiveRecord. Os três
métodos existem, mas com comportamentos diferentes:
• #length - É o mesmo do Array, portanto faz com que o
ActiveRecord busque todos os objetos no banco, instancie-os e
depois faz a contagem;
• #count - Conta quantos objetos existem no banco de dados, fa-
zendo uma consulta SQL (vimos agora pouco: faz parte dos méto-
dos de cálculo) ou usando o counter cache;
• #size - Chama a contagem pelo método #length caso os obje-
tos tenham sido carregados, caso contrário, conta via o método
#count.
Dessa forma, sempre que for fazer contagem de objetos, prefira usar o
#size!
Vamos
mostrar
então
no
template
de
avaliações
(app/views/rooms/_review.html.erb) esses números:
<section class="review">
<% if room.show_form? %>
<%= render :partial => 'review_form', :locals => {:room => room} %>
<% else %>
<%= t '.stats', :average => room.stars,
:max => room.review_points.max,
:count => room.total_reviews %>
272
Casa do Código
Capítulo 12. Avaliação de quartos, relacionamentos muitos para muitos e. . .
<% end %>
</section>
E no arquivo de I18n (config/locales/pt-BR.yml), usamos uma funcionali-
dade para plurais:
pt-BR:
# ...
rooms:
#...
review:
login_to_review: 'Faça o login para avaliar quartos'
stats:
zero: 'Não há avaliações'
one: '%{average}/%{max} (1 avaliação)'
other: '%{average}/%{max} (%{count} avaliações)'
O que acontece é que, quando usamos a chave :count na tradução, podemos
criar mensagens diferenciadas para cada valor de :count: zero para zero, one para
um e other para o restante, assim não precisamos nos preocupar em fazer regras para
cada valor.
273


Casa do Código
Capítulo 12. Avaliação de quartos, relacionamentos muitos para muitos e. . .
Figura 12.6: TV5.org: Site não preparado para alta densidade
Veja a diferença entre a qualidade da fonte, que é um elemento facilmente esca-
lonável e as imagens. Quando elas são ampliadas, muitos defeitos visuais ocorrem.
Compare com o exemplo do site CSS Tricks (http://www.css-tricks.com), que é oti-
mizado para “Retina Display":
275

Casa do Código
Capítulo 12. Avaliação de quartos, relacionamentos muitos para muitos e. . .
@font-face {
font-family: "FontAwesome";
src: url(font-path('fontawesome-webfont.eot'));
src: url(font-path('fontawesome-webfont.eot') + '?#iefix')
format('eot'),
url(font-path('fontawesome-webfont.woff')) format('woff'),
url(font-path('fontawesome-webfont.ttf')) format('truetype'),
url(font-path('fontawesome-webfont.svg') + '#FontAwesome')
format('svg');
font-weight: normal;
font-style: normal;
}
// O restante é o mesmo...
Temos que fazer essa alteração para que o Assets Pipeline gere a rota correta
para a fonte, através do font-path. Como o font-path é uma diretiva SCSS e é
pré-processada para gerar o CSS final, renomeamos o arquivo.
A pasta vendor/assets/fonts não está no caminho de pesquisa de as-
sets do Assets Pipeline.
Para adicionar, basta adicionar a seguinte linha do
config/application.rb:
module Colchonet
class Application < Rails::Application
# ...
config.assets.paths << Rails.root.join("vendor", "assets", "fonts") end
end
Por
fim,
adicione
a
'font-awesome'
no
manifesto
CSS
(app/assets/stylesheets/application.css):
/*
* ...
*= require_self
*= require_tree .
*= require 'font-awesome'
*/
Pronto, temos a Font Awesome instalada e pronta para uso!
277
12.8. Eu vejo estrelas - usando CSS e JavaScript para melhorar as avaliações
Casa do Código
Como instalar o Font Awesome sem o Assets Pipeline?
Se você não quiser usar o Assets Pipeline para as fontes, basta copiar
as fontes para public/fonts, copiar o CSS para public/stylesheets
e
acertar
a
url para /fonts/fontawesome-webfont.eot e o
mesmo para as outras entradas.
Não há necessidade de alterar o
config/application.rb.
12.8
Eu vejo estrelas - usando CSS e JavaScript para
melhorar as avaliações
Com back-end e o front-end, vamos colocar a “Font Awesome” para uso: vamos usar
estrelas para mostrar as pontuações de 1 a 5 na listagem. Para isso, vamos mudar um
pouco o template, o CSS e o I18n.
Comecemos pelo template de avaliações (app/views/room/_review.html.erb):
<section class="review">
<% if room.show_form? %>
<%= render :partial => 'review_form', :locals => {:room => room} %>
<% else %>
<% room.stars.times do %>
<span class="star filled_star"></span>
<% end %>
<% (room.review_points.max - room.stars).times do %>
<span class="star empty_star"></span>
<% end %>
<%= t '.stats', :count => room.total_reviews %>
<% end %>
</section>
Primeiro, criamos o número de estrelas preenchidas (filled_star) e depois cri-
amos o número de estrelas não preenchidas (subtraindo o número de estrelas do
quarto do total de 5). O resultado é o seguinte:
<section class="review">
<span class="star filled_star"></span>
278
Casa do Código
Capítulo 12. Avaliação de quartos, relacionamentos muitos para muitos e. . .
<span class="star filled_star"></span>
<span class="star filled_star"></span>
<span class="star empty_star"></span>
<span class="star empty_star"></span>
(27 avaliações)
</section>
Atualizamos também o I18n (config/locales/pt-BR.yml) para alterar a men-
sagem:
pt-BR:
rooms:
review:
login_to_review: 'Faça o login para avaliar quartos'
stats:
one: '(1 avaliação)'
other: '(%{count} avaliações)'
E, por fim, o CSS (app/assets/stylesheets/room.css.scss):
.review .star {
font-family: "FontAwesome";
font-size: 18px;
}
.review .filled_star {
color: gold;
text-shadow: 1px 1px #999;
&:before { content: "\f005"; }
}
.review .empty_star {
color: #aaa;
&:before { content: "\f006"; }
}
Usamos o :before para adicionar um caractere especial da “Font Awesome” an-
tes das estrelas.
Pronto! Depois dessas alterações, temos a listagem com estrelas:
279

Casa do Código
Capítulo 12. Avaliação de quartos, relacionamentos muitos para muitos e. . .
.review .icon-ok,
.review .icon-remove {
position: absolute;
right: 0;
}
As próximas 2 regras são para estilizar a estrela de acordo com o novo HTML do
formulário, que veremos em seguida.
.review label i {
font-size: 18px;
text-shadow: 1px 1px #999;
cursor:pointer;
color: #ccc;
}
.review label.toggled i {
color: gold;
}
A última regra é para sumir com os botões radio e o “enviar”.
.review form > input {
display:none;
}
O novo template do formulário (app/views/rooms/_review_form.html.erb)
deve ficar assim:
<% if room.can_review? %>
<%= form_for room.review_route, :remote => true do |f| %>
<% room.review_points.each do |point| %>
<%= f.radio_button :points, point
%>
<%= f.label :points, :value => point do %>
<i class="icon-star"></i>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
<% else %>
<span class="login_required">
281
12.8. Eu vejo estrelas - usando CSS e JavaScript para melhorar as avaliações
Casa do Código
<%= t('.login_to_review') %>
</span>
<% end %>
A diferença está na maneira que construímos o label. Ao invés de colocarmos o
valor, vamos simplesmente desenhar uma estrela, de acordo com o “Font-Awesome”.
Para encerrar essa funcionalidade, adicionamos o código JavaScript para alterar
a cor das estrelas quando o usuário passar o mouse sobre elas, e com isso mostrar ao
usuário que ele está de fato alterando a sua avaliação.
Para
isso,
adicione
o
seguinte
código
JavaScript
no
arquivo
app/assets/javascripts/rooms.js:
$(function() {
// ...
function highlightStars(elem) {
elem.parent().children('label').removeClass('toggled');
elem.addClass('toggled').prevAll('label').addClass('toggled');
}
highlightStars($('.review input:checked + label'));
var $stars = $('.review input:enabled ~ label');
$stars.on('mouseenter', function() {
highlightStars($(this));
});
$stars.on('mouseleave', function() {
highlightStars($('.review input:checked + label'));
});
$('.review input').on('change', function() {
$stars.off('mouseenter').off('mouseleave').off('click');
$(this).parent('form').submit();
});
});
A primeira função, highlightStars, é a responsável por adicionar e remover o
destaque das estrelas. Baseado no elemento passado, primeiro remove-se o destaque
282

Casa do Código
Capítulo 12. Avaliação de quartos, relacionamentos muitos para muitos e. . .
de todas as estrelas (classe CSS toggled) e em seguida adiciona-se a mesma classe
apenas ao elemento em destaque e os anteriores.
Usando essa função, ativamos as estrelas previamente selecionadas pelo usuário.
Isso é importante para mostrar ao usuário que a ação dele teve efeito. Este seletor
executa no momento que a página é carregada.
A primeira parte, input:checked irá retornar todos os inputs que estão mar-
cados (válido somente para check boxes e radio buttons). Usando o +, retornamos
apenas o primeiro objeto imediatamente ao redor deste input. Isso significa que o
seletor irá retornar o label imediatamente ao lado de um input que está selecionado.
Veja a seguir o resultado:
Figura 12.9: Formulário de avaliação enviado
var $stars = $('.review input:enabled ~ label');
Na linha anterior, selecionamos todos os labels (as estrelas) em inputs que estão
habilitados. O comportamento é parecido com o seletor input:checked + label,
porém dessa vez procuramos todos que estão habilitados. O objetivo é que só iremos
dar destaque quando o formulário estiver habilitado, ou seja, apenas antes de ser
enviado.
$stars.on('mouseenter', function() {
highlightStars($(this));
});
$stars.on('mouseleave', function() {
highlightStars($('.review input:checked + label'));
});
283
12.9. Encerrando
Casa do Código
Esses três blocos são responsáveis pelos eventos do mouse. O primeiro evento,
o mouseenter, ocorre quando o usuário posiciona o mouse em cima de uma estrela.
Nesse momento, vamos destacar a estrela clicada e as anteriores.
No evento mouseleave, vamos voltar ao estado inicial do formulário, ou seja, se
já havia uma estrela marcada, tornamos ela marcada novamente.
Finalmente, temos o seguinte bloco:
$('.review input').on('change', function() {
$stars.off('mouseenter').off('mouseleave').off('click');
$(this).parent('form').submit();
});
O evento change é executado após o click em uma estrela. Nesse momento, des-
ligamos todos os Event handlers que criamos, ou seja, desabilitamos a animação de
estrelas e ativamos o evento de submit do formulário, fazendo o envio do formulário
via AJAX.
12.9
Encerrando
Parabéns! Você perseverou até o fim das funcionalidades principais do Colcho.net!
Foi um caminho longo e difícil, especialmente nesse capítulo. Mas você aprendeu a
fazer muita coisa:
• Associações muitos-para-muitos;
• Remover objetos com segurança;
• Usar fontes e webfonts para deixar seu design bonito e escalonável;
• Customizar o Assets Pipeline;
• Novas funcionalidades do ActiveRecord: find_or_initialize_by_... e
find_or_create_by_...;
• Organização de controles complexos;
• Organização de rotas complexas;
• Usar o ActiveRecord para fazer contas da melhor maneira possível;
• Opções avançadas de I18n;
284
Casa do Código
Capítulo 12. Avaliação de quartos, relacionamentos muitos para muitos e. . .
• Extração e organização de templates complexos;
• Uso de presenters para simplificar templates complexos;
• Usar jQuery e AJAX;
Depois de tudo isso, você já está preparado para criar sua própria aplicação do
zero. Os conhecimentos vistos nesse capítulo já cobrem muitas funcionalidades usa-
das em aplicações de verdade, com bastante complexidade. É lógico que livro ne-
nhum irá substituir a experiência de construir as suas próprias aplicações, mas agora
você já tem a base para colocar suas ideias em prática.
No próximo capítulo, vamos colocar algumas funcionalidades muito úteis, co-
muns na maioria das aplicações web e que serão de grande facilidade de implementar.
Parabéns, você está chegando lá!
285
Capítulo 13
Polindo o Colcho.net
Não importa se você vai devagar, o que importa é você nunca parar
– Confúcio
Este é o último capítulo em que vamos desenvolver o Colcho.net! Você está che-
gando ao final, e a aplicação já está bem funcional. Nós vamos fazer algumas me-
lhorias na aplicação em geral, desenvolvendo as seguintes funcionalidades: busca
textual, URL slugs usando a gem friendly_id, paginação usando a gem kaminari,
upload de fotos usando a gem carrierwave e, finalmente, colocar o Colcho.net on-
line!
13.1
Faça buscas textuais apenas com o Rails
Até o momento, os usuários não conseguem pesquisar por nenhuma informação em
nossa aplicação. Precisamos então permitir que o usuário pesquise informações no
Colcho.net, melhorando a usabilidade da aplicação.
13.1. Faça buscas textuais apenas com o Rails
Casa do Código
No Colcho.net há três informações importantes pelas quais um usuário pode
pesquisar: localidade, título e a descrição de um quarto. Precisamos de uma busca
que nos permita pesquisar nesses três “textos”, ou seja, precisamos de uma busca
textual.
Busca textual é um assunto bastante complexo e existem vários livros sobre o
assunto, portanto vamos cobrir o que é possível criar com apenas o uso do Rails.
A funcionalidade é a seguinte: vamos criar um campo de texto próximo ao título
de listagem de quartos. Vamos pegar o conteúdo da caixa de texto e procurar nos
campos mencionados anteriormente. Dos resultados encontrados, vamos marcar
visualmente para o usuário facilmente identificar o trecho que está procurando.
Primeiro, vamos construir a funcionalidade no back-end. Para isso, vamos criar
um método chamado .search no modelo de quartos (app/models/room.rb):
class Room < ActiveRecord::Base
# ...
def self.search(query)
if query.present?
where(['location LIKE :query OR
title LIKE :query OR
description LIKE :query', :query => "%#{query}%"])
else
scoped
end
end
# ..
end
Neste método, se a busca estiver presente, fazemos o filtro usando o opera-
dor LIKE do SQL para fazer a busca nos campos mencionados: location, title
e description. Caso contrário, vamos retornar o escopo atual:
Room.search('Sao')
# SELECT "rooms".* FROM "rooms" WHERE (location LIKE '%Sao%' OR
# title LIKE '%Sao%' OR
# description LIKE '%Sao%')
# => [#<Room id: 4, ... >]
Room.search('')
# SELECT "rooms".* FROM "rooms"
# => [#<Room id: 4, ... >]
288
Casa do Código
Capítulo 13. Polindo o Colcho.net
Por que usar scoped e não all?
Seria natural pensar em usar o método .all no caso da busca não es-
tiver presente, pois retorna todos os objetos. Porém, se este método for
usado em conjunto com outros escopos, o que é bastante comum, o .all
iria cancelar todos os outros escopos e o comportamento final seria bem
inesperado. O .scoped faz esse papel de manter o escopo.
Como o modelo já faz todo o trabalho pesado, no controle de quartos
(app/controllers/rooms_controller.rb) vamos apenas chamar o método que
acabamos de criar. A ação index fica da seguinte forma:
class RoomsController < ApplicationController
# ...
def index
@search_query = params[:q]
rooms = Room.search(@search_query)
@rooms = rooms.most_recent.map do |room|
RoomPresenter.new(room, self, false)
end
end
# ...
end
Vamos diferenciar o template de índice (app/views/rooms/index.html.erb)
apenas para mostrar um título diferente caso estejamos exibindo resultado de bus-
cas. Para isso, vamos verificar a presença de um termo de busca (search_query).
Vamos incluir no topo do template o formulário de busca. O resultado é:
<%= render 'search' %>
<h1>
<% if @search_query.present? %>
<%= t '.search_results' %>
<% else %>
<%= t '.title' %>
289
13.1. Faça buscas textuais apenas com o Rails
Casa do Código
<% end %>
</h1>
<%= render @rooms %>
Na partial contendo o formulário (app/views/rooms/_search.html.erb) não
será possível usar o helper form_for, pois não estamos criando um formulário de
modelo, mas sim um formulário qualquer. O Rails possui também helpers para esta
situação:
<%= form_tag rooms_path, :method => :get, :class => 'search' do %>
<%= text_field_tag :q, @search_query,
:placeholder => t('.search_for') %>
<% end %>
O que fazemos é simplesmente enviar à ação index do controle de quartos o
parâmetro q, que possui o conteúdo para filtrar a listagem. Observe que temos que
declarar o método HTTP para GET para que o roteador não nos envie para a ação
create.
Atualizamos as chaves I18n (config/locales/pt-BR.yml), para adicionar as
mensagens relativas às buscas:
rooms:
index:
title: 'Quartos disponíveis'
search_results: 'Resultados da busca'
search:
search_for: 'Buscar por...'
Em seguida, usamos o helper highlight do Rails, para destacar os resultados da
busca, na partial de quarto. Alteraremos o título, a descrição e a localidade do quarto
(app/views/rooms/_room.html.erb):
<article>
...
<h2>
<%= link_to highlight(room.title, @search_query), room.route %>
</h2>
...
<p>
<span class="location">
290
Casa do Código
Capítulo 13. Polindo o Colcho.net
<%= link_to highlight(room.location, @search_query),
"https://maps.google.com/?q=#{room.location}",
:target => :blank %>
</span>
</p>
<p><%= highlight(room.description, @search_query) %> </p>
...
</article>
Por fim, adicionamos o estilo CSS para o campo de busca.
Usa-
mos
a
“Font
Awesome”
para
adicionar
o
clássico
ícone
da
lupa
(app/assets/stylesheets/room.css.scss), e vamos estilizar os campos des-
tacados pela função highlight:
#content .search {
float: right;
margin: 0;
position: relative;
&:after {
padding: 5px;
font-size: 18px;
font-family: 'FontAwesome';
position: absolute;
content: "\f002";
color: #bbb;
top: 0;
right: 0;
}
}
.highlight {
font-size: inherit;
font-weight: inherit;
background-color: gold;
}
Com essas alterações, ao realizarmos uma pesquisa, seu resultado é exibido com
o destaque no termo procurado.
291

Casa do Código
Capítulo 13. Polindo o Colcho.net
chamado #to_param:
room = Room.first
# => #<Room id: 4, ... >
room.to_param
# => "4"
Uma alteração simples para transformar as URLs é sobrescrever o método
#to_param para fazer o que quisermos e atualizar os controles de forma a usar a
busca, ou seja, ao invés de usar .find no modelo, usamos algum outro método apro-
priado.
Fazer isso não é difícil. Porém, há vários detalhes que temos que tomar cui-
dado para implementar essa funcionalidade. Primeiro, temos que tomar cuidado
com slugs antigas, ou seja, quando o usuário alterar o modelo de forma que o slug
seja alterado, precisamos guardar o antigo slug para que links antigos não quebrem,
sejam eles seus ou de outros sites. Segundo, o que fazer com conflitos de slugs? Mais
ainda, como lidar com detalhes difíceis de prever, tal como transliteração de carac-
teres, ou seja, transformar símbolos como “ø” ou “ã” em “oe” e “a"?
Portanto, ao invés de ter que nos preocupar com estas situações (e outras não
pensadas também), podemos nos aproveitar da experiência de outros desenvolve-
dores.
Para resolver o problema de slugs, vamos usar a gem friendly_id, feita pelo
Norman Clarke, desenvolvedor que atua na comunidade Ruby e Rails há anos.
Para instalar esta gem, o primeiro passo é declará-la no Gemfile:
gem 'friendly_id'
E pedir para o Bundler instalar as dependências:
$ bundle
...
Installing friendly_id (4.0.8)
...
Em seguida, a gem precisa que você crie um campo extra, chamado slug, na
tabela quarto, que é a informação que teremos a URL amigável. Para isso, vamos
criar uma nova migração:
293
13.2. URLs mais amigáveis através de slugs
Casa do Código
$ rails g migration add_slugs_to_rooms slug:string:index
invoke active_record
create
db/migrate/20120811170119_add_slugs_to_rooms.rb
Precisamos adicionar um índice de unicidade. Por isso, alteraremos a migração
gerada para adicionar essa restrição:
class AddSlugsToRooms < ActiveRecord::Migration
def change
add_column :rooms, :slug, :string
add_index :rooms, :slug, :unique => true
end
end
Vamos também ter que criar uma tabela para guardar os slugs antigos. O
friendly_id já faz isso para nós, basta executar:
$ rails generate friendly_id
create db/migrate/20120811171412_create_friendly_id_slugs.rb
Executamos as migrações:
$ rake db:migrate
Por fim, alteraremos o modelo quarto (app/models/room.rb) para usar a fun-
cionalidade de slugs, estendendo o módulo FriendlyId, adicionando uma restrição
de presença de slug e configurando o módulo friendly_id para usar as funcionali-
dades :slugged, que é o modo padrão de operação da gem e a :history, para gravar
o histórico de slugs:
class Room < ActiveRecord::Base
extend FriendlyId
#...
validates_presence_of :title
validates_presence_of :slug
friendly_id :title, :use => [:slugged, :history]
#...
end
294
Casa do Código
Capítulo 13. Polindo o Colcho.net
Pronto! Você já tem a funcionalidade de URL slugs. Para testar, crie um novo
quarto e veja a URL como fica (lembre-se de reiniciar o servidor do Rails, caso já
não tenha feito).
Atualizando quartos já cadastrados
O FriendlyId irá funcionar com quartos sem slug, ou seja, irá usar o ID
do modelo caso não seja possível usar o slug. Porém, para termos consis-
tência, podemos atualizar os modelos para usarem slugs, basta ativar os
callbacks de save que o FriendlyId irá fazer o resto:
Room.find_each(&:save)
O find_each é uma alternativa ao all para fazer alterações em lotes, caso
você tenha muitos registros no banco de dados.
13.3
Paginação de dados de forma descomplicada
Imagine o Colcho.net no futuro, explodindo de sucesso e com mais de 500 quartos
cadastrados. A listagem ficará lenta, pois são muitos objetos para se exibir e calcular
a média da pontuação. Para essa situação, é normal paginar o resultado das buscas,
de modo que os resultados menos interessantes fiquem ao final.
Essa funcionalidade não é complicada de se criar usando escopos. Mas a gem
kaminari torna ainda mais fácil o nosso trabalho.
Vamos primeiro instalar a gem. Como de praxe, basta colocá-la no Gemfile e
fazer o bundle:
gem 'kaminari'
$ bundle
...
Installing kaminari (0.13.0)
...
O kaminari insere, através de meta-programação, alguns métodos no
ActiveRecord para fazer a paginação, porém o nosso uso de presenters atrapalhou
isso, já que estamos usando uma Array deles.
295
13.3. Paginação de dados de forma descomplicada
Casa do Código
Precisamos criar um outro presenter para que possamos chamar os métodos de
paginação do kaminari. Outra coisa que precisamos fazer é tornar essa nova classe
uma espécie de coleção, para que o render :collection => @rooms continue fun-
cionando.
Infelizmente o Rails não definiu uma interface para este tipo de interação. Por-
tanto precisamos implementar o método #to_ary, que faz uma conversão do objeto
atual para Array quando isso for necessário (via conversão implícita). Veja o exemplo
a seguir:
class ImplicitArray
def to_ary
[1,2,3]
end
end
# => nil
[:a] + ImplicitArray.new
# => [:a, 1, 2, 3]
Repare que ao juntarmos o Array de :a, com a instância de ImplicitArray, o
resultado foi um novo Array com a junção de :a e o resultado do método to_ary de
ImplicitArray.
Com isso em mente, vamos criar o presenter RoomCollectionPresenter
(app/presenters/room_collection_presenter.rb), que irá delegar os métodos
de paginação para a coleção ActiveRecord e criar presenters de quarto quando o
collection presenter for convertido para Array.
class RoomCollectionPresenter
delegate :current_page, :num_pages, :limit_value,
:to => :@rooms
def initialize(rooms, context)
@rooms = rooms
@context = context
end
def to_ary
@rooms.map do |room|
RoomPresenter.new(room, @context, false)
end
end
end
296
Casa do Código
Capítulo 13. Polindo o Colcho.net
Reuso dos presenters
Este é o tipo de código que sempre precisamos, portanto você pode apli-
car em seus próprios projetos. Se preferir usar um presenter funcional e
pronto, vale a pena checar a gem simple_presenter, do Nando Vieira:
https://github.com/fnando/simple_presenter
Alteramos
a
ação
index
do
RoomsController
(app/controllers/rooms_controller.rb) para que ela fique da seguinte maneira:
class RoomsController < ApplicationController
PER_PAGE = 10
# ...
def index
@search_query = params[:q]
rooms = Room.search(@search_query).
page(params[:page]).
per(PER_PAGE)
@rooms = RoomCollectionPresenter.new(rooms.most_recent, self)
end
end
Aplicamos a paginação no modelo de quarto, via o uso dos métodos .page, que
define a página a ser buscada e o método .per, que define a quantidade de objetos por
página. Na última linha da ação, embrulhamos a coleção no presenter que acabamos
de criar.
Com isso pronto, para usar a paginação do kaminari, basta colocar o helper
paginate no template da ação index (app/views/rooms/index.html.erb):
...
<%= render @rooms %>
<%= paginate @rooms %>
Isso é tudo que precisamos para fazer a paginação funcionar.
Vamos melhorar um pouco mais, criando um novo conjunto de chaves I18n para
o kaminari, no arquivo config/locales/pt-BR.pagination.yml:
297

Casa do Código
Capítulo 13. Polindo o Colcho.net
13.4
Upload de fotos de forma simples
Imagine você alugando um quarto ou um apartamento sem conseguir ver fotos desse
lugar? Difícil, não? Para isso, precisamos permitir que fotos sejam associadas ao
quarto, através de upload de arquivos de imagens.
Para fazer o upload de fotos de quartos, vamos usar uma gem chamada
carrierwave. Com ela é possível criar thumbnails de fotos automaticamente e até
enviar essa foto para ser servida de serviços como o S3, da Amazon (http://aws.
amazon.com/s3/) ou Cloud Files, da Rackspace (http://www.rackspace.com/cloud/
public/files/), serviços bastante interessantes para grandes sites.
Para instalá-la, a receita é parecida: colocar a gem no Gemfile e fazer o bundle.
A diferença é que temos que declarar a dependência à gem rmagick manualmente.
Isso deve-se ao fato de que o carrierwave funciona com outras gems também:
gem 'carrierwave'
gem 'rmagick'
$ bundle
...
Installing carrierwave (0.6.2)
Installing rmagick (2.13.1) with native extensions
...
O funcionamento do carrierwave é o seguinte: primeiro, é necessário criar um
uploader, uma classe com a descrição das transformações que a imagem vai passar
(criação de thumbnails, por exemplo), onde guardar o arquivo enviado pelo usuário e
outras coisas. Para facilitar esse trabalho, o carrierwave instala um gerador. Então,
vamos criar o uploader para fotos de quartos:
$ rails g uploader Picture
create app/uploaders/picture_uploader.rb
O arquivo gerado possui diversos comentários sobre a forma de utilizar o uploa-
der e os parâmetros configuráveis. O resultado, das linhas não comentadas deve ser
o seguinte:
# encoding: utf-8
class PictureUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
299
13.4. Upload de fotos de forma simples
Casa do Código
include Sprockets::Helpers::RailsHelper
storage :file
# Diretório onde os arquivos serão armazenados
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
# Redimensiona a imagem para ficar no tamanho de
# no máximo 500x500, mantendo o aspecto e cortando
# a imagem, se necessário.
process :resize_to_fill => [500, 500]
# Dimensões do thumbnail
version :thumb do
process :resize_to_fill => [100, 100]
end
# Informa os formatos permitidos
def extension_white_list
%w(jpg jpeg gif png)
end
end
Carrierwave não é só para imagens
Apesar de conter muitas facilidades para o upload de imagens, ele pode
ser usado com qualquer outro formato de arquivo, desde que você desa-
tive as funcionalidades específicas para imagens, como processamento.
Uma vez criado o uploader, precisamos criar uma coluna no banco de dados para
que o carrierwave guarde o nome do arquivo e saiba recuperá-lo na hora de exibir
a foto. Para isso, criemos e executemos a migração a seguir:
$ rails g migration add_picture_to_rooms picture
create
db/migrate/20120813014045_add_picture_to_rooms.rb
300
Casa do Código
Capítulo 13. Polindo o Colcho.net
$ rake db:migrate
== AddPictureToRooms: migrating ==============
-- add_column(:rooms, :picture, :string)
-> 0.0012s
== AddPictureToRooms: migrated (0.0013s) =====
Em seguida, precisamos associar o uploader ao modelo quarto. Isso é feito atra-
vés da class macro mount, método que o carrierwave adiciona ao ActiveRecord.
Portanto, no modelo quarto (app/models/room.rb), basta adicionar o seguinte có-
digo:
class Room < ActiveRecord::Base
# Adicione :picture na lista de atributos:
attr_accessible :description, :location, :title, :picture
# ...
mount_uploader :picture, PictureUploader
friendly_id :title, :use => [:slugged, :history]
# ...
end
Essa class macro irá tornar o campo picture do modelo quarto em um
PictureUploader, ao invés de uma simples string. A partir daí, basta colocarmos
mais um campo no formulário de quartos, para que o arquivo seja informado. Então,
no app/views/rooms/_form.html.erb:
...
<%= form_for(@room) do |f| %>
...
<p>
<%= f.label :picture %>
<%= f.file_field :picture %>
<%= error_tag @room, :picture %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
301
13.4. Upload de fotos de forma simples
Casa do Código
Adicione também a chave para traduzir o novo campo no arquivo de I18n
(config/locales/pt-BR.yml):
pt-BR:
#...
activerecord:
#...
attributes:
# ...
room:
description: Descrição
location: Localização
title: Título
picture: Foto
Por fim, vamos alterar o presenter, HTML e o CSS para exibir a ima-
gem.
Altere o presenter de quartos para incluir os métodos que verificam
se há uma imagem, e também que devolve o thumbnail e a própria imagem
(app/presenters/room_presenter.rb):
class RoomPresenter
# ...
def picture_url
@room.picture_url
end
def thumb_url
@room.picture.thumb.url
end
def has_picture?
@room.picture?
end
end
O template de quartos (app/views/rooms/_room.html.erb) fica assim:
<article class="room">
...
<%= link_to(image_tag(room.thumb_url), room.picture_url)
if room.has_picture? %>
302

Casa do Código
Capítulo 13. Polindo o Colcho.net
<p><%= highlight(room.description, @search_query) %> </p>
...
</article>
O CSS para a imagem de quartos (app/assets/stylesheets/room.css.scss)
é:
.room img {
float: left;
margin-right: 10px;
}
Et voilà! Os quartos agora possuem foto:
Figura 13.3: Quarto com uma foto
13.5
Coloque a aplicação no ar com o Heroku
Antigamente, colocar um aplicativo web no ar era complicado e exigia bastante co-
nhecimento de configuração de servidor. Hoje em dia, contudo, existem serviços que
oferecem hospedagem de aplicativos sem a noção de termos um servidor. Platform
as a service - PaaS, ou “Plataforma como serviço”, é mais uma das facetas de Cloud
Computing, na qual um usuário contrata uma plataforma e poder de processamento,
ao invés de contratar servidores, sejam de metal ou virtual.
303
13.5. Coloque a aplicação no ar com o Heroku
Casa do Código
Um desses serviços é o Heroku (www.heroku.com). Hoje em dia, não existe
forma mais fácil de colocar uma aplicação Rails no ar do que usar o Heroku. Por
isso, vamos usá-lo: é de graça para pouco processamento de requisição (1 requisição
por segundo), que é o bastante para nossa aplicação.
Preparando a aplicação para o Heroku
Porém, precisamos fazer algumas alterações no aplicativo para fazer o Heroku
funcionar: o Heroku funciona com PostgreSQL e estamos usando o SQLite3. Por esse
motivo, é necessário colocar a gem pg no Gemfile, responsável pela conectividade
a bancos PostgreSQL e alterar uma consulta SQL. Primeiro, vamos a alteração do
Gemfile e execute bundle em seguida:
# gem 'sqlite3'
gem 'pg'
$ bundle
...
Installing pg (0.14.0) with native extensions
...
Vamos agora alterar a consulta SQL problemática: no PostgreSQL, a consulta
LIKE é sensível a maiúsculas e minúsculas, enquanto a ILIKE não. Vamos alterar o
método Room.search (app/models/room.rb) para usar a nova consulta:
class Room < ActiveRecord::Base
# ...
def self.search(query)
if query.present?
where(['location ILIKE :query OR
title ILIKE :query OR
description ILIKE :query', :query => "%#{query}%"])
else
scoped
end
end
# ...
end
304
Casa do Código
Capítulo 13. Polindo o Colcho.net
Desenvolvimento usando PostgreSQL
Infelizmente esse comportamento do LIKE vs. ILIKE é incompatível no SQLite.
Por isso, pode ser interessante para você desenvolver diretamente no PostgreSQL.
Se você usa Linux, verifique os pacotes de sua distribuição. De preferência, ins-
tale uma versão igual ou superior à 9. No OS X, para usuários de 10.7 (Lion) ou
superior, você já tem o PostgreSQL. Para Windows, você pode usar o instalador do
site oficial: http://postgresql.org.
Em
seguida,
altere
a
parte
development e test do arquivo
config/database.yml para refletir as alterações. Não é necessário ter production,
ele será criado pelo próprio Heroku. Veja o exemplo de como deve ficar o arquivo:
development:
adapter: postgresql
database: colchonet_dev
host: localhost
username: vinibaggio
password:
pool: 5
timeout: 5000
test:
adapter: postgresql
database: colchonet_test
host: localhost
username: vinibaggio
password:
pool: 5
timeout: 5000
Por fim, execute:
$ rake db:create db:migrate
== CreateRooms: migrating =====================
-- create_table(:rooms)
...
== AddPictureToRooms: migrating ===============
-- add_column(:rooms, :picture, :string)
-> 0.0011s
== AddPictureToRooms: migrated (0.0012s) ======
305
13.5. Coloque a aplicação no ar com o Heroku
Casa do Código
Colocando a aplicação no ar
Para começar a usar o Heroku, é necessário instalar o git, criar uma conta no
Heroku e baixar o Heroku Tools. Uma vez com tudo instalado, basta ir na pasta do
projeto, e criar um repositório git e fazer um commit:
$ git init .
Initialized empty Git repository in /book/code/colchonet/.git/
$ git add .
$ git commit -m "Primeiro commit"
[master (root-commit) a2ed4be] Primeiro commit
106 files changed, 3169 insertions(+)
create mode 100644 .gitignore
create mode 100644 Gemfile
create mode 100644 Gemfile.lock
create mode 100644 README.rdoc
...
O que é git? Como instalar?
O git é um sistema de versionamento de código-fonte. Se você já
ouviu falar em CVS, SVN ou Mercurial, o git é parecido com todos
eles, com algumas outras funcionalidades bastante interessantes. Para
saber mais, veja o screencast “Começando com Git” http://colcho.net/
comecando-com-git. Nele você poderá saber como funciona o git e
como instalá-lo.
Uma vez com o commit criado, vamos usar o heroku tools para criar um repo-
sitório remoto chamado heroku e preparar o nosso novo site.
$ heroku login
Enter your Heroku credentials.
Email: [email protected]
Password (typing will be hidden):
Authentication successful.
$ heroku create
306
Casa do Código
Capítulo 13. Polindo o Colcho.net
Creating calm-badlands-8648... done, stack is cedar
http://calm-badlands-8648.herokuapp.com/ |
[email protected]:calm-badlands-8648.git
Git remote heroku added
Precisamos adicionar um add-on ao novo site para que possamos entregar os
e-mails de cadastro. Vamos usar o Mailgun, é de graça e possui integração simples
com o Heroku:
$ heroku addons:add mailgun:starter
Adding mailgun:starter on calm-badlands-8648... done, v8 (free)
Usèheroku addons:docs mailgun:starter` to view documentation.
Precisamos
atualizar
o
ambiente
production
(config/environments/production.rb), pois é neste ambiente que o Heroku
executa a nossa aplicação. Precisamos atualizar as configurações do ActionMailer
para usar a nova URL do site e as configurações do Mailgun (conforme vimos na
seção 9.2):
Colchonet::Application.configure do
#...
config.action_mailer.default_url_options = {
:host => "calm-badlands-8648.herokuapp.com"
}
config.action_mailer.smtp_settings = {
:port
=> ENV['MAILGUN_SMTP_PORT'],
:address
=> ENV['MAILGUN_SMTP_SERVER'],
:user_name
=> ENV['MAILGUN_SMTP_LOGIN'],
:password
=> ENV['MAILGUN_SMTP_PASSWORD'],
:domain
=> 'calm-badlands-8648.herokuapp.com',
:authentication => :plain,
}
config.action_mailer.delivery_method = :smtp
end
As configurações do Mailgun são colocadas como variáveis de ambiente, bas-
tante conveniente para não termos que gerenciar chaves de API. Em seguida, execute
o derradeiro comando:
307
13.5. Coloque a aplicação no ar com o Heroku
Casa do Código
$ git push heroku master
Counting objects: 154, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (140/140), done.
Writing objects: 100% (154/154), 940.50 KiB | 339 KiB/s, done.
Total 154 (delta 10), reused 0 (delta 0)
-----> Heroku receiving push
...
-----> Discovering process types
Procfile declares types
-> (none)
Default types for Ruby/Rails -> console, rake, web, worker
-----> Compiled slug size is 12.4MB
-----> Launching... done, v4
http://calm-badlands-8648.herokuapp.com deployed to Heroku
To [email protected]:calm-badlands-8648.git
* [new branch]
master -> master
Quase pronto... Por fim, temos que migrar o banco de dados recém criado:
$ heroku run rake db:migrate
Running `rake db:migrateàttached to terminal... up, run.1
Connecting to database specified by DATABASE_URL
Migrating to CreateRooms (20120610045608)
...
Depois que o comando terminar, basta acessar a URL que você recebeu e já pode
passar para sua família e amigos! Muito bom, não é mesmo? Parabéns! Você com-
pletou o Colcho.net!
308
Capítulo 14
Próximos passos
Agora, isso não é o fim. Nem sequer é o início do fim. Mas é, talvez, o fim do começo.
– Sir Winston Churchill
Agora que você já é familiar ao Rails, está na hora de fazer as suas próprias apli-
cações. Seria muita pretensão deste humilde livro te ensinar tudo que existe no ecos-
sistema Rails, portanto, depois da leitura deste livro, ainda é necessário buscar mais
material.
Lista de email
Se você quer tirar alguma dúvida sobre este livro, você pode se juntar à lista
de emails do livro em http://colcho.net/lista e poderá enviar sua pergunta. Os par-
ticipantes dela, inclusive o autor deste livro, tentarão te ajudar. Lembre-se de ser
educado!
Casa do Código
Conhecimentos de Rails
O primeiro lugar que você deve buscar para tirar dúvidas e entender o fun-
cionamento específico de algum componente do Rails são os RailsGuides (http:
//guides.rubyonrails.org). Nele você poderá buscar informações sobre os principais
componentes do Rails, tais como ActiveRecord, templates, etc.
Se sua dúvida for mais específica, talvez seja interessante consultar a documen-
tação da API, que fica em http://api.rubyonrails.org. A documentação é fácil de na-
vegar, bastando que você saiba o método que quer procurar.
Se você quiser aprender receitas de como resolver certos problemas, o Ryan Ba-
tes, bastante famoso na comunidade Rails, faz o RailsCasts (http://railscasts.com).
Cada screencast é uma receita de como resolver um problema, e o inglês que ele fala
é claro, sendo uma ótima maneira de aprender. Se você ainda não está confortável
em entender uma narração em inglês, pode ler os AsciiCasts (http://asciicasts.com/),
que é praticamente uma transcrição do RailsCasts.
Se você prefere livros, uma grande recomendação é o “Rails 3 Recipes”, do Chad
Fowler (http://colcho.net/rails-recipes). Nele você pode encontrar diversas receitas
de bolo de como resolver problemas encontrados no dia-a-dia. Se você quer se apro-
fundar no framework, não existe livro melhor do que o “Crafting Rails Applications”
(http://colcho.net/crafting-rails-apps). Neste livro, o José Valim, um dos principais
desenvolvedores do próprio framework, te guia para um mergulho de cabeça nas
profundidades do Rails. Pode ser um pouco difícil para iniciantes, mas é um livro
de altíssima qualidade.
Dominando o Ruby
Se você quer se tornar um desenvolvedor Ruby e Rails profissional, reco-
mendo aprofundar-se na linguagem, pois é de extrema importante um profissi-
onal conhecer bem as ferramentas que está trabalhando. Minha recomendação
para este fim é o “Eloquent Ruby”, do Russ Olsen (http://colcho.net/eloquent-ruby).
Outro livro recomendado é o “Ruby Programming Language” (http://colcho.net/
ruby-programming-lang), de autoria de David Flanagan e Yukihiro Matsumoto, o
próprio Matz.
Dominando testes
A comunidade Ruby e Rails em geral gosta bastante de testes unitários e argu-
mentam que esta prática melhora o design de código e ajuda a reduzir o número de
310
Casa do Código
Capítulo 14. Próximos passos
bugs. É interessante entender as ideias, por isso recomendo a leitura do “Test-Driven
Development”, do Kent Beck (http://colcho.net/tdd). Em seguida, é interessante ler
como TDD se aplica no Ruby, com RSpec e Cucumber, através do livro “The RSpec
Book” (http://colcho.net/rspec-book). Você pode também procurar o “Guia rápido
de RSpec”, do Nando Vieira (http://colcho.net/guia-rspec).
É difícil no começo, não se preocupe se tiver dificuldades, é normal. Um pouco
de perseverança e você poderá sentir os benefícios dessa prática. Há também um
livro específico para receitas de teste com Rails, o “Rails Test Prescriptions” (http:
//colcho.net/test-prescriptions).
Envolvimento na comunidade
A comunidade rubista brasileira é bastante ativa. Em São Paulo, por exemplo,
existe o GURU-SP, porém é possível encontrar outros grupos de Ruby e Rails pelo
Brasil e pelo mundo. Procure as listas de email, participe e fique atento aos encon-
tros, você pode aprender muito! Não deixe de participar também em outros eventos
de programação que existem no Brasil, pois exposição a tecnologias e novas ideias
sempre te ajudarão com qualquer linguagem ou framework.
Open source e leitura de código
A comunidade de Ruby e Rails é bastante envolvida em open source e se envolver
em um projeto é uma grande forma de, além de contribuir, aprender com outros
desenvolvedores. A leitura de código também é encorajada. Uma ótima maneira é
procurar sua gem favorita no GitHub (http://www.github.com) e navegar pelo código
fonte.
311
Índice Remissivo
Casa do Código
Índice Remissivo
save, 104
Convenção sobre Configuração, 113
to_proc, 47
cookies, 200
valid?, 104
CSRF, 113
LOADED_FEATURES, 67
LOAD
default_locale, 163
_PAT H, 67
default_scope, 210
.limit, 147
delegate, 219
accessor, 52
dynamic finder, 188
ActionMailer, 177
emails multipart, 179
ActionMailer::Base, 178
ember.js, 74
ActiveModel, 77, 194
ensure, 65
ActiveRecord, 77
ERB, 94
ActiveSupport, 80
execute, 230
after_filter, 171
extend, 63
Arel, 77
around_filter, 171
finders dinâmicos, 256
Asset Pipeline, 148
fixtures, 93
associação em massa, 122
flash, 122
form_for, 118
backbone.js, 74
form_tag, 290
BCrypt, 138
from, 207
before_filter, 171
begin, 64
gem, 68
belongs_to, 232
gemspec, 68
geradores, 100
callbacks, 184
group, 207
class, 51
class macro, 54
has_secure_password, 138
closures, 49
having, 207
config.assets.paths, 277
helpers, 157
312
Casa do Código
Índice Remissivo
herança, 57
order, 207
I18n, 161, 178
params, 120
i18n.default_locale, 162
partial, 95
include, 62
password_field, 118
includes, 207
private, 58
initialize, 51
Proc, 46
protect_from_forgery, 113
joins, 207
protected, 58
l, 240
public, 58
label, 118
Rails, 2
lambda, 48
readonly, 207
limit, 207
recurso singleton, 187
link_to, 126
redirect_to, 122
load, 67
references, 231
localize, 240
reorder, 207
lock, 207
require, 67
método de classe, 52
require_relative, 67
método de escrita, 53
require_self, 151
método de instância, 51
require_tree, 151
método de leitura, 52
rescue, 65
métodos de classe, 56
REST, 75
Módulos, 60
return, 40
mailer, 178
reverse_order, 207
mass-assignment, 122
root_path, 135
match, 174
rotas, 93
migração, 100
Ruby, 2
migrações, 91
scope, 170, 250
mixins, 61
secret_token, 202
module, 60
select, 207
MVC, 76
self, 52
new, 52
session, 200
nokogiri, 69
Session Hijack, 202
notice, 126
setter, 53
sprockets, 150
offset, 207
313
Índice Remissivo
Casa do Código
strftime, 240
submit, 118
text_area, 118
text_field, 118
text_field_tag, 290
try, 215
update_attributes, 131
validates_confirmation_of, 105
validates_length_of, 106
validates_presence_of, 104
variáveis de instância, 52
view helper, 157
webfonts, 274
where, 207
314
Casa do Código
Referências Bibliográficas
Referências Bibliográficas
[1] David Thomas Andrew Hunt. The Pragmatic Programmer: From Journeyman
to Master. Addison-Wesley Professional, 1999.
[2] Kent Beck. Test-Driven Development: By Example. Addison-Wesley Professio-
nal, 2003.
[3] Gregory Brown. Ruby Best Practices. O’Reilly, 2009.
[4] Gregory Brown. Issue 24: Connascence as a software design metric. http://
colcho.net/issue-24, 2011.
[5] Zach Dennis Aslak Hellesøy Bryan Helmkamp Dan North David Chelimsky,
Dave Astels. The RSpec Book: Behaviour-Driven Development with RSpec, Cu-
cumber, and Friends. Pragmatic Bookshelf, 2010.
[6] Avdi Grimm. Exceptional Ruby. ShipRise, 2011.
[7] Ian Robinson Jim Webber, Savas Parastatidis. Rest in Practice: Hypermedia and
Systems Architecture. O’Reilly Media, 2010.
[8] Paolo Perrotta. Metaprogramming Ruby: Program like the Ruby Pros. Pragmatic
Bookshelf, 2010.
[9] Nando Vieira. Guia rápido de rspec. http://colcho.net/guia-rspec, 2010.
[10] Jim Weirich. Building blocks of modularity. http://colcho.net/bbom, 2009.
315