curso de ruby on rails - aula 03
DESCRIPTION
Material do curso de Ruby on Rails.TRANSCRIPT
Aprendendo Ruby on Rails - Aula 3
Maurício Linhares
partials – reorganizando as views } Quando uma view cresce demais ou tem pedaços que
podem ser reutilizados em outros lugares, esses pedaços podem ser transformados em um “partial”;
} Partials são arquivos de view onde o nome começa com um “_” (underline) como em “_items.html.erb”;
} É possível passar variáveis para partials e eles também tem acesso a todas as variáveis de instância que estejam nas views;
2
app/views/items/_item.html.erb <tr> <td> <%= item.nome %> </td> <td> <%= item.quantidade %> </td> <td> <%= number_to_currency item.preco_unitario %> </td> <td> <%= number_to_currency item.preco_total %> </td> </tr>
3
Fazendo a chamada do partial – app/views/itens/index.html.erb <table> <thead> <tr> <td> Nome </td> <td> Quantidade </td> <td> Preço unitário </td> <td> Preço total </td> </tr> </thead> <tbody> <% pedido_atual.itens.each do |item| %> <%= render 'item', :item => item %> <% end %> </tbody> </table>
4
<%= render ‘ item‘, :item => item %> } Gere o partial “_item” dentro da pasta da view atual;
} Passe a variável item com o nome “:item” para o partial;
} Quando o caminho completo não for dado, o partial vai ser sempre procurado na pasta de view do controller atual;
} render :partial => “items/item”, :locals => { :item => item }
5
Alterando vários itens ao mesmo tempo no carrinho – formulários aninhados
} A solução mais simples pra editar vários elementos ao mesmo tempo em uma página;
} Rails tem suporte direto a formulários aninhados dentro do framework;
} Para usar formulários aninhados (para os objetos relacionados a outro) nós utilizamos o helper “accepts_nested_attributes_for”;
6
Na classe Pedido class Pedido < ActiveRecord::Base has_many :itens accepts_nested_attributes_for :itens #resto dos métodos end
7
Formulário no carrinho <%= form_tag atualizar_carrinho_itens_path, :method => :post do %> <table> <thead> <tr> <td> Nome </td> <td> Quantidade </td> <td> Preço unitário </td> <td> Preço total </td> </tr> </thead> <tbody> <% pedido_atual.itens.each do |item| %> <%= render 'item', :item => item %> <% end %> </tbody> </table> <% end %>
8
Adicionando campo de formulário ao _item.html.erb
9
<tr> <td><%= item.produto.nome %></td> <td> <%= hidden_field_tag 'pedido[itens_attributes][][id]', item.id %> <%= text_field_tag 'pedido[itens_attributes][][quantidade]', item.quantidade %> </td> <td><%= number_to_currency item.produto.preco %></td> <td><%= number_to_currency item.preco_total %></td> </tr>
Formato do formulário para itens aninhados
10
} 'pedido[itens_attributes][][id]’ } Cada item deve vir com o seu próprio id, para que o item
correto seja atualizado;
} 'pedido[itens_attributes][][quantidade]’ } Junto do ID vem o atributo (ou os atributos) que você deseja
alterar;
} Veja que entre o [itens_attributes] e [id] existe um “[]”, esse par de colchetes serve pra definir que você está alterando um array de itens;
Formato do formulário que está sendo enviado para o controller
11
} "pedido"=> } {"itens_attributes"=>[
} {"id"=>"1","quantidade"=>"5"}, } {"id"=>"2","quantidade"=>"4"}, } {"id"=>"3","quantidade"=>"8”}
} ]}
Adicionando uma nova rota
12
} map.resources :itens, :collection => { :atualizar_carrinho => :post }
} Ações do tipo “collection” são definidas quando você vai chamar um método do controller que não afeta um recurso em específico, a URL gerada seria “/itens/atualizar_carrinho”;
} A outra opção seria usar ações do tipo “member”, que afetam recursos específicos, a URL gerada seria “/itens/nome_da_acao/1”
Implementando o controller
13
def atualizar_carrinho pedido_atual.update_attributes( params[:pedido] ) respond_to do |format| format.html do flash[:success] = 'Carrinho de compras atualizado com sucesso' redirect_to itens_path end end end
Removendo itens que tenham a quantidade menor do que 1
14
} O ActiveRecord define vários hooks (“ganchos”) onde você pode executar código durante o ciclo de vida de um objeto: } before_validation } before_validation_on_create } validate_on_create } after_validation } after_validation_on_create } before_save } before_create } after_create } after_save
Ganchos do ActiveRecord
15
} Esses ganchos podem ser utilizados para executar código nas suas classes, como fazer cache de valores, preencher os objetos com dados padrão;
} Você pode definir vários métodos para um mesmo gancho, todos eles são executados na sequência em que foram definidos;
} Quando um método retorna “false”, ele pára a execução de todos os ganchos que vem após ele;
Implementando um gancho
16
class Pedido < ActiveRecord::Base has_many :itens, :dependent => :destroy accepts_nested_attributes_for :itens after_save :remover_itens_zerados # resto do código protected def remover_itens_zerados zerados = self.itens.find_all { |item| item.quantidade.to_i < 1 } self.itens.delete( *zerados ) end end
self.itens.delete( *args ) – métodos encontrados em associações
17
} itens.delete – remove objetos da associação (apagando ele do banco se o :dependent for :destroy ou :delete_all)
} itens(true) – atualiza a coleção pra os valores mais atuais no banco
} itens.item_ids – traz os ids dos objetos associados
} itens.find – faz uma consulta no banco apenas nos objetos que fazem parte da associação
} Outros métodos em -> http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many
Montando a administração do site
18
} Vive em um caminho/namespace separado do site principal (como em “/admin”);
} Normalmente precisa de um controle de acesso mais complexo, liberando apenas para usuários que sejam administradores;
} Também precisa de um layout específico pra ela, em vez de usar o layout comum do site;
Criando o controller base da administraçào
19
class Admin::BaseController < ApplicationController layout 'administracao' end
Pastas
20
} O arquivo do controller deve ser base_controller.rb e deve ser criado dentro da pasta “app/controllers/admin”;
} Quando um controller tem um namespace ( Admin::BaseController ), a pasta onde ele fica deve representar o mesmo caminho do namespace dele;
} O nome final do arquivo é o nome do controller SEM o namespace;
Criando o controller base da administração
21
} No controller fazemos a uma chamada ao método “layout” e passamos como parâmetro a string “administracao”;
} Isso define que o controller Admin::BaseController não vai mais utilizar o layout “application.html.erb” mas sim o “administracao.html.erb”;
} Cada controller pode definir o seu próprio layout base com a chamada do método “layout”;
Configurando as rotas com namespace
22
namespace :admin do resources :produtos end
rake routes | grep admin
23
} admin_produtos GET /admin/produtos } POST /admin/produtos } new_admin_produto GET /admin/produtos/new } edit_admin_produto GET /admin/produtos/:id/edit } admin_produto GET admin/produtos/:id } PUT /admin/produtos/:id } DELETE /admin/produtos/:id
Instalando a gem de paginação
24
} Instalando a gem } gem install will_paginate
} Configurando ela no environment.rb } config.gem “will_paginate”
Adicionando métodos de paginação em ApplicationController
25
class ApplicationController < ActionController::Base #outros métodos protected def load_page @page = params[:page] || 1 @per_page = params[:per_page] || 10 end def paginate( scope, options = {} ) load_page scope.paginate( :page => @page, :per_page => @per_page ) end end
Métodos básicos - admin/produtos_controller.rb
26
class Admin::ProdutosController < Admin::BaseController before_filter :load_produto, :only => [ :new, :edit, :create, :update, :destroy ] #mais código aqui protected def load_produto @produto = params[:id].blank? ? Produto.new : Produto.find( params[:id] ) end def ir_para_listagem( mensagem ) respond_to do |format| format.html do flash[:success] = mensagem redirect_to admin_produtos_path end end end end
before_filter
27
} Define métodos a serem executados antes da ação do controller ser executada;
} Normalmente são utilizados para transformar dados da sessão em variáveis para o controller ou validar se a requisição é válida ou não;
} Podem ser aplicados a todos os métodos de um controller ou apenas a alguns com :only e :except;
Criando e editando produtos – admin/produtos_controller.rb
28
def new respond_to do |format| format.html do render :new end end end alias :edit :new def create if @produto.update_attributes( params[:produto] ) ir_para_listagem( 'Produto criado/atualizado com sucesso' ) else new end end alias :update :create
alias
29
} alias :edit :new } Crie um método “edit” que aponte para o método “new”
} Simplifica a definição de controllers, já que os métodos “new” e “edit” fazem a mesma coisa;
} O funcionamento é o mesmo para “create” e “update”, os métodos fazem a mesma coisa, então definir um alias é mais prático;
Listando e removendo produtos – admin/produtos_controller.rb
30
def index @produtos = paginate( Produto ) respond_to do |format| format.html end end def destroy @produto.destroy ir_para_listagem( 'Produto removido com sucesso' ) end
Preparando a listagem de produtos da administração – admin/produtos/index
31
<% unless @produtos.blank? %> <%= will_paginate @produtos %> <table> <%= cabecalho_de_tabela 'Nome', 'Preço', 'Ações' %> <tbody> <%= render @produtos %> </tbody> </table> <%= will_paginate @produtos %> <% else %> <p> Não há produtos cadastrados no sistema. </p> <% end %>
Definindo um método em application_helper.rb
32
module ApplicationHelper def cabecalho_de_tabela( *nomes ) colunas = nomes.map { |nome| "<th>#{nome}</th>" } linha = content_tag( :tr, colunas.join("\n").html_safe ) content_tag( :thead, linha ).html_safe end end
Definindo um método em application_helper.rb
33
} Métodos definidos em quaisquer arquivos dentro da pasta helper estão disponíveis por padrão em todas as views da sua aplicação;
} Você deve agrupar os métodos em helpers dependendo da funcionalidade que implementam ou modelo sobre o qual interagem;
} Não defina todos os seus métodos em application_helper.rb, crie outros arquivos de helper para organizar o seu código J
<%= will_paginate @produtos %>
34
} O método de views will_paginate vai gerar na sua view um controle de paginação com os números de páginas e os botões anterior e próximo;
} O parâmetro que ele recebe é uma coleção que tenha sido retornada através de um método paginate em um dos seus models, uma coleção normal não vai ser aceita;
} Você pode personalizar as mensagens ou o HTML gerado através de opções passadas para esse método;
<%= render @produtos %>
35
} Atalho para gerar uma lista de objetos que compartilham o mesmo partial;
} No caso do exemplo, é equivalente a fazer: <% @produtos.each do |produto| %> <%= render “admin/produtos/produto”, :produto => produto %> <% end %> } O caminho do partial a ser buscado vai ser sempre
“caminho_atual/nome_da_classe_no_singular”
Linha de produto – admin/produtos/_produto.html.erb
36
<tr> <td> <%= produto.nome %> </td> <td> <%= number_to_currency produto.preco %> </td> <td> <%= link_to 'Editar', edit_admin_produto_path(produto) %> | <%= link_to 'Remover’, admin_produto_path(produto), :method => :delete, :confirm => 'Tem certeza de que deseja remover este produto?' %> </td> </tr>
link_to - :method => :delete
37
} Links em HTML geram, por padrão, uma requisição do tipo GET, mas no nosso caso precisamos que um link gere uma requisição do tipo :delete;
} O Rails oferece uma opção chamada :method para links para que eles possam executar chamadas a outros métodos HTTP;
} Além disso também é possível usar a opção :confirm para validar se o usuário quer realmente efetuar aquela ação;
Criando um helper para simplificar o formulário de produtos
38
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 }, &block ) end end
RESTful forms
39
} Formulários em Rails seguem a padronização RESTful que as rotas para os métodos definem;
} Para criar um novo produto, é necessário um POST em “/admin/produtos”, para editar um produto é necessário um PUT em “/admin/produtos/ID”;
} O método admin_form_for_produto usa o método new_record? de Produto para saber se ele está criando um novo ou editando um produto existente;
Formulário de criação/edição de produtos
40
<%= admin_form_for_produto do |f| %> <%= error_messages_for @produto %> <p> Nome: <br/> <%= f.text_field :nome %> </p> <p> Preço: <br/> <%= f.text_field :preco %> </p> <p> Descrição: <br/> <%= f.text_area :descricao %> </p> <p> <%= submit_tag 'Enviar' %> </p> <% end %>
Implementando o helper para mostrar os erros
41
module ApplicationHelper def error_messages_for( object, title = 'Foram encontrados erros nos seus dados' ) if object render 'compartilhados/erros', :title => title, :errors => object.errors.full_messages end end end
E o HTML – app/views/compartilhados/_erros.html.erb
42
<div class="alert-message block-message error"> <a class="close" href="#">×</a> <p><strong> <%= title %> </strong></p> <ul> <% errors.each do |error| %> <li><%= error %></li> <% end %> </ul> </div>