Você está na página 1de 26

Ruby on Rails um meta-framework gratuito que promete aumentar velocidade e facilidade no desenvolvimento de sites orientados a banco de dados (database-driven

n web sites), uma vez que possvel criar aplicaes com base em estruturas pr-definidas. Freqentemente referenciado como Rails ou RoR, o Ruby on Rails um projeto de cdigo aberto escrito na linguagem de programao Ruby. As aplicaes criadas utilizando o framework Rails so desenvolvidas com base no padro de projeto MVC (Model-View-Controller). Esse documento tem o objetivo no de ser um tutorial, mas ser um guia de referncia para os integrantes do grupo, pois descreve uma aplicao completa desenvolvida utilizando Ruby on Rails. A maior parte dos membros do grupo no utilizavam esse framework anteriormente, o estudo do mesmo fundamental para o desenvolvimento da aplicao. Um guia de referncia vai diminuir o tempo para obter informaes sobre como realizar uma determinada tarefa especfica, reduzindo a necessidade de consultas a fontes extensas e que possuem um detalhamento muito maior, consumindo assim mais tempo no desenvolvimento.

Executando o comando rails


E:>cd projetosrails E:Projetosrails>rails ToDo E:Projetosrails>cd Todo E:ProjetosrailsToDo>

Executando rails Todo cria um novo diretrio de nome ToDo que vai conter a aplicao organizada em diretrios e ja com diversos arquivos padro, os mais importantes so os seguintes:

app - inclui o cdigo da aplicao, se divide em model, view, controller e helper config - contem o arquivo database.yml com as configuraes de acesso ao banco de dados log - Logs de aplicao. Nota: o arquivo development.log possui registros para cada ao executada pelo rails, bastante til para soluo de erros e depurao, mas precisa ser limpo regularmente! public - O diretrio com os arquivos estticos da aplicao, contem os sub diretrios images, javascripts e stylesheets

Para executar a aplicao basta executar o comando a baixo:


E:ProjetosrailsToDo>ruby script\server

E a aplicao ja deve estar acessvel atravs do endereo: http://localhost:3000/ Neste ponto voc deve ver a pgina Congratulations, youve put Ruby on Rails!

Configurando o banco de dados. Criar um banco de dados de nome todos em um MySQL editar o arquivo config\database.yml e colocar o seguinte cdigo:
development: adapter: mysql database: todos host: localhost username: foo password: bar

Criando a tabela de categorias. A tabela de categorias sera utilizada nos prximos exemplos, ela consiste apenas em uma lista que ira ser utilizada para agrupar as nossas tarefas. [sql]CREATE TABLE `categories` ( `id` smallint(5) unsigned NOT NULL auto_increment, `category` varchar(20) NOT NULL default , `created_on` timestamp(14) NOT NULL, `updated_on` timestamp(14) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `category_key` (`category`) ) TYPE=MyISAM COMMENT=List of categories; [/sql] Algumas dicas sobre a nomenclatura dos campos. ? underscores nos nomes dos campos sero alterados por espaos pelo Rails, para nomes legveis por humanos ? cuidado com a mistura de letras maisculas e minsculas - algumas partes do cdigo do Rails so case sensitive ? toda tabela deve ter uma chave primria de nome id, no MySQL o mais fcil criar isto como numeric auto_increment ? links para outras tabelas seguem a conveno de nomes _id ? O Rails vai atualizar automaticamente campos de nome created_at/created_on ou updated_at/updated_on, ento uma boa ideia ter estes campos nas tabelas. Documentation: ActiveRecord::Timestamp ? Dica til: se voc estver criando um sistema multi usurio (no importante para este tutorial), O Rails vai implementar Locking otimista se voc adicionar um campo de nome lock_version do tipo integer, com valor padro 0. Tudo o que voc vai precisar fazer adicionar um campo hidden de mesmo nome nos seus formulrios. Documentation: ActiveRecord::Locking

Modelo de dados. Criando um arquivo vazio.


E:ProjetosrailsToDo>ruby scriptgenerate model category exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/category.rb create test/unit/category_test.rb create test/fixtures/categories.yml create db/migrate create db/migrate/001_create_categories.rb E:ProjetosrailsToDo>

vai criar um arquivo de nome category.rb, e dois arquivos de teste de nome category_test.rb e categories.yml. Scaffolf O controller o corao de uma aplicao Rails
E:ProjetosrailsToDo>ruby scriptgenerate controller category exists app/controllers/ exists app/helpers/ create app/views/category exists test/functional/ create app/controllers/category_controller.rb create test/functional/category_controller_test.rb create app/helpers/category_helper.rb E:ProjetosrailsToDo>

O comando acima, gera tres arquivos e um diretrio vazio. Vamos editar o arquivo category_controller.rb [ruby]class CategoryController < ApplicationController scaffold :category end[/ruby] Documentation: ActionController::Scaffolding Depois disto, acesse a pgina: http://localhost:3000/category e se maravilhe com um CRUD criado com uma linha de cdigo. Tente explorar um pouquinho o CRUD e cadastrar a mesma categoria duas vezes, o Rails vai mostrar uma mensagem de erro parecida com ? ActiveRecord::StatementInvalid in Category#create?. Isto pode ser corrigido adicionando algumas validaes no modelo.

Aperfeioando o Model. Rails fornece diversos tipos de validaes de grtis, ou quase. vamos editar o arquivo category.rb e adicionar alguma validao ao nosso modelo vazio. [ruby]class Category < ActiveRecord::Base validates_length_of :category, :within => 1..20 validates_uniqueness_of :category, :message => already exists end[/ruby] Estas duas novas linhas vo adicionar validao automtica. ? validates_length_of: o campo no pode ser vazio e valida tambm o tamanho ? validates_uniqueness_of: verifica a duplicidade dos cdigos. Eu no gosto da mensagem padro do Rails - xxx has already been taked: - ento eu forneci a mensagem a ser utilizada. Esta uma das melhores features do rails. Teste os padres, se no gostar costumize. Documentation: ActiveRecord::Validations::ClassMethods Tente novamente inserir um registro duplicado, desta vez o erro mostrado de uma forma bem mais amigavel. Os estilos no so lindos, mas o que voc esperava sem tabalho nenhum? E isto por enquanto, no segundo dia vamos melhorar um pouco mais a aplicao e comear a trabalhar com relacionamentos. Com a ao scaffold :entidade o rails gera o cdigo necessrio dinamicamente, mas podemos tambm gerar o cdigo para que possamos ler e alterar
ruby script/generate scaffold categoryexists app/controllers/ exists app/helpers/ create app/views/categories exists app/views/layouts/ exists test/functional/ dependency model exists app/models/ exists test/unit/ exists test/fixtures/ identical app/models/category.rb identical test/unit/category_test.rb identical test/fixtures/categories.yml create app/views/categories/_form.rhtml create app/views/categories/list.rhtml create app/views/categories/show.rhtml create app/views/categories/new.rhtml create app/views/categories/edit.rhtml create app/controllers/categories_controller.rb identical test/functional/categories_controller_test.rb create app/helpers/categories_helper.rb create app/views/layouts/categories.rhtml identical public/stylesheets/scaffold.css

Este escript gerou todo o cdigo necessrio para este controller.

Preste ateno na pequena diferena de nomenclatura, nos mudamos do singular para o plural, agora para acessar o controller acessaremos o endereo: http://localhost:3000/categories, na verdade melhor remover o controller antigo para evitar confuses. O cdigo gerado pelo Scaffold O controller Vamos dar uma olhada no cdigo do controller, o controller o lugar para a implementao da lgica da aplicao. Ele interage com o usurio atravs das views e com o banco de dados utilizando os models. Voc deve conseguir ler o cdigo do controller e entender como a aplicao funciona. O cdigo gerado pelo scaffold deve parecer com o seguinte:
class CategoriesController < ApplicationControllerdef index list render :action => 'list' end # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) verify :method => :post, :only => [ :destroy, :create, :update ], :redirect_to => { :action => :list } def list @category_pages, @categories = paginate :categories, :per_page => 10 end def show @category = Category.find(params[:id]) end def new @category = Category.new end def create @category = Category.new(params[:category]) if @category.save flash[:notice] = 'Category was successfully created.' redirect_to :action => 'list' else render :action => 'new' end end def edit @category = Category.find(params[:id]) end def update @category = Category.find(params[:id]) if @category.update_attributes(params[:category]) flash[:notice] = 'Category was successfully updated.' redirect_to :action => 'show', :id => @category else render :action => 'edit' end end def destroy Category.find(params[:id]).destroy redirect_to :action => 'list' end

end

Quando o usurio de uma aplicao rails, por exemplo show, o controller vai executar o cdigo na seo apropriada, neste caso def show, e depois por padro vai rendrizar o controller de mesmo nome, neste caso show.rhtml, este comportamento padro pode ser alterado com facilidade.

render :action, vai renderizar o template referente a outra action, por exemplo a ao def index vai executar o cdigo do metodo list, e depois vai renderizar o template list.rhtml em vez do index.rhtml que no existe. redirect_to, vai um passo adiante, e retorna um 302 moved para o browser, e o redireciona para o metodo indicado do controller, alterando tambm a URL do browser.

Documentao: ActionController::Base O controller utiliza metodos da classe ActiveRecord por exemplo new, save, find, find_all, update_attributes e destroy para mover os dados de e para o banco de dados, perceba que no necessrio escrever nenhum SQL, mas se voc quiserver o SQL que o rails esta gerando, ele escrito para o arquivo development.log. Documentao: ActiveRecord::Base Perceba tambm que uma ao lgica, pela perspectiva do usurio, pode requerer duas passagens pelo cdigo do controller, por exemplo quando um usurio seleciona Edit o controller busca os dados do banco e renderiza o template edit.rhtml e quando o usurio finaliza a edio, o metodo update chamado, que por sua vez atualiza o model e renderiza a ao show. A View Views so o que utilizamos para definir a interface com o usurio. O Rails pode renderizar o HTML final apresentado para o usurio utilizando 3 componentes. Layout Templates Partial em app\views\layouts, em app\views\<controller>, em app\views\<controller>, padro application.rhtml, padro <action>.rhtml padro _<partial>.rhtml ou <controller>.rhtml

Um layout prove cdigo comum a todas as actions, normalmente o inicio e o fim do HTML. Um template prove o cdigo especifico da action, por exemplo o cdigo especifico para o edit ou list. Um Partial prove cdigo comum, utilizado em diversas actions, como por exemplo uma tabela para o layout de um form.

Layout

Padres de nomenclatura do Rails: se existir no diretrio app\views\layouts, um arquivo com o mesmo nome do controller.rhtml este utilizado por padro, a no ser que outro seja configurado manualmente. Sempre existe um arquivo de nome application.rhtml que utilizado caso o layout do controller no seja encontrado, e nenhum outro seja configurado manualmente. O cdigo gerado para o layout no arquivo categories.rhtml parecido com o seguinte:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="content-type" content="text/html;charset=UTF-8" /> <title>Categories: <%= controller.action_name %></title> <%= stylesheet_link_tag 'scaffold' %> </head> <body> <p style="color: green"><%= flash[:notice] %></p><%= yield %> </body></html>

A maior parte HTML simples, mais algumas poucas entradas de cdigo ruby entre <% e %>, Este layout sera utilizado no processo padro de renderizao, e contem o HTML padro para todas as pginas. O cdigo Ruby do template sera traduzido para HTML em tempo de execuo.

<% controller.action_name %> inclui o nome da action sendo executada no titulo, por exemplo List <% stylesheet_link_tag %> um helper, uma maneira fcil de gerar cdigo HTML, existem dzias de helpers no rails, este gera apenas uma tag <link href=/stylesheets/scaffold.css media=screen rel=Stylesheet type=text/css/> <% yeld %> a chave para o que acontece depois, ele insere o cdigo gerado pela action executada neste ponto do HTML.

Template Padres de nomenclatura do Rails: templates ficam no diretrio apps\controller\`action`.rhtml o cdigo para o new.rhtml gerado pelo scaffold parece com o seguinte:
<h1>New category</h1> <% form_tag :action => 'create' do %> <%= render :partial => 'form' %> <%= submit_tag "Create" %> <% end %> <%= link_to 'Back', :action => 'list' %>

<% form_tag %> vai gerar uma tag <form> com a action correta para a ao especificada do controller, no caso create <%= render :partial %> vai renerizar o cdigo do partial especificado

<% submit_tag %> vai gerar um <input type=submit> com o texto especificado <% link_to %> simplesmente cria um link para a action especificada

Partial Padro de nomenclatura do Rails: o partial foo vai estar em app/`controller`/_foo.rhtml, preste ateno no _ inicial. O scaffold utiliza o mesmo cdigo de formulrio para processar o formulrio de create e edit, o cdigo dele se parece com o seguinte:
<%= error_messages_for 'category' %> <!--[form:category]--> <p><label for="category_category">Category</label><br/> <%= text_field 'category', 'category' %></p> <p><label for="category_created_on">Created on</label><br/> <%= datetime_select 'category', 'created_on' %></p> <p><label for="category_updated_on">Updated on</label><br/> <%= datetime_select 'category', 'updated_on' %></p> <!--[eoform:category]-->

<% error_messages_for %> renderiza um texto com markup HTML se algum erro foi detectado em um envio anterior do formulrio, o HTML gerado parece com o seguinte:
<div class="errorExplanation" id="errorExplanation"> <h2>n errors prohibited this xxx from being saved</h2> <p>There were problems with the following fields:</p> <ul> <li>field_1 error_message_1</li> <li>... ...</li> <li>field_n error_message_n</li> </ul> </div>

Nos vimos o erro em ao no primeiro di, na seo capturando erros, perceba as classes css que podem ser utilizadas para alterar o estilo.

<% text_field %> um helper do rails que gera um <input type=text> para o campo especificado <% datetime_select %> outro helper que gera um conjunto de campos select para a entrada de datas

As pginas para edio e visualizao so similares a de criao, ja a pgina de listagem possui alguns pequenos truques. O controler executa o seguinte cdigo para popular a listagem:
@category_pages, @categories = paginate :category, :per_page => 10

o comando paginate popula uma lista ordenada com per_page registros por vez, e contem toda a lgica de paginao. neste caso a coleo @catetory_pages uma instancia da classe Paginator.

vamos ver como ficou o template para esta ao:


<h1>Listing categories</h1> <table> <tr> <% for column in Category.content_columns %> <th><%= column.human_name %></th> <% end %> </tr> <% for category in @categories %> <tr> <% for column in Category.content_columns %> <td><%=h category.send(column.name) %></td> <% end %> <td><%= link_to 'Show', :action => 'show', :id => category %></td> <td><%= link_to 'Edit', :action => 'edit', :id => category %></td> <td><%= link_to 'Destroy', { :action => 'destroy', :id => category }, :confirm => 'Are you sure?', :method => :post %></td> </tr> <% end %> </table> <%= link_to 'Previous page', { :page => @category_pages.current.previous } if @category_pages.current.previous %> <%= link_to 'Next page', { :page => @category_pages.current.next } if @category_pages.current.next %> <br /> <%= link_to 'New category', :action => 'new' %>

content_columns - retorna a lista de todas as colunas simples, excluindo IDs e relacionamentos human_name - uma funo que retorna um nome mais legivel para um atributo, por exemplo first_name vai ser First name h - faz o escape automtico de caracteres HTML confirm - uma opo para o comando link_to que abre um confirm em javascript antes de seguir o link

Melhorando um pouco o cdigo gerado O Controller:


def list @category_pages, @categories = paginate :categories, :per_page => 10, :order_by => "category" end

Para que a lista ja venha ordenada Bom, por enquanto isto, no proximo Dia com o Rails, vamos adicionar os cadastros de notas e itens, e brincar um pouco com os helpers do Rails. Agora hora de comear o corao da nossa aplicao. 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 prximo dia de Rails. Cada tabela possui uma chave primria chamada id que utilizada tambm para fazer o link entre as tabelas.

Modelo de dados simplificado A tabela ItemsOs campos da tabela Items so os seguintes::


done - 1 quer dizer que a tarefa foi concluida priority - 1 (Prioridade alta) a 5 (prioridade baixa) description - Texto livre, descrevendo o que deve ser feito due_date - Data em que o item ja precisa estar concluido category_id - Link para a categoria que este item pertence (id na tabela categories) note_id - um link para uma nota opcional, descrevendo este item (id na tabela notes) private - 1 quer dizer que esta tarefa esta classificada como Private

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 edio 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

o uso de belongs_to e validates_associated fazem o link entre a tabela Items usando o campo category_id com a tabela Category.

Documentao: ActiveRecord::Associations::ClassMethods Validando a entrada de dados


validates_presence_of protege campos NOT NULL do caso de o usurio esquecer de digitar os dados validates_format_of utiliza expresses regulares para validar o formato dos dados digitados Quando um usurio digita dados em um campo numrico, o Rails vai sempre converter os dados para um numero ? se os dados no forem numricos, sera convertido para zero. Se vo quiser verificar o que o usurio realmente digitou, voc precisa fazer isto antes da converso de dados usando _before_type_cast. validates_inclusion_of verifica a entrada do usurio em uma lista de possveis valores validates_length_of valida o comprimento dos dados.

A tabela Notes Esta tabela contem um campo de texto apenas, para armazenar informaes 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 cdigo que vamos desenvolver, permite que o usurio adicione uma nota para qualquer tarefa. Mas o que acontece se o usurio deleta uma tarefa que estava associada a uma nota? Precisamos encontrar uma maneira de remover a nota tambm, caso contrrio, 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 tambm podemos remove-las, clicando no link Purge completed itens. Colocando este cdigo 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 no ser que no 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

Documentao: ActiveRecord::Callbacks Mais Scaffolding Vamos gerar mais um pouco de cdigo com o scaffold. Vamos gerar cdigo para as tabelas Itens e Notes. Nos ainda no estamos prontos para lidar com as Notas, mas tendo o cdigo gerado, quer dizer que podemos referenciar as notas sem ter um monte de erros chatos no meio do caminho. Da mesma forma que na construo 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 no quando o scaffold perguntar se voc quer sobre escrevela. Mais sobre as views Criando um layout para a aplicao

Nesta altura ja esta ficando claro que todas as pginas da aplicao tero as mesmas primeiras linhas de cdigo, ento comea a fazer sentido mover o layout comum para um layout compartilhado por toda a aplicao. Remova todos os arquivos de app\views\layouts\*.rhtml, e crie um arquivo de nome application.rhtml com o seguinte contedo.
<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 tambm para o <h1>. Eu renomeei o arquivo public/stylesheets/scaffold.css para todo.css para melhor clareza do cdigo, e tambm brinquei um pouquinho com as cores, e bordas da tabela, para criar um layot um pouquinho melhor. Tambm coloquei um pequeno javascript para posicionar o cursos no primeiro campo do tipo input para que o usurio possa sair digitando quando abrir qualquer tela. A tela To Do List O que estou tentando alcanar um look and feel similar a um PalmPilot ou PDAde desktop. O produto final mostrado na imagem a baixo.

Alguns pontos:

Clicando no tick do cabealho da coluna, vai remover todos os itens completados (aqueles marcados com um tick) A listagem pode ser ordenada clicando nos caealhos Pri, Description, Due Date, e Category os valores 0/1 para Done sero convertidos em um icone tick Os itens que ja passaram do Due Date aparecem em vermelho e negrito A existencia de uma nota associada mostrada pelo icone de nota os valores 0/1 para Private so convertidos em um pequeno cadeado Cada item pode ser editado ou removido clicando nos icones a direita da tela Novos itens podem ser adicionados clicando em New To Do na parte de baixo da tela H um boto que mostra a tela de Categories criada no tutorial anterior

O cdigo 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 clicveis so criadas com o comando link_to_image, que por padro procura imagens no diretrio pub/images com um sufixo .png; Clicando na imagem o mtodo especificado sera executado. Adicionando o parametro :confirm gera um popup de confirmao com javascript como antes. Documentao: ActionView::Helpers::UrlHelper Clicando em OK vai chamar o mtodo purge_completed. O novo mtodo 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 ordenao clicando nos cabealhos das colunas Clicando na coluna Pri chama o mtodo list_by_priority . Este mtodo tambm 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 ordenao para a consulta no mtodo padro list, e criamos um mtodo list_by_priority novo. Perceba que precisamos especificar render_action list, pois por padro o rails iria procurar um template de nome list_by_priority (que no existe Adicionando um Helper Os cabealhos para as colunas Note e Private so imagens, mas no so clicaveis. eu decidi criar um pequeno mtodo 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 so bem parecidos com o metodo ja criado, mas se voc tiver problemas com o list_by_category, no prximo tutorial eu mostro o cdigo. Documentao: ActionView::Helpers::UrlHelper Usando Javascript para os botes de navegao onClick um mtodo padro para gerenciar clicks e navegao entre pginas. Entretanto, Rails faz um grande trabalho, para renderizar URLs amigveis, ento precisamos perguntar ao rails Rails qual a URL a ser utilizada. Para um controller e uma action, url_for vai retornar a URL correta. Documentao: ActionController::Base Formatando uma tabela com Partials Eu quiz criar um efeito ajax para a listagem de itens. Partials so a soluo para implementar isto; eles podem ser chamados usando o mtodo render_partial:
<% for item in @items %> <%= render_partial "list_stripes", item %> <% end %>

ou pelo mtodo mais economico render_collection_of_partials:

render_collection_of_partials "list_stripes", @items

Documentao: ActionView::Partials O Rails tambm passa um nmero sequential list_stripes_counter ara o Partial. este sequencial a chave para implementar colorao alternada para a linhas da tabela. Uma maneira simplesmente testar se o nmero 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 cdigo at a o simbolo ? pergunta: O restante da diviso por dois no zero? O rsto da linha uma complicao de um if/then/else que sacrifica a legibilidade por menos cdigo: se a expresso antes do ? for verdadeira retorna o valor antes do : caso contrario retorna o outro valor. A mesma tcnica utilizada para outras formataes 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 Documentao: ActionView::Helpers::DateHelper Tratando excees no Ruby Infelizmente, date_select aceita coisas como 31 de fevereiro. O Rails ento se perde ao tentar salvar esta data no banco de dados. Uma forma de contornar isto capturar esta excesso usando rescue, uma forma de tratar excesses 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 secundria: 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 coleo @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 no fosse o suficiente, este cdigo ja faz o escape de caracteres HTML para voc. Documentao: ActionView::Helpers::FormOptionsHelper Lembre-se que estes dados devem vir de algum lugar - o que significa uma pequena adio de cdigo 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 verso simplificada do cenrio anterior. Deixar listas de valores hard-coded no HTML no sempre uma boa idia - mais fcil alterar listas em tabelas do que valores no cdigo. Entretanto, existem casos em que esta uma soluo vlida, ento 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 tambm o boto New To Do. para melhoras as telas at agora, eu tambm fiz as seguintes alteraes 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 criao 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 vocs :D. Se vocs quiserem uma cpia 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.: agradeo se os leitores que esto 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 vocs ja comecem a se acostumar com os migrations do rails, o cdigo 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 => :null => false t.column "due_date", :date t.column "category_id", :integer, :limit => :null => false t.column "note_id", :integer, :limit => t.column "private", :integer, :limit => :null => false t.column "created_on", :timestamp, :null => false t.column "updated_on", :timestamp, :null => false t.column "done", :integer, :limit => :null => false t.column "priority", :integer, :limit => :null => false end 40, :default => "", 5, 5 3, :default => 0, :default => 0,

5, 5,

:default => 0, :default => 3,

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

A tela de Notas Claro que o Scaffold daentidade Notes, ja cria uma estrutura completa CRUD para as notas, mas no queremos que o usurio acesse isto diretamente. Queremos poder linkar uma nota a uma tarefa ja existente.

E caso ja exista uma nota para esta tarefa, precisamos poder editar ou remove-la.

Primeiro, vamos dar uma olhada novamente na tela de edio de tarefas. Atualmente ela ja ajusta os botes caso exista ou no uma nota para esta tarefa.
<% @heading = "Edit To Do" %> <%= error_messages_for 'item' %> <%= start_form_tag :action => 'update', :id => @item %> <table> <%= render_partial "form" %> <tr> <td><b>Notes: </b></td> <% if @item.note_id.nil? %> <td>None</td> <td><%= link_to_image "note", :controller => "notes", :action => "new", :id => @item.id %></td> <% else %> <td><%=h @item.note.more_notes %></td> <td><%= link_to_image "edit_button", :controller => "notes", :action

=> "edit", :id => @item.note_id %></td> <td><%= link_to_image "delete_button", {:controller => "notes", :action => "destroy", :id => @item.note_id }, :confirm => "Are you sure you want to delete this note?" %></td> <% end %> </tr> </table> <hr /> <%= submit_tag "Save" %> <%= submit_tag "Cancel", {:type => 'button', :onClick=>"parent.location='" + url_for( :action => 'list' ) + "'" } %> <%= end_form_tag %>

A tela de edio de notas. Editar uma nota bastante fcil, segue o template:
<% @heading = "Edit Note" %> <%= start_form_tag :action => 'update', :id => @note %> <%= render_partial "form" %> <%= submit_tag "Save" %> <%= submit_tag "Cancel", {:type => 'button', :onClick=>"parent.location='" + url_for( :controller => 'items', :action => 'list' ) + "'" } %> <%= end_form_tag %>

E o Partial Correspondente:
<table> <tr> <td><label for="note_more_notes">More notes</label></td> <td><%= text_area 'note', 'more_notes' %></td> </tr> </table>

Assim que o update ou destroy de uma nota completado, precisamos voltar a tela de listagem de tarefas.
def update @note = Note.find(params[:id]) if @note.update_attributes(params[:note]) flash[:notice] = 'Note was successfully updated.' redirect_to :controller => 'items', :action => 'list' else render :action => 'edit' end end def destroy Note.find(params[:id]).destroy redirect_to :controller => 'items', :action => 'list' end

Lembrem-se que as regras de integridade referencial, ja definidas no model e estas vo garantir que caso removamos uma nota, as referencias para a mesma sero removidas automaticamente. A tela de criao de Notas.

Criar uma nova nota um pouco mais complicado. O que queremos fazer :

Armazenar a nota na tabela notes. Localizar o ID da nota recem criada. Armazenar o ID da nota recem criada no campo note_id da tarefa associada.

Variveis de sesso so uma tima forma de armazenar valores entre telas, vamos utilizar esta abordagem para armazenar o ID da tarefa associada a nova nota. Documentao: ActionController::Base Primeiro, quando clicamos no link para criar uma nova nota, nos passamos o ID da tarefa associada: app\views\items\edit.rhtml (excerpt)
<td><%= link_to_image "note", :controller => "notes", :action => "new", :id => @item.id %></td>

O mtodo new no controller de notas armazena este ID na sesso: app\controllers\notes_controller.rb (excerpt)


def new @session[:item_id] = @params[:id] @note = Note.new end

O template de New Notes no tem nenhuma novidade:


<% @heading = "New Note" %> <%= start_form_tag :action => 'create' %> <%= render_partial "form" %> <%= submit_tag "Save" %> <%= submit_tag "Cancel", {:type => 'button', :onClick=>"parent.location='" + url_for(:controller => 'items', :action => 'list' ) + "'" } %> <%= end_form_tag %>

O mtodo create pega o ID novamente da sesso, utiliza o mesmo para buscar a tarefa do banco e atualizar o campo note_id na tabela items, e novamente redireciona a tela para a listagem de tarefas. app\controllers\notes_controller.rb (excerpt)
def create @note = Note.new(@params[:note]) if @note.save flash['notice'] = 'Note was successfully created.' @item = Item.find(@session[:item_id]) @item.update_attribute(:note_id, @note.id) redirect_to :controller => 'items', :action => 'list' else render_action 'new' end

end

Algumas alteraes na tela de categorias. No sobrou muita coisa para fazer no sistema agora, alem de pequenos ajustes nas telas criadas no primeiro dia para que tenham o mesmo esquema de navegao por botes. app\views\categories\list.rhtml
<% @heading = "Categories" %> <form action="/categories/new" method="post"> <table> <tr> <th>Category</th> <th>Created</th> <th>Updated</th> </tr> <% for category in @categories %> <tr> <td><%=h category["category"] %></td> <td><%= category["created_on"].strftime("%I:%M %p %d-%b-%y") %></td> <td><%= category["updated_on"].strftime("%I:%M %p %d-%b-%y") %></td> <td><%= link_to_image 'edit', { :action => 'edit', :id => category.id } %></td> <td><%= link_to_image 'delete', { :action => 'destroy', :id => category.id },:confirm => 'Are you sure you want to delete this category?' %></td> </tr> <% end %> </table> <hr /> <input type="submit" value="New Category..." /> <input type="button" value="To Dos" onClick="parent.location='<%= url_for(:controller => 'items', :action => 'list' ) %>'"> </form>

app\views\categories\new.rhtml
<% @heading = "Add new Category" %> <%= error_messages_for 'category' %> <%= start_form_tag :action => 'create' %> <%= render_partial "form" %> <hr /> <input type="submit" value="Save" /> <input type="button" value="Cancel" onClick="parent.location='<%= url_for( :action=> 'list' ) %>'"> <%= end_form_tag %>

app\views\categories\edit.rhtml
<% @heading = "Rename Category" %> <%= error_messages_for 'category' %> <%= start_form_tag :action => 'update', :id => @category %> <%= render_partial "form" %> <hr /> <input type="submit" value="Update" /> <input type="button" value="Cancel" onClick="parent.location='<%= url_for( :action=> 'list' ) %>'"> <%= end_form_tag %>

Navegao pelo sistema O grafo final de navegao pelo sistema mostrado na imagem a baixo. Qualquer cdigo redundante gerado pelo scaffold pode ser removido sem problemas (por exemplo show.rhtml). Esta a beleza do scaffold, ele no te custa nada, ento pode ser simplesmente excluido quando no for necessrio.

Configurando a pgina inicial da aplicao. Primeiro renomeie o arquivo pub.ic/index.html para public/index.html.orig E edite o arquivo: config\routes.rb (excerpt)
map.connect '', :controller => 'items'

Pronto, agora a tela inicial da aplicao a listagem de tarefas.

Você também pode gostar