2. Mas o design do meu formulário não
está igual ao do Bootstrap!
Form Builders to the rescue!
3. Ao trabalhar com formulários em Rails, use
sempre form-builders
} Forma padronizada de lidar com formulários no Rails;
} Faz com que você organize o markup/html/css da sua
aplicação de forma mais limpa;
} Oficializa o reuso de formulários HTML por todo o site,
além de garantir que os estilos são os mesmos em todos
os lugares;
4. Como implementar um form-builder?
} O que você quer fazer é implementar um Decorator que
vai cobrir o form builder padrão do Rails;
} Reuse as chamadas que já existem e adicione ou
empacote o resultado do form builder padrão com os
seus estilos personalizados;
} O Bootstrap do twitter foi feito sob medida pra ser
utilizado com form builders do Rails;
5. Iniciando a implementação – app/helpers/
bootstrap_form_builder.rb
class BootstrapFormBuilder < ActionView::Helpers::FormBuilder
def get_error_text(field)
self.object && self.object.errors[field].first
end
def content_tag(*args)
@template.content_tag(*args).html_safe
end
def render_label(field, title)
label(title.blank? ? self.object.class.human_attribute_name(field) : title)
end
end
6. Começando a magia – define_method
class BootstrapFormBuilder < ActionView::Helpers::FormBuilder
basic_helpers = %w{text_field text_area select password_field file_field}
basic_helpers.each do |name|
define_method(name) do |field, *args|
options = args.last.is_a?(Hash) ? args.last : {}
label = render_label(field, options[:label])
error_text = get_error_text(field)
wrap_field(label, super(field, *args), error_text)
end
end
end
7. Como assim define_method?
} Define um método em tempo de execução dentro de um
objeto (a técnica é comumente conhecida como
metaprogramação);
} No nosso caso, que é a implementação de um Decorator,
é a solução ideal, pois provê uma forma genérica de
adicionar a mesma funcionalidade aos vários métodos que
precisam dela, sem repetição;
8. Implementando wrap_field
def wrap_field(label, content, error_text)
wrapper_class = ['clearfix']
unless error_text.blank?
wrapper_class << 'error'
end
result = [content]
unless error_text.blank?
result << content_tag(:span, error_text, :class => 'help-inline')
end
result = result.join(' ').html_safe
@template.content_tag(:div, label + content_tag(:div, result, :class => 'input'), :class =>
wrapper_class.join(' '))
end
9. Implementando para campos que tem vários
itens de formulário
multipart_helpers = %w{date_select datetime_select}
multipart_helpers.each do |name|
define_method(name) do |field, *args|
options = args.last.is_a?(Hash) ? args.last : {}
label = render_label(field, options[:label])
error_text = get_error_text(object, field)
wrap_multipart_field(label, super(field, *args), error_text)
end
end
10. Empacotando campos com vários itens
def wrap_multipart_field(label, content, error_text)
wrapper_class = ['clearfix']
unless error_text.blank?
wrapper_class << 'error'
end
result = [content]
unless error_text.blank?
result << content_tag(:span, error_text, :class => 'help-inline')
end
result = content_tag(:div, result.join(' ').html_safe, :class => 'inline-inputs')
@template.content_tag(:div, label + content_tag(:div, result, :class => 'input'), :class =>
wrapper_class.join(' '))
end
11. Detalhe especial
} Ao usar human_attribute_name o seu objeto precisa ter a tradução
dos atributos definida, como em:
“pt-BR”:!
activerecord:!
models:!
produto:!
one: Produto!
other: Produtos!
attributes:!
produto:!
preco: Preço!
nome: Nome!
descricao: Descrição!
12. Alterando o formulário de produtos pra usar
o novo Form Builder
<%= admin_form_for_produto do |f| %>
<fieldset>
<legend> Criar/Editar Produto </legend>
<%= error_messages_for @produto %>
<%= f.text_field :nome %>
<%= f.text_field :preco %>
<%= f.text_area :descricao %>
<div class="actions">
<%= submit_tag 'Enviar', :class => 'btn primary' %>
</div>
</fieldset>
<% end %>
13. Alterando o método que abre o formulário –
app/helpers/admin/produtos_helper.rb
module Admin::ProdutosHelper
def admin_form_for_produto( &block )
opcoes = if @produto.new_record?
[admin_produtos_path, :post]
else
[admin_produto_path( @produto ), :put]
end
form_for( @produto,
:url => opcoes.first,
:html => { :method => opcoes.last },
:builder => BootstrapFormBuilder, &block )
end
end
14. Criando os usuários
} Os usuários da aplicação englobam tanto as pessoas que
vão vir fazer compras no site como também os
administradores que vão alimentar a administração;
} Os usuários serão cadastrados através de suas contas de
email e serão marcados como usuários comuns ou
administradores;
16. Criando a classe Usuario – validações e
campos virtuais
class Usuario < ActiveRecord::Base
attr_accessor :senha, :termos_e_condicoes
validates_presence_of :nome, :email
validates_acceptance_of :termos_e_condicoes, :if => :new_record?
validates_presence_of :senha_em_hash, :if => :senha_necessaria?
validates_confirmation_of :senha, :if => :senha_necessaria?
validates_length_of :senha, :within => 4..40, :if => :senha_necessaria?
end
17. Senhas e segurança
} Por motivos de segurança, você nunca deve gravar uma
senha de um dos seus usuários no banco de dados em um
formato onde os dados possam ser reconstruídos;
} Senhas devem sempre ser gravadas em na forma de
hashes, onde não é possível obter a senha novamente,
apenas através de força bruta;
} Não criptografe senhas, não as guarde em formatos que
possam ser descobertos;
18. Definindo o controle de senhas do usuário
class Usuario < ActiveRecord::Base
before_validation :hashear_senha
def senha_necessaria?
self.senha_em_hash.blank? || !self.senha.blank?
end
def senha_correta?( _senha )
self.senha_em_hash == Usuario.hashear( _senha, self.salt )
end
protected
def hashear_senha
return true if self.senha.blank?
self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s(:db)}--#{self.email}--#{self.nome}") if self.new_record?
self.senha_em_hash = Usuario.hashear(self.senha, self.salt)
end
end
19. Métodos de classe complementares
class Usuario < ActiveRecord::Base
class << self
def hashear( senha, salt )
Digest::SHA1.hexdigest("--#{salt}--#{senha}--")
end
def autenticar( email, senha )
usuario = Usuario.first( :conditions => { :email => email } )
if usuario && usuario.senha_correta?( senha )
usuario
else
nil
end
end
end
end
20. Usuários e Senhas
} Os campos senha e senha_confirmation são ambos
campos virtuais, eles existem apenas para ajudar a
geração do hash da senha;
} A senha nunca é gravada em lugar nenhum como o
usuário a digitou, ela será sempre gravada como hash;
} Ao fazer o login do usuário, nós recebemos o email e a
senha digitada, mas transformamos ela no hash para
poder fazer a comparação no método “autenticar”;
21. Preparando o applicaction_controller.rb para
o controle de usuários
def usuario_atual
if @usuario_atual != false
@usuario_atual = login_pela_sessao
end
@usuario_atual
end
def usuario_atual=( usuario )
@usuario_atual = usuario
session[:usuario_id] = usuario.id
end
def login_pela_sessao
resultado = !session[:usuario_id].blank? &&
Usuario.find_by_id( session[:usuario_id] )
resultado ? resultado : false
end
22. Definindo o controle geral
} O usuário que está atualmente logado na aplicação está
acessível pelo método “usuario_atual”;
} O método avalia se há um usuário na sessão, na
chave :usuario_id e se não houver ele retorna “false”, para
que o código saiba que não há um usuário logado no
momento;
} Se a variável @usuario_atual já estiver definida como
“false”, ele não repete o teste, pois sabe que não há
usuário logado no momento;
23. Preparando o application_controller.rb para
o controle de acesso de usuários
helper_method :pedido_atual, :usuario_atual, :logged_in?, :
administrador?
def logged_in?
self.usuario_atual
end
def administrador?
logged_in? && self.usuario_atual.administrador?
end
24. Filtros para controle de acesso
def login_necessario
unless self.logged_in?
respond_to do |format|
format.html do
flash[:erro] = 'Você precisa estar logado para acessar esta página'
redirect_to sessao_path
end
end
else
true
end
end
25. Filtros para controle de acesso
def administrador_necessario
unless self.administrador?
respond_to do |format|
format.html do
flash[:erro] = 'Você precisa ser um administrador para visualizar esta página'
redirect_to sessao_path
end
end
else
true
end
end
26. Filtros para controle de acesso
} Os métodos login_necessario e
administrador_necessario devem ser utilizados como
“before_filter” em ações que requerem que o usuário
esteja logado ou que seja um administrador;
} O primeiro caso do uso é para os controllers da
administração do site, apenas administradores devem ter
acesso aquelas páginas;
} Como o código causa um redirect se o usuário não for o
que se espera, a execução da requisição pára no filtro;
27. Bloqueio de acesso apenas para
administradores
class Admin::BaseController < ApplicationController
layout 'administracao'
before_filter :administrador_necessario
end
28. Criando as contas de usuários –
usuarios_controller.rb
class UsuariosController < ApplicationController
before_filter :login_necessario, :only => [ :edit, :update ]
before_filter :load_usuario
def new
respond_to do |format|
format.html { render :new }
end
end
alias :edit :new
alias :show :new
protected
def load_usuario
@usuario = logged_in? ? self.usuario_atual : Usuario.new
end
end
29. Criando contas de usuários –
usuarios_controller.rb
class UsuariosController < ApplicationController
def create
@usuario.attributes = params[:usuario]
if @usuario.save
self.usuario_atual = @usuario unless logged_in?
respond_to do |format|
format.html do
flash[:notice] = 'Dados recebidos com sucesso'
redirect_to produtos_path
end
end
else
self.new
end
end
end
30. Criando contas de usuários
} Apenas usuários que estejam logados podem chamar as
ações edit, já que há um filtro que faz o bloqueio das
chamadas;
} Se o usuário estiver sendo criado neste momento, ele é
automaticamente logado no sistema, através da chamada:
} self.usuario_atual = @usuario
} Se os dados não forem válidos, ele é retornado para a
página do formulário;
31. Formulário de criação/edição de usuários
<%= form_for @usuario, :url => usuario_path, :html => { :method => :post} do |f| %>
<%= f.error_messages %>
<p> Nome: <br/> <%= f.text_field :nome %> </p>
<p> Email: <br/> <%= f.text_field :email %> </p>
<p> Senha: <br/> <%= f.password_field :senha %> </p>
<p> Confirme a senha: <br/> <%= f.password_field :senha_confirmation %> </p>
<% if @usuario.new_record? %>
<p> <%= f.check_box :termos_e_condicoes %> Eu li e aceito os termos e condições </p>
<% end %>
<p> <%= submit_tag 'Enviar' %> </p>
<% end %>
32. Formulário de criação de usuários
} O formulário não referencia os campos salt ou
senha_em_hash, apenas os campos virtuais senha,
senha_confirmation e termos_e_condicoes;
} Não é necessário gravar no banco de dados se o usuário
aceitou ou não os termos e condições, se ele criou a
conta, isso quer dizer que ele aceitou o campo;
} Se nós não estamos mais criando um novo usuário, não é
necessário mostrar o campo :termos_e_condicoes;
33. Rotas do tipo resource
} Quando uma rota é definida como “resource” (no
singular), significa que o recurso que ela representa é
único (ou único para a aplicação ou único para o usuário
atual);
} Exemplos disso são o usuário que está atualmente logado
ou os dados da sua conta;
} Quando você define uma rota como “resource :usuario”
as chamadas vão ser no singular, mas o nome do
controller deve ser no plural, como em
“UsuariosController”;
34. Criando rotas para usuários e sessões
resource :sessao
resource :usuario
35. Fazendo o login de usuários já existentes –
sessoes_controller.rb
class SessoesController < ApplicationController
def new
respond_to do |format|
format.html do
render :new
end
end
end
alias :show :new
#logoff
def destroy
reset_session
respond_to do |format|
format.html do
flash[:aviso] = 'Você saiu da aplicação com sucesso'
redirect_to sessao_path
end
end
end
end
36. Fazendo o login de usuários já existentes
def create
@usuario = Usuario.autenticar( params[:email] , params[:senha])
if @usuario
self.usuario_atual = @usuario
respond_to do |format|
format.html do
flash[:aviso] = "Seja bem vindo a nossa loja, #{self.usuario_atual.nome}"
redirect_to produtos_path
end
end
else
respond_to do |format|
format.html do
flash.now[:erro] = 'Não foi encontrado um usuário com o email e a senha que você forneceu'
new
end
end
end
end
37. Formulário de login de usuários – sessoes/
new.html.erb
<%= form_tag sessao_path do %>
<p>
Email:
<br/>
<%= text_field_tag :email, params[:email] %>
</p>
<p>
Senha:
<br/>
<%= password_field_tag :senha %>
</p>
<p> <%= submit_tag 'Enviar' %> </p>
<% end %>
38. Apagando as senhas até mesmo dos logs da
aplicação
class ApplicationController < ActionController::Base
#remover estes parâmetros dos logs
filter_parameter_logging :senha, :senha_confirmation
end
39. Fazendo a união do carrinho de compras do
usuário não logado com o usuário logado
def usuario_atual=( usuario )
@usuario_atual = usuario
session[:usuario_id] = usuario.id
usuario.create_pedido_atual unless usuario.pedido_atual
unless self.pedido_atual.blank?
usuario.pedido_atual.unir( self.pedido_atual )
self.pedido_atual.destroy
end
session[:pedido_id] = usuario.pedido_atual.id
end
40. Unindo carrinhos de compra
} Se o usuário já estava adicionando itens ao carrinho antes
de efetuar a compra, esses itens não podem ser perdidos;
} Assim que o usuário é logado dentro do sistema, o seu
pedido atual é unido com o pedido atual do usuário, de
forma que nenhum dos itens é perdido;
41. Template para criar conta ou login –
compartilhados/_login_logout.html.erb
<% if logged_in? %>
<p>
Olá <%= usuario_atual.nome %>, seja bem vindo!
<br/>
<%= link_to 'Clique aqui para atualizar os seus dados de usuário', usuario_path %>
</p>
<p>
<%= link_to 'Logoff', sessao_path, :method => :delete, :confirm => 'Tem certeza de que deseja sair da
aplicação?' %>
</p>
<% else %>
<p>
<%= link_to 'Já é usuário? Clique aqui para fazer login', sessao_path %>
|
<%= link_to 'Ainda não é usuário? Clique aqui para se cadastrar', usuario_path %>
</p>
<% end %>
42. Enviando emails com Rails
} O Rails vem com um componente padrão para o envio de
emails, o ActionMailer;
} Com ele, para enviar um email, você só precisa criar um
objeto que herde de ActionMailer::Base e criar o
template do texto do email a ser enviado;
} Os emails podem conter texto em quaisquer formato,
assim como anexos;
44. Criando um diretório para guardar os
mailers
} Crie uma pasta chamada mailers em “app”
} No seu environment.rb
} config.load_paths += [ "#{RAILS_ROOT}/app/mailers" ]
45. Criando uma classe base para os mailers –
app/mailers/base_mailer.rb
class BaseMailer < ActionMailer::Base
default_url_options[:host] = Rails.env.production? ?
’loja.com.br' : 'localhost:3000'
default :from => 'contato@loja.com.br'
end
46. Por que uma classe base?
} Adicionar métodos padrão para todos os mailers;
} Adicionar configurações padrão que vão valer para todos
os mailers da aplicação;
} Facilitar a migração futura para uma fila de emails, como
ar_mailer;
47. Implementação do mailer de usuarios –
app/mailers/usuarios_mailer.rb
class UsuariosMailer < BaseMailer
def registro( usuario )
@usuario = usuario
mail(
:to => usuario.email,
:subject => ‘Seja bem vindo a loja virtual em Rails ‘)
end
end
48. Email de registro de usuários – app/views/
usuarios_mailer/registro.text.erb
Olá <%= @usuario.nome %> ( <%= @usuario.email %> ),
Seja vem vindo a loja de exemplo do curso de Rails da
LinuxFi.
49. Adicionando o código de envio de email na
hora que o usuário for criado
class Usuario < ActiveRecord::Base
after_create :enviar_email
def enviar_email
UsuariosMailer.registro( self ).deliver
end
end
50. Enviando emails
} Quando você define um método para envio de emails no
seu mailer, a forma de chamar ele é:
} NomeDaClasse. nome_do_metodo( parametros ).deliver
} Como em:
} UsuariosMailer. registro( usuario ).deliver
} Isso vai fazer com que o ActionMailer gere o texto do
email e o envie para o destinatário definido;