Quatro dias de Ruby On Rails - Primeiro dia, Quatro dias de Ruby On Rails - Segundo dia
Seguindo com a tradução do tutorial, vamos ao terceiro dia (desculpem pela demora, mas não andava com muita vontade de escrever
)
Agora é hora de começar o “coração” da nossa aplicação. A tabela Itens contem a lista de “Tarefas”. Cada item pertence a uma categoria das que criamos no Segundo Dia, cada Item pode opcionalmente possuir uma nota, criada em uma tabela separada, mas esta vamos deixar para o próximo dia de Rails. Cada tabela possui uma chave primária chamada “id” que é utilizada também para fazer o link entre as tabelas.

Items table
CREATE TABLE items ( id smallint(5) unsigned NOT NULL auto_increment, done tinyint(1) unsigned NOT NULL default '0', priority tinyint(1) unsigned NOT NULL default '3', description varchar(40) NOT NULL default '', due_date date default NULL, category_id smallint(5) unsigned NOT NULL default '0', note_id smallint(5) unsigned default NULL, private tinyint(3) unsigned NOT NULL default '0', created_on timestamp(14) NOT NULL, updated_on timestamp(14) NOT NULL, PRIMARY KEY (id) ) TYPE=MyISAM COMMENT='List of items to be done';
The Model
Como fizemos antes, vamos gerar um “model” em branco.
W:ToDo>ruby script/generate model item exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/item.rb create test/unit/item_test.rb create test/fixtures/items.yml W:ToDo>
E agora vamos a edição do model:
class Item < ActiveRecord::Base belongs_to :category validates_associated :category validates_format_of :done_before_type_cast, :with => /[01]/, :message=>"must be 0 or 1" validates_inclusion_of :priority, :in=>1..5, :message=>"must be between 1 (high) and 5 (low)" validates_presence_of :description validates_length_of :description, :maximum=>40 validates_format_of :private_before_type_cast, :with => /[01]/, :message=>"must be 0 or 1"end
Validando links entre tabelas
Documentação: ActiveRecord::Associations::ClassMethods
Validando a entrada de dados
A tabela Notes
Esta tabela contem um campo de texto apenas, para armazenar informações extras sobre uma tarefa especifica. Estes dados poderiam ter sido armazenados em um campo da tabela “Itens”; mas fazendo desta forma, vamos aprender muito mais sobre o Rails
CREATE TABLE notes ( id smallint(6) NOT NULL auto_increment, more_notes text NOT NULL, created_on timestamp(14) NOT NULL, updated_on timestamp(14) NOT NULL, PRIMARY KEY (id) ) TYPE=MyISAM COMMENT='Additional optional information for to-dos';
O Model
Vamos gerar mais um model em branco, e desta vez sem muitas novidades.
class Note < ActiveRecord::Base validates_presence_of :more_notes end
Apenas, precisamos lembrar de adicionar a referencia ao Model de Itens
class Item < ActiveRecord::Base belongs_to :note
Usando o Model para manter a integridade referencial
O código que vamos desenvolver, permite que o usuário adicione uma nota para qualquer tarefa. Mas o que acontece se o usuário deleta uma tarefa que estava associada a uma nota? Precisamos encontrar uma maneira de remover a nota também, caso contrário, vamos ter muitas notas orfans poluindo o banco de dados.
No maneira Model / View / Controller de fazer as coisas, o Model deve cuidar disto. Por que? bom, você vera depois que poderemos deletar tarefas, clicando em um icone na tela de tarefas, mas também podemos remove-las, clicando no link “Purge completed itens”. Colocando este código no Model, ele sera executado independente de por onde removemos as tarefas.
app\models\item.rb (excerpt)
def before_destroy unless note_id.nil? Note.find(note_id).destroy end end
Isto pode ser lido da seguinte forma: antes de remover um item da tabela Itens, procure um item na tabela Notes que tenha o “id” igual ao campo notes_id do item que esta para ser removido, e remova-o. A não ser que não haja nenhum
Igualmente, se um registro da tabela notes é removido, a referencia para este deve ser removida da tabela Itens:
app\models\note.rb (excerpt)
def before_destroy
Item.find_by_note_id(id).update_attribute('note_id',NIL)
end
end
Documentação: ActiveRecord::Callbacks
Mais Scaffolding
Vamos gerar mais um pouco de código com o scaffold. Vamos gerar código para as tabelas Itens e Notes. Nos ainda não estamos prontos para lidar com as Notas, mas tendo o código gerado, quer dizer que podemos referenciar as notas sem ter um monte de erros chatos no meio do caminho. Da mesma forma que na construção de uma casa ? o scaffolding permite que você construa uma parede por vez, sem que todo o resto desmorone a sua volta.
W:ToDo>ruby script/generate scaffold Item [snip] W:ToDo>ruby script/generate scaffold Note [snip] W:ToDo>
Nota: Como nos alteramos a folha de estilos no tutorial anterior, responda não quando o scaffold perguntar se você quer sobre escrevela.
Mais sobre as views
Criando um layout para a aplicação
Nesta altura ja esta ficando claro que todas as páginas da aplicação terão as mesmas “primeiras linhas” de código, então começa a fazer sentido mover o layout comum para um layout compartilhado por toda a aplicação. Remova todos os arquivos de app\views\layouts\*.rhtml, e crie um arquivo de nome application.rhtml com o seguinte conteúdo.
<html>
<head>
<title><%= @heading %></title>
<%= stylesheet_link_tag 'todo' %>
<script language="JavaScript">
<!-- Begin
function setFocus() {
if (document.forms.length > 0) {
var field = document.forms[0];
for (i = 0; i < field.length; i++) {
if ((field.elements[i].type == "text") || (field.elements[i].type == "textarea")
|| (field.elements[i].type.toString().charAt(0) == "s")) {
document.forms[0].elements[i].focus();
break;
}
}
}
}
// End -->
</script>
</head>
<body OnLoad="setFocus()">
<h1><%=@heading %></h1>
<% if @flash["notice"] %>
<span class="notice">
<%=h @flash["notice"] %>
</span>
<% end %>
<%= @content_for_layout %>
</body>
</html>
O @heading agora é utilizado para o <title> e também para o <h1>. Eu renomeei o arquivo public/stylesheets/scaffold.css para todo.css para melhor clareza do código, e também brinquei um pouquinho com as cores, e bordas da tabela, para criar um layot um pouquinho melhor. Também coloquei um pequeno javascript para posicionar o cursos no primeiro campo do tipo input para que o usuário possa sair digitando quando abrir qualquer tela.
A tela “To Do List”
O que estou tentando alcançar é um “look and feel” similar a um PalmPilot ou PDAde desktop. O produto final é mostrado na imagem a baixo.

Alguns pontos:
O código para atingir estas metas esta a baixo:
app\views\items\list.rhtml
<% @heading = "To Do List" %>
<%= start_form_tag :action => 'new' %>
<table>
<tr>
<th><%= link_to_image "done", {:action => "purge_completed"}, :confirm => "Are you sure you want to permanently delete all completed To Dos?" %></th>
<th><%= link_to_image "priority",{:action => "list_by_priority"}, "alt" => "Sort by Priority" %></th>
<th><%= link_to_image "description",{:action => "list_by_description"}, "alt" => "Sort by Description" %></th>
<th><%= link_to_image "due_date", {:action => "list"}, "alt" => "Sort by Due Date" %></th>
<th><%= link_to_image "category", {:action => "list_by_category"}, "alt" => "Sort by Category" %></th>
<th><%= show_image "note" %></th>
<th><%= show_image "private" %></th>
<th> </th>
<th> </th>
</tr>
<%= render_collection_of_partials "list_stripes", @items %>
</table>
<hr />
<%= submit_tag "New To Do..." %>
<%= submit_tag "Categories...", {:type => 'button', :onClick=>"parent.location='" + url_for( :controller => 'categories', :action => 'list' ) + "'" } %>
<%= end_form_tag %>
<%= "Page: " + pagination_links(@item_pages, :params => { :action => @params["action"] || "index" }) + "<hr />" if @item_pages.page_count>1 %>
Removendo as tarefas completadas clicando em um icone
Imagens clicáveis são criadas com o comando link_to_image, que por padrão procura imagens no diretório pub/images com um sufixo .png; Clicando na imagem o método especificado sera executado.
Adicionando o parametro :confirm gera um popup de confirmação com javascript como antes.
Documentação: ActionView::Helpers::UrlHelper
Clicando em “OK” vai chamar o método purge_completed. O novo método purge_completed precisa ser definido no controller:
app\controllers\items_controller.rb (excerpt)
def purge_completed Item.destroy_all "done = 1" redirect_to :action => 'list' end
Item.destroy_all remove todos os itens da tabela em que “done=1″ e retorna para a listagem.
Alterando a ordenação clicando nos cabeçalhos das colunas
Clicando na coluna “Pri” chama o método list_by_priority . Este método também precisa ser declarado no controller:
app\controllers\items_controller.rb (excerpt)
def list @item_pages, @items = paginate :item, :per_page => 10, :order_by => 'due_date,priority' end def list_by_priority @item_pages, @items = paginate :item,:per_page => 10, :order_by => 'priority,due_date' render_action 'list' end
Nos especificamos uma ordenação para a consulta no método padrão “list”, e criamos um método list_by_priority novo. Perceba que precisamos especificar render_action ‘list’, pois por padrão o rails iria procurar um template de nome list_by_priority (que não existe ![]()
Adicionando um Helper
Os cabeçalhos para as colunas Note e Private são imagens, mas não são clicaveis. eu decidi criar um pequeno método show_image(name) para apenas mostrar a imagem:
app/helpers/application_helper.rb
# Methods added to this helper will be available to all templates in the application.
module ApplicationHelper
def self.append_features(controller)
controller.ancestors.include?(ActionController::Base) ?
controller.add_template_helper(self) : super
end
def show_image(src)
img_options = { "src" => src.include?("/") ? src : "/images/#{src}" }
img_options["src"] = img_options["src"] + ".png" unless
img_options["src"].include?(".")
img_options["border"] = "0"
tag("img", img_options)
end
end
Ja que este helper é associado a todos os controllers.
Nota: list_by_description e list_by_category são bem parecidos com o metodo ja criado, mas se você tiver problemas com o list_by_category, no próximo tutorial eu mostro o código.
Documentação: ActionView::Helpers::UrlHelper
Usando Javascript para os botões de navegação
onClick é um método padrão para gerenciar clicks e navegação entre páginas. Entretanto, Rails faz um grande trabalho, para renderizar URLs amigáveis, então precisamos perguntar ao rails Rails qual a URL a ser utilizada.
Para um controller e uma action, url_for vai retornar a URL correta.
Documentação: ActionController::Base
Formatando uma tabela com “Partials”
Eu quiz criar um efeito “ajax” para a listagem de itens. Partials são a solução para implementar isto; eles podem ser chamados usando o método render_partial:
<% for item in @items %> <%= render_partial "list_stripes", item %> <% end %>
ou pelo método mais economico render_collection_of_partials:
render_collection_of_partials "list_stripes", @items
Documentação: ActionView::Partials
O Rails também passa um número sequential “list_stripes_counter” ara o Partial. este sequencial é a chave para implementar coloração alternada para a linhas da tabela. Uma maneira é simplesmente testar se o número é par ou impar: se for par, utilizar cinza claro; se impar, usar cinza escuro.
app\views\items\_list_stripes.rhtml
<tr class="<%= list_stripes_counter.modulo(2).nonzero? ? "dk_gray" : "lt_gray" %>">
<td style="text-align: center"><%= list_stripes["done"] == 1 ?show_image("done_ico.gif") : " " %></td>
<td style="text-align: center"><%= list_stripes["priority"] %></td>
<td><%=h list_stripes["description"] %></td>
<% if list_stripes["due_date"].nil? %>
<td> </td>
<% else %>
<%= list_stripes["due_date"] < Date.today ? '<td class="past_due" style="text-align: center">' : '<td style="text-align: center">' %>
<%= list_stripes["due_date"].strftime("%d/%m/%y") %></td>
<% end %>
<td><%=h list_stripes.category ? list_stripes.category["category"] : "Unfiled"%></td>
<td><%= list_stripes["note_id"].nil? ? " " : show_image("note_ico.gif")%></td>
<td><%= list_stripes["private"] == 1 ? show_image("private_ico.gif") : " "%></td>
<td><%= link_to_image("edit", { :controller => 'items', :action => "edit", :id =>list_stripes.id }) %></td>
<td><%= link_to_image("delete", { :controller => 'items', :action => "destroy",:id => list_stripes.id }, :confirm => "Are you sure you want to delete this item?")%></td>
</tr>
um pouco de Rubyé utilizado para testar se o contador é par ou impar e renderizar class=”dk_gray” ou
class=”lt_gray”: list_stripes_counter.modulo(2).nonzero? class=”dk_gray”?”dk_gray” :”lt_gray”
O código até a o simbolo ? pergunta: O restante da divisão por dois não é zero?
O rsto da linha é uma “complicação” de um if/then/else que sacrifica a legibilidade por menos código: se a expressão antes do ? for verdadeira retorna o valor antes do : caso contrario retorna o outro valor.
A mesma técnica é utilizada para outras formatações na tela.
A tela para “Nova Tarefa”
O template como podemos ver é bastante simples:
app/views/items/new.rhtml
<% @heading = "New To Do" %>
<%= error_messages_for 'item' %>
<%= start_form_tag :action => 'create' %>
<table>
<%= render_partial "form" %>
</table>
<hr />
<%= submit_tag "Save" %>
<%= submit_tag "Cancel", {:type => 'button', :onClick=>"parent.location='" + url_for(:action => 'list' ) + "'" } %>
<%= end_form_tag %>
O codigo real esta no partial:
app\views\items\_form.rhtml
<tr> <td><b>Description: </b></td> <td><%= text_field "item", "description", "size" => 40, "maxlength" => 40%></td> </tr> <tr> <td><b>Date due: </b></td> <td><%= date_select "item", "due_date", :use_month_numbers => true %></td> </tr> <tr> <td><b>Category: </b></td> <td><select id="item_category_id" name="item[category_id]"> <%= options_from_collection_for_select @categories, "id", "category",@item.category_id %> </select> </td> </tr> <tr> <td><b>Priority: </b></td> <% @item.priority = 3 %> <td><%= select "item","priority",[1,2,3,4,5] %></td> </tr> <tr> <td><b>Private? </b></td> <td><%= check_box "item","private" %></td> </tr> <tr> <td><b>Complete? </b></td> <td><%= check_box "item", "done" %></td> </tr>
Criando um Drop Down para um campo Date
date_select gera um dropdown para entrada de datas:
date_select “item”, “due_date”, :use_month_numbers => true
Documentação: ActionView::Helpers::DateHelper
Tratando exceções no Ruby
Infelizmente, date_select aceita coisas como 31 de fevereiro. O Rails então se perde ao tentar salvar esta “data” no banco de dados. Uma forma de contornar isto é capturar esta excessão usando rescue, uma forma de tratar excessões no Rails
def create begin @item = Item.new(@params[:item]) if @item.save flash['notice'] = 'Item was successfully created.' redirect_to :action => 'list_by_priority' else @categories = Category.find_all render_action 'new' end rescue flash['notice'] = 'Item could not be saved.' redirect_to :action => 'new' end end
Criando um Drop Down a partir de uma tabela secundária:
Este é um outro exemplo do rails resolvendo problemas do dia a dia de uma maneira bastante economica. neste exemplo:
options_from_collection_for_select @categories, “id”, “category”, @item.category_id
options_from_collection_for_select lê todos os registros da coleção @categories e renderiza para cada um: <option
value=”[value of id]“>[value of category]</option>. O valor que for igual a @item_category_idvai ser marcado como “selected”. Como se isto não fosse o suficiente, este código ja faz o “escape” de caracteres HTML para você.
Documentação: ActionView::Helpers::FormOptionsHelper
Lembre-se que estes dados devem vir de algum lugar - o que significa uma pequena adição de código ao controller:
app\controllers\items_controller.rb (excerpt)
def new @categories = Category.find_all @item = Item.new end def edit @categories = Category.find_all @item = Item.find(@params[:id]) end
Criando um Drop Down de uma lista de constantes
Esta é uma versão simplificada do cenário anterior. Deixar listas de valores hard-coded no HTML não é sempre uma boa idéia - é mais fácil alterar listas em tabelas do que valores no código. Entretanto, existem casos em que esta é uma solução válida, então com o Rails você faz o seguinte:
select “item”,”priority”,[1,2,3,4,5]
Criando uma Checkbox
Outro problema do dia a dia; outro helper do Rails:
check_box “item”,”private”
Toques finais
Retocando a folha de estilos
Neste ponto, a tela “To Do List” ja deve estar funcionando, e também o botão “New To Do”. para melhoras as telas até agora, eu também fiz as seguintes alterações na folha de estilos:
public\stylesheets\ToDo.css
body { background-color: #c6c3c6; color: #333; }
.notice {
color: red;
background-color: white;
}
h1 {
font-family: verdana, arial, helvetica, sans-serif;
font-size: 14pt;
font-weight: bold;
}
table {
background-color:#e7e7e7;
border: outset 1px;
border-collapse: separate;
border-spacing: 1px;
}
td { border: inset 1px; }
.notice {
color: red;
background-color: white;
}
.lt_gray { background-color: #e7e7e7; }
.dk_gray { background-color: #d6d7d6; }
.hightlight_gray { background-color: #4a9284; }
.past_due { color: red }
A tela “Edit To Do”
O resto deste terceiro dia vai ser tomado pela criação da tela “Edit To Do”, que é muito parecida com a “New To Do”. Eu ficava bastante irritado com os livros da faculdade que diziam: deixo isto como um exercicio para o leitor, mas agora é uma ótima hora para fazer o mesmo com vocês :D.
Se vocês quiserem uma cópia da folha de estilos e das imagens, eu as disponibilizei neste link.
E até a ultima parte do tutorial, que se tudo der certo, vai demorar muito menos do que esta para ser publicada ![]()
PS.: agradeço se os leitores que estão gostando do tutorial colocarem links em seus blogs para as 3 partes ja publicadas.
PS2.: precisei alterar os campos done e priority da tabela itens para int(5) para que tudo funcionasse, e executei rake db:schema:dump, e para que vocês ja comecem a se acostumar com os “migrations” do rails, o código gerado por ele segue a baixo:
# This file is autogenerated. Instead of editing this file, please use the # migrations feature of ActiveRecord to incrementally modify your database, and # then regenerate this schema definition. ActiveRecord::Schema.define() do create_table "categories", :force => true do |t| t.column "category", :string, :limit => 20, :default => "", :null => false t.column "created_on", :timestamp, :null => false t.column "updated_on", :timestamp, :null => false end add_index "categories", ["category"], :name => "category_key", :unique => true create_table "items", :force => true do |t| t.column "description", :string, :limit => 40, :default => "", :null => false t.column "due_date", :date t.column "category_id", :integer, :limit => 5, :default => 0, :null => false t.column "note_id", :integer, :limit => 5 t.column "private", :integer, :limit => 3, :default => 0, :null => false t.column "created_on", :timestamp, :null => false t.column "updated_on", :timestamp, :null => false t.column "done", :integer, :limit => 5, :default => 0, :null => false t.column "priority", :integer, :limit => 5, :default => 3, :null => false end create_table "notes", :force => true do |t| t.column "more_notes", :text, :default => "", :null => false t.column "created_on", :timestamp, :null => false t.column "updated_on", :timestamp, :null => false end end
Tudo pronto? então siga para o Quarto e último dia do nosso tutorial.
PS.: agradeço se os leitores que estão gostando do tutorial colocarem links em seus blogs para o tutorial, indicando para seus amigos.
Se você gostou deste post, lembre-se de assinar o RSS feed do blog, para ser notificado de novos posts!
Tags: artigo, produtividade, Ruby
[...] Tudo pronto? então siga para o Terceiro dia. [...]
Reply to this comment[...] Rails), eu traduzi a pouco tempo o ótimo tutorial Four Dais on Rails: Primeiro Dia, Segundo Dia, Terceiro Dia, Quarto e Último [...]
Reply to this comment[...] Rails), eu traduzi a pouco tempo o ótimo tutorial Four Dais on Rails: Primeiro Dia, Segundo Dia, Terceiro Dia, Quarto e Último Dia [...]
Reply to this commentUrubatan… pq quando vc usa o belongs_to para fazer a ligação entre as tabelas vc não coloca o has_many do outro lado? não acha que ficaria mais didatico? claro que na real vc não esta usando mais pensando em implementações de pesquisas isso podia ser util não acha…
Reply to this commentJeffeson, o has_many só é útil se for utilizado
Reply to this commentSe não for utilizado ele estaria só complicando a visa desnecessariamente