ruby on rails

120
Ruby On Rails 9 de Julho de 2008

Upload: tiago

Post on 16-Apr-2017

41 views

Category:

Education


0 download

TRANSCRIPT

Page 1: Ruby on rails

Ruby On Rails

9 de Julho de 2008

Page 2: Ruby on rails

Conteúdo

I Sobre essa apostila 3

II Informações Básicas 5

III GNU Free Documentation License 10

IV Ruby on Rails 19

1 O que é o curso Ruby on Rails 20

2 Plano de ensino 212.1 Objetivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212.2 Público Alvo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212.3 Pré-requisitos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212.4 Descrição . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212.5 Metodologia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212.6 Cronograma . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212.7 Programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222.8 Avaliação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232.9 Bibliografia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

3 Iniciando o Ruby on Rails 243.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243.2 Instalação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243.3 Configurando Banco de Dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

4 Gerando Migrações e Modelos de Dados 304.1 Gerando Migrações e Modelos de dados I . . . . . . . . . . . . . . . . . . . . . . . . 30

5 MVC e CONTROLLER 395.1 MVC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395.2 O primeiro controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

6 Layouts e Roteamento 436.1 Layouts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 436.2 Roteamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

1

Page 3: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

7 Scaffolding e Validações 487.1 Scaffolding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 487.2 Validações . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

8 Segundo Controller 568.1 Início . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

9 Extendendo um modelo de dados e Helpers 699.1 Extendendo um modelo de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . 699.2 Usando Helpers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70

10 Relacionamentos 7510.1 Início . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75

11 Belongs to 8111.1 Belongs to . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81

12 Has many, has one, has many through 9112.1 Has many . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9112.2 Has one . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9312.3 Has many, trough . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94

2

Page 4: Ruby on rails

Parte I

Sobre essa apostila

3

Page 5: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Conteúdo

O conteúdo dessa apostila é fruto da compilação de diversos materiais livres publicados na in-ternet, disponíveis em diversos sites ou originalmente produzido no CDTC em http://www.cdtc.org.br.

O formato original deste material bem como sua atualização está disponível dentro da licençaGNU Free Documentation License, cujo teor integral encontra-se aqui reproduzido na seção demesmo nome, tendo inclusive uma versão traduzida (não oficial).

A revisão e alteração vem sendo realizada pelo CDTC ([email protected]), desde outubrode 2006. Criticas e sugestões construtivas são bem-vindas a qualquer tempo.

Autores

A autoria deste conteúdo, atividades e avaliações é de responsabilidade de Diego de AquinoSoares ([email protected]).

O texto original faz parte do projeto Centro de Difusão de Tecnolgia e Conhecimento, que vemsendo realizado pelo ITI em conjunto com outros parceiros institucionais, atuando em conjuntocom as universidades federais brasileiras que tem produzido e utilizado Software Livre, apoiandoinclusive a comunidade Free Software junto a outras entidades no país.

Informações adicionais podem ser obtidas atréves do email [email protected], ou dahome page da entidade, através da URL http://www.cdtc.org.br .

Garantias

O material contido nesta apostila é isento de garantias e o seu uso é de inteira responsabi-lidade do usuário/leitor. Os autores, bem como o ITI e seus parceiros, não se responsabilizamdireta ou indiretamente por qualquer prejuízo oriundo da utilização do material aqui contido.

Licença

Copyright ©2006,Diego de Aquino Soares ([email protected]).

Permission is granted to copy, distribute and/or modify this document under the termsof the GNU Free Documentation License, Version 1.1 or any later version published bythe Free Software Foundation; with the Invariant Chapter being SOBRE ESSA APOS-TILA. A copy of the license is included in the section entitled GNU Free DocumentationLicense.

4

Page 6: Ruby on rails

Parte II

Informações Básicas

5

Page 7: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Sobre o CDTC

Objetivo Geral

O Projeto CDTC visa a promoção e o desenvolvimento de ações que incentivem a dissemina-ção de soluções que utilizem padrões abertos e não proprietários de tecnologia, em proveito dodesenvolvimento social, cultural, político, tecnológico e econômico da sociedade brasileira.

Objetivo Específico

Auxiliar o Governo Federal na implantação do plano nacional de software não-proprietário ede código fonte aberto, identificando e mobilizando grupos de formadores de opinião dentre osservidores públicos e agentes políticos da União Federal, estimulando e incentivando o mercadonacional a adotar novos modelos de negócio da tecnologia da informação e de novos negóciosde comunicação com base em software não-proprietário e de código fonte aberto, oferecendotreinamento específico para técnicos, profissionais de suporte e funcionários públicos usuários,criando grupos de funcionários públicos que irão treinar outros funcionários públicos e atuar comoincentivadores e defensores de produtos de software não proprietários e código fonte aberto, ofe-recendo conteúdo técnico on-line para serviços de suporte, ferramentas para desenvolvimento deprodutos de software não proprietários e de seu código fonte livre, articulando redes de terceiros(dentro e fora do governo) fornecedoras de educação, pesquisa, desenvolvimento e teste de pro-dutos de software livre.

Guia do aluno

Neste guia, você terá reunidas uma série de informações importantes para que você comeceseu curso. São elas:

• Licenças para cópia de material disponível

• Os 10 mandamentos do aluno de Educação a Distância

• Como participar dos fóruns e da wikipédia

• Primeiros passos

É muito importante que você entre em contato com TODAS estas informações, seguindo oroteiro acima.

Licença

Copyright ©2006, Diego de Aquino Soares ([email protected]).

6

Page 8: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

É dada permissão para copiar, distribuir e/ou modificar este documento sob os termosda Licença de Documentação Livre GNU, Versão 1.1 ou qualquer versão posteriorpublicada pela Free Software Foundation; com o Capítulo Invariante SOBRE ESSAAPOSTILA. Uma cópia da licença está inclusa na seção entitulada "Licença de Docu-mentação Livre GNU".

Os 10 mandamentos do aluno de educação online

• 1. Acesso a Internet: ter endereço eletrônico, um provedor e um equipamento adequado épré-requisito para a participação nos cursos a distância.

• 2. Habilidade e disposição para operar programas: ter conhecimentos básicos de Informá-tica é necessário para poder executar as tarefas.

• 3. Vontade para aprender colaborativamente: interagir, ser participativo no ensino a distân-cia conta muitos pontos, pois irá colaborar para o processo ensino-aprendizagem pessoal,dos colegas e dos professores.

• 4. Comportamentos compatíveis com a etiqueta: mostrar-se interessado em conhecer seuscolegas de turma respeitando-os e fazendo ser respeitado pelo mesmo.

• 5. Organização pessoal: planejar e organizar tudo é fundamental para facilitar a sua revisãoe a sua recuperação de materiais.

• 6. Vontade para realizar as atividades no tempo correto: anotar todas as suas obrigações erealizá-las em tempo real.

• 7. Curiosidade e abertura para inovações: aceitar novas idéias e inovar sempre.

• 8. Flexibilidade e adaptação: requisitos necessário a mudança tecnológica, aprendizagense descobertas.

• 9. Objetividade em sua comunicação: comunicar-se de forma clara, breve e transparente éponto-chave na comunicação pela Internet.

• 10. Responsabilidade: ser responsável por seu próprio aprendizado. O ambiente virtual nãocontrola a sua dedicação, mas reflete os resultados do seu esforço e da sua colaboração.

Como participar dos fóruns e Wikipédia

Você tem um problema e precisa de ajuda?

Podemos te ajudar de 2 formas:

A primeira é o uso dos fóruns de notícias e de dúvidas gerais que se distinguem pelo uso:

O fórum de notícias tem por objetivo disponibilizar um meio de acesso rápido a informaçõesque sejam pertinentes ao curso (avisos, notícias). As mensagens postadas nele são enviadas a

7

Page 9: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

todos participantes. Assim, se o monitor ou algum outro participante tiver uma informação queinteresse ao grupo, favor postá-la aqui.Porém, se o que você deseja é resolver alguma dúvida ou discutir algum tópico específico docurso, é recomendado que você faça uso do Fórum de dúvidas gerais que lhe dá recursos maisefetivos para esta prática.

. O fórum de dúvidas gerais tem por objetivo disponibilizar um meio fácil, rápido e interativopara solucionar suas dúvidas e trocar experiências. As mensagens postadas nele são enviadasa todos participantes do curso. Assim, fica muito mais fácil obter respostas, já que todos podemajudar.Se você receber uma mensagem com algum tópico que saiba responder, não se preocupe com aformalização ou a gramática. Responda! E não se esqueça de que antes de abrir um novo tópicoé recomendável ver se a sua pergunta já foi feita por outro participante.

A segunda forma se dá pelas Wikis:

Uma wiki é uma página web que pode ser editada colaborativamente, ou seja, qualquer par-ticipante pode inserir, editar, apagar textos. As versões antigas vão sendo arquivadas e podemser recuperadas a qualquer momento que um dos participantes o desejar. Assim, ela oferece umótimo suporte a processos de aprendizagem colaborativa. A maior wiki na web é o site "Wikipé-dia", uma experiência grandiosa de construção de uma enciclopédia de forma colaborativa, porpessoas de todas as partes do mundo. Acesse-a em português pelos links:

• Página principal da Wiki - http://pt.wikipedia.org/wiki/

Agradecemos antecipadamente a sua colaboração com a aprendizagem do grupo!

Primeiros Passos

Para uma melhor aprendizagem é recomendável que você siga os seguintes passos:

• Ler o Plano de Ensino e entender a que seu curso se dispõe a ensinar;

• Ler a Ambientação do Moodle para aprender a navegar neste ambiente e se utilizar dasferramentas básicas do mesmo;

• Entrar nas lições seguindo a seqüência descrita no Plano de Ensino;

• Qualquer dúvida, reporte ao Fórum de Dúvidas Gerais.

Perfil do Tutor

Segue-se uma descrição do tutor ideal, baseada no feedback de alunos e de tutores.

O tutor ideal é um modelo de excelência: é consistente, justo e profissional nos respectivosvalores e atitudes, incentiva mas é honesto, imparcial, amável, positivo, respeitador, aceita asidéias dos estudantes, é paciente, pessoal, tolerante, apreciativo, compreensivo e pronto a ajudar.

8

Page 10: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

A classificação por um tutor desta natureza proporciona o melhor feedback possível, é crucial, e,para a maior parte dos alunos, constitui o ponto central do processo de aprendizagem.’ Este tutorou instrutor:

• fornece explicações claras acerca do que ele espera, e do estilo de classificação que iráutilizar;

• gosta que lhe façam perguntas adicionais;

• identifica as nossas falhas, mas corrige-as amavelmente’, diz um estudante, ’e explica por-que motivo a classificação foi ou não foi atribuída’;

• tece comentários completos e construtivos, mas de forma agradável (em contraste com umreparo de um estudante: ’os comentários deixam-nos com uma sensação de crítica, deameaça e de nervosismo’)

• dá uma ajuda complementar para encorajar um estudante em dificuldade;

• esclarece pontos que não foram entendidos, ou corretamente aprendidos anteriormente;

• ajuda o estudante a alcançar os seus objetivos;

• é flexível quando necessário;

• mostra um interesse genuíno em motivar os alunos (mesmo os principiantes e, por isso,talvez numa fase menos interessante para o tutor);

• escreve todas as correções de forma legível e com um nível de pormenorização adequado;

• acima de tudo, devolve os trabalhos rapidamente;

9

Page 11: Ruby on rails

Parte III

GNU Free Documentation License

10

Page 12: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

(Traduzido pelo João S. O. Bueno através do CIPSGA em 2001)Esta é uma tradução não oficial da Licençaa de Documentação Livre GNU em Português

Brasileiro. Ela não é publicada pela Free Software Foundation, e não se aplica legalmente a dis-tribuição de textos que usem a GFDL - apenas o texto original em Inglês da GNU FDL faz isso.Entretanto, nós esperamos que esta tradução ajude falantes de português a entenderem melhora GFDL.

This is an unofficial translation of the GNU General Documentation License into Brazilian Por-tuguese. It was not published by the Free Software Foundation, and does not legally state thedistribution terms for software that uses the GFDL–only the original English text of the GFDL doesthat. However, we hope that this translation will help Portuguese speakers understand the GFDLbetter.

Licença de Documentação Livre GNU Versão 1.1, Março de 2000

Copyright (C) 2000 Free Software Foundation, Inc.59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

É permitido a qualquer um copiar e distribuir cópias exatas deste documento de licença, masnão é permitido alterá-lo.

INTRODUÇÃO

O propósito desta Licença é deixar um manual, livro-texto ou outro documento escrito "livre"nosentido de liberdade: assegurar a qualquer um a efetiva liberdade de copiá-lo ou redistribui-lo,com ou sem modificações, comercialmente ou não. Secundariamente, esta Licença mantémpara o autor e editor uma forma de ter crédito por seu trabalho, sem ser considerado responsávelpelas modificações feitas por terceiros.

Esta Licença é um tipo de "copyleft"("direitos revertidos"), o que significa que derivações dodocumento precisam ser livres no mesmo sentido. Ela complementa a GNU Licença Pública Ge-ral (GNU GPL), que é um copyleft para software livre.

Nós fizemos esta Licença para que seja usada em manuais de software livre, por que softwarelivre precisa de documentação livre: um programa livre deve ser acompanhado de manuais queprovenham as mesmas liberdades que o software possui. Mas esta Licença não está restrita amanuais de software; ela pode ser usada para qualquer trabalho em texto, independentementedo assunto ou se ele é publicado como um livro impresso. Nós recomendamos esta Licença prin-cipalmente para trabalhos cujo propósito seja de introdução ou referência.

APLICABILIDADE E DEFINIÇÕES

Esta Licença se aplica a qualquer manual ou outro texto que contenha uma nota colocada pelodetentor dos direitos autorais dizendo que ele pode ser distribuído sob os termos desta Licença.

11

Page 13: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

O "Documento"abaixo se refere a qualquer manual ou texto. Qualquer pessoa do público é umlicenciado e é referida como "você".

Uma "Versão Modificada"do Documento se refere a qualquer trabalho contendo o documentoou uma parte dele, quer copiada exatamente, quer com modificações e/ou traduzida em outralíngua.

Uma "Seção Secundária"é um apêndice ou uma seção inicial do Documento que trata ex-clusivamente da relação dos editores ou dos autores do Documento com o assunto geral doDocumento (ou assuntos relacionados) e não contém nada que poderia ser incluído diretamentenesse assunto geral (Por exemplo, se o Documento é em parte um livro texto de matemática, aSeção Secundária pode não explicar nada de matemática).

Essa relação poderia ser uma questão de ligação histórica com o assunto, ou matérias relaci-onadas, ou de posições legais, comerciais, filosóficas, éticas ou políticas relacionadas ao mesmo.

As "Seções Invariantes"são certas Seções Secundárias cujos títulos são designados, comosendo de Seções Invariantes, na nota que diz que o Documento é publicado sob esta Licença.

Os "Textos de Capa"são certos trechos curtos de texto que são listados, como Textos de CapaFrontal ou Textos da Quarta Capa, na nota que diz que o texto é publicado sob esta Licença.

Uma cópia "Transparente"do Documento significa uma cópia que pode ser lida automatica-mente, representada num formato cuja especificação esteja disponível ao público geral, cujosconteúdos possam ser vistos e editados diretamente e sem mecanismos especiais com editoresde texto genéricos ou (para imagens compostas de pixels) programas de pintura genéricos ou(para desenhos) por algum editor de desenhos grandemente difundido, e que seja passível deservir como entrada a formatadores de texto ou para tradução automática para uma variedadede formatos que sirvam de entrada para formatadores de texto. Uma cópia feita em um formatode arquivo outrossim Transparente cuja constituição tenha sido projetada para atrapalhar ou de-sencorajar modificações subsequentes pelos leitores não é Transparente. Uma cópia que não é"Transparente"é chamada de "Opaca".

Exemplos de formatos que podem ser usados para cópias Transparentes incluem ASCII sim-ples sem marcações, formato de entrada do Texinfo, formato de entrada do LaTex, SGML ou XMLusando uma DTD disponibilizada publicamente, e HTML simples, compatível com os padrões, eprojetado para ser modificado por pessoas. Formatos opacos incluem PostScript, PDF, formatosproprietários que podem ser lidos e editados apenas com processadores de texto proprietários,SGML ou XML para os quais a DTD e/ou ferramentas de processamento e edição não estejamdisponíveis para o público, e HTML gerado automaticamente por alguns editores de texto comfinalidade apenas de saída.

A "Página do Título"significa, para um livro impresso, a página do título propriamente dita,mais quaisquer páginas subsequentes quantas forem necessárias para conter, de forma legível,o material que esta Licença requer que apareça na página do título. Para trabalhos que nãotenham uma página do título, "Página do Título"significa o texto próximo da aparição mais proe-minente do título do trabalho, precedendo o início do corpo do texto.

12

Page 14: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

FAZENDO CÓPIAS EXATAS

Você pode copiar e distribuir o Documento em qualquer meio, de forma comercial ou nãocomercial, desde que esta Licença, as notas de copyright, e a nota de licença dizendo que estaLicença se aplica ao documento estejam reproduzidas em todas as cópias, e que você não acres-cente nenhuma outra condição, quaisquer que sejam, às desta Licença.

Você não pode usar medidas técnicas para obstruir ou controlar a leitura ou confecção decópias subsequentes das cópias que você fizer ou distribuir. Entretanto, você pode aceitar com-pensação em troca de cópias. Se você distribuir uma quantidade grande o suficiente de cópias,você também precisa respeitar as condições da seção 3.

Você também pode emprestar cópias, sob as mesmas condições colocadas acima, e tambémpode exibir cópias publicamente.

FAZENDO CÓPIAS EM QUANTIDADE

Se você publicar cópias do Documento em número maior que 100, e a nota de licença doDocumento obrigar Textos de Capa, você precisará incluir as cópias em capas que tragam, clarae legivelmente, todos esses Textos de Capa: Textos de Capa da Frente na capa da frente, eTextos da Quarta Capa na capa de trás. Ambas as capas também precisam identificar clara elegivelmente você como o editor dessas cópias. A capa da frente precisa apresentar o titulo com-pleto com todas as palavras do título igualmente proeminentes e visíveis. Você pode adicionaroutros materiais às capas. Fazer cópias com modificações limitadas às capas, tanto quanto estaspreservem o título do documento e satisfaçam a essas condições, pode ser tratado como cópiaexata em outros aspectos.

Se os textos requeridos em qualquer das capas for muito volumoso para caber de formalegível, você deve colocar os primeiros (tantos quantos couberem de forma razoável) na capaverdadeira, e continuar os outros nas páginas adjacentes.

Se você publicar ou distribuir cópias Opacas do Documento em número maior que 100, vocêprecisa ou incluir uma cópia Transparente que possa ser lida automaticamente com cada cópiaOpaca, ou informar, em ou com, cada cópia Opaca a localização de uma cópia Transparentecompleta do Documento acessível publicamente em uma rede de computadores, a qual o públicousuário de redes tenha acesso a download gratuito e anônimo utilizando padrões públicos deprotocolos de rede. Se você utilizar o segundo método, você precisará tomar cuidados razoavel-mente prudentes, quando iniciar a distribuição de cópias Opacas em quantidade, para assegurarque esta cópia Transparente vai permanecer acessível desta forma na localização especificadapor pelo menos um ano depois da última vez em que você distribuir uma cópia Opaca (direta-mente ou através de seus agentes ou distribuidores) daquela edição para o público.

É pedido, mas não é obrigatório, que você contate os autores do Documento bem antes deredistribuir qualquer grande número de cópias, para lhes dar uma oportunidade de prover vocêcom uma versão atualizada do Documento.

13

Page 15: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

MODIFICAÇÕES

Você pode copiar e distribuir uma Versão Modificada do Documento sob as condições das se-ções 2 e 3 acima, desde que você publique a Versão Modificada estritamente sob esta Licença,com a Versão Modificada tomando o papel do Documento, de forma a licenciar a distribuiçãoe modificação da Versão Modificada para quem quer que possua uma cópia da mesma. Alémdisso, você precisa fazer o seguinte na versão modificada:

A. Usar na Página de Título (e nas capas, se houver alguma) um título distinto daquele do Do-cumento, e daqueles de versões anteriores (que deveriam, se houvesse algum, estarem listadosna seção "Histórico do Documento"). Você pode usar o mesmo título de uma versão anterior seo editor original daquela versão lhe der permissão;

B. Listar na Página de Título, como autores, uma ou mais das pessoas ou entidades responsá-veis pela autoria das modificações na Versão Modificada, conjuntamente com pelo menos cincodos autores principais do Documento (todos os seus autores principais, se ele tiver menos quecinco);

C. Colocar na Página de Título o nome do editor da Versão Modificada, como o editor;

D. Preservar todas as notas de copyright do Documento;

E. Adicionar uma nota de copyright apropriada para suas próprias modificações adjacente àsoutras notas de copyright;

F. Incluir, imediatamente depois das notas de copyright, uma nota de licença dando ao públicoo direito de usar a Versão Modificada sob os termos desta Licença, na forma mostrada no tópicoabaixo;

G. Preservar nessa nota de licença as listas completas das Seções Invariantes e os Textos deCapa requeridos dados na nota de licença do Documento;

H. Incluir uma cópia inalterada desta Licença;

I. Preservar a seção entitulada "Histórico", e seu título, e adicionar à mesma um item dizendopelo menos o título, ano, novos autores e editor da Versão Modificada como dados na Página deTítulo. Se não houver uma sessão denominada "Histórico"no Documento, criar uma dizendo otítulo, ano, autores, e editor do Documento como dados em sua Página de Título, então adicionarum item descrevendo a Versão Modificada, tal como descrito na sentença anterior;

J. Preservar o endereço de rede, se algum, dado no Documento para acesso público a umacópia Transparente do Documento, e da mesma forma, as localizações de rede dadas no Docu-mento para as versões anteriores em que ele foi baseado. Elas podem ser colocadas na seção"Histórico". Você pode omitir uma localização na rede para um trabalho que tenha sido publicadopelo menos quatro anos antes do Documento, ou se o editor original da versão a que ela se refirader sua permissão;

K. Em qualquer seção entitulada "Agradecimentos"ou "Dedicatórias", preservar o título da

14

Page 16: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

seção e preservar a seção em toda substância e fim de cada um dos agradecimentos de contri-buidores e/ou dedicatórias dados;

L. Preservar todas as Seções Invariantes do Documento, inalteradas em seus textos ou emseus títulos. Números de seção ou equivalentes não são considerados parte dos títulos da seção;

M. Apagar qualquer seção entitulada "Endossos". Tal sessão não pode ser incluída na VersãoModificada;

N. Não reentitular qualquer seção existente com o título "Endossos"ou com qualquer outrotítulo dado a uma Seção Invariante.

Se a Versão Modificada incluir novas seções iniciais ou apêndices que se qualifiquem comoSeções Secundárias e não contenham nenhum material copiado do Documento, você pode optarpor designar alguma ou todas aquelas seções como invariantes. Para fazer isso, adicione seustítulos à lista de Seções Invariantes na nota de licença da Versão Modificada. Esses títulos preci-sam ser diferentes de qualquer outro título de seção.

Você pode adicionar uma seção entitulada "Endossos", desde que ela não contenha qual-quer coisa além de endossos da sua Versão Modificada por várias pessoas ou entidades - porexemplo, declarações de revisores ou de que o texto foi aprovado por uma organização como adefinição oficial de um padrão.

Você pode adicionar uma passagem de até cinco palavras como um Texto de Capa da Frente, e uma passagem de até 25 palavras como um Texto de Quarta Capa, ao final da lista de Textosde Capa na Versão Modificada. Somente uma passagem de Texto da Capa da Frente e uma deTexto da Quarta Capa podem ser adicionados por (ou por acordos feitos por) qualquer entidade.Se o Documento já incluir um texto de capa para a mesma capa, adicionado previamente porvocê ou por acordo feito com alguma entidade para a qual você esteja agindo, você não podeadicionar um outro; mas você pode trocar o antigo, com permissão explícita do editor anterior queadicionou a passagem antiga.

O(s) autor(es) e editor(es) do Documento não dão permissão por esta Licença para que seusnomes sejam usados para publicidade ou para assegurar ou implicar endossamento de qualquerVersão Modificada.

COMBINANDO DOCUMENTOS

Você pode combinar o Documento com outros documentos publicados sob esta Licença, sobos termos definidos na seção 4 acima para versões modificadas, desde que você inclua na com-binação todas as Seções Invariantes de todos os documentos originais, sem modificações, e listetodas elas como Seções Invariantes de seu trabalho combinado em sua nota de licença.

O trabalho combinado precisa conter apenas uma cópia desta Licença, e Seções InvariantesIdênticas com multiplas ocorrências podem ser substituídas por apenas uma cópia. Se houvermúltiplas Seções Invariantes com o mesmo nome mas com conteúdos distintos, faça o título de

15

Page 17: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

cada seção único adicionando ao final do mesmo, em parênteses, o nome do autor ou editororigianl daquela seção, se for conhecido, ou um número que seja único. Faça o mesmo ajustenos títulos de seção na lista de Seções Invariantes nota de licença do trabalho combinado.

Na combinação, você precisa combinar quaisquer seções entituladas "Histórico"dos diver-sos documentos originais, formando uma seção entitulada "Histórico"; da mesma forma combinequaisquer seções entituladas "Agradecimentos", ou "Dedicatórias". Você precisa apagar todas asseções entituladas como "Endosso".

COLETÂNEAS DE DOCUMENTOS

Você pode fazer uma coletânea consitindo do Documento e outros documentos publicadossob esta Licença, e substituir as cópias individuais desta Licença nos vários documentos comuma única cópia incluida na coletânea, desde que você siga as regras desta Licença para cópiaexata de cada um dos Documentos em todos os outros aspectos.

Você pode extrair um único documento de tal coletânea, e distribuí-lo individualmente sobesta Licença, desde que você insira uma cópia desta Licença no documento extraído, e siga estaLicença em todos os outros aspectos relacionados à cópia exata daquele documento.

AGREGAÇÃO COM TRABALHOS INDEPENDENTES

Uma compilação do Documento ou derivados dele com outros trabalhos ou documentos se-parados e independentes, em um volume ou mídia de distribuição, não conta como uma Ver-são Modificada do Documento, desde que nenhum copyright de compilação seja reclamado pelacompilação. Tal compilação é chamada um "agregado", e esta Licença não se aplica aos outrostrabalhos auto-contidos compilados junto com o Documento, só por conta de terem sido assimcompilados, e eles não são trabalhos derivados do Documento.

Se o requerido para o Texto de Capa na seção 3 for aplicável a essas cópias do Documento,então, se o Documento constituir menos de um quarto de todo o agregado, os Textos de Capado Documento podem ser colocados em capas adjacentes ao Documento dentro do agregado.Senão eles precisarão aparecer nas capas de todo o agregado.

TRADUÇÃO

Tradução é considerada como um tipo de modificação, então você pode distribuir traduçõesdo Documento sob os termos da seção 4. A substituição de Seções Invariantes por traduçõesrequer uma permissão especial dos detentores do copyright das mesmas, mas você pode incluirtraduções de algumas ou de todas as Seções Invariantes em adição às versões orignais dessasSeções Invariantes. Você pode incluir uma tradução desta Licença desde que você também in-clua a versão original em Inglês desta Licença. No caso de discordância entre a tradução e a

16

Page 18: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

versão original em Inglês desta Licença, a versão original em Inglês prevalecerá.

TÉRMINO

Você não pode copiar, modificar, sublicenciar, ou distribuir o Documento exceto como expres-samente especificado sob esta Licença. Qualquer outra tentativa de copiar, modificar, sublicen-ciar, ou distribuir o Documento é nula, e resultará automaticamente no término de seus direitossob esta Licença. Entretanto, terceiros que tenham recebido cópias, ou direitos de você sob estaLicença não terão suas licenças terminadas, tanto quanto esses terceiros permaneçam em totalacordo com esta Licença.

REVISÕES FUTURAS DESTA LICENÇA

A Free Software Foundation pode publicar novas versões revisadas da Licença de Documen-tação Livre GNU de tempos em tempos. Tais novas versões serão similares em espirito à versãopresente, mas podem diferir em detalhes ao abordarem novos porblemas e preocupações. Vejahttp://www.gnu.org/copyleft/.

A cada versão da Licença é dado um número de versão distinto. Se o Documento especificarque uma versão particular desta Licença "ou qualquer versão posterior"se aplica ao mesmo, vocêtem a opção de seguir os termos e condições daquela versão específica, ou de qualquer versãoposterior que tenha sido publicada (não como rascunho) pela Free Software Foundation. Se oDocumento não especificar um número de Versão desta Licença, você pode escolher qualquerversão já publicada (não como rascunho) pela Free Software Foundation.

ADENDO: Como usar esta Licença para seus documentos

Para usar esta Licença num documento que você escreveu, inclua uma cópia desta Licençano documento e ponha as seguintes notas de copyright e licenças logo após a página de título:

Copyright (c) ANO SEU NOME.É dada permissão para copiar, distribuir e/ou modificar este documento sob os termos da Licençade Documentação Livre GNU, Versão 1.1 ou qualquer versão posterior publicada pela Free Soft-ware Foundation; com as Seções Invariantes sendo LISTE SEUS TÍTULOS, com os Textos daCapa da Frente sendo LISTE, e com os Textos da Quarta-Capa sendo LISTE. Uma cópia da li-cença está inclusa na seção entitulada "Licença de Documentação Livre GNU".

Se você não tiver nenhuma Seção Invariante, escreva "sem Seções Invariantes"ao invés dedizer quais são invariantes. Se você não tiver Textos de Capa da Frente, escreva "sem Textos deCapa da Frente"ao invés de "com os Textos de Capa da Frente sendo LISTE"; o mesmo para osTextos da Quarta Capa.

Se o seu documento contiver exemplos não triviais de código de programas, nós recomenda-mos a publicação desses exemplos em paralelo sob a sua escolha de licença de software livre,

17

Page 19: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

tal como a GNU General Public License, para permitir o seu uso em software livre.

18

Page 20: Ruby on rails

Parte IV

Ruby on Rails

19

Page 21: Ruby on rails

Capítulo 1

O que é o curso Ruby on Rails

Ruby on Rails, ou simplismente Rails, é um framework para aplicações web desenvolvidotodo na linguagem de programação Ruby. Para vocês, que estão fazendo esse curso, devem estáfamiliarizados com essa linguagem de programação.

Rails é uma ferramenta muito poderosa na hora de fazer aplicações web, como vocês poderãover ao longo do curso.

20

Page 22: Ruby on rails

Capítulo 2

Plano de ensino

2.1 Objetivo

Qualificar técnicos e programadores no framework Ruby on Rails

2.2 Público Alvo

Técnicos e Programadores que desejam trabalhar com Ruby on Rails.

2.3 Pré-requisitos

Os usuários deverão ser, necessariamente, indicados por empresas públicas e ter conheci-mento básico acerca da lógica de programação, HTML, Linguagem Ruby, MySQL e CSS.

2.4 Descrição

O curso de Ruby on Rails será realizado na modalidade EAD e utilizará a plataforma Moodlecomo ferramenta de aprendizagem. Ele é composto de um módulo de aprendizado que será dadona primeira semana e um módulo de avaliação que será dado na segunda semana. O materialdidático estará disponível on-line de acordo com as datas pré-estabelecidas no calendário. Aversão utilizada para o Ruby on Rails será a 1.1.

2.5 Metodologia

O curso está dividido da seguinte maneira:

2.6 Cronograma

• Lição 1 - Iniciando o Ruby on Rails;

• Lição 2 - Gerando migrações e modelos de dados;

21

Page 23: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

• Lição 3 - MVC e CONTROLLER;

• Lição 4 - Layouts e Roteamento;

• Lição 5 - Scaffolding e Validações;

• Lição 6 - Segundo controller;

• Lição 7 - Extendendo um modelo de dados e Helpers.

• Lição 8 - Relacionamentos;

• Lição 9 - Belongs to

• Lição 10 - has many, has one, e has many, through

As lições contém o contéudo principal. Elas poderão ser acessadas quantas vezes forem neces-sárias, desde que esteja dentro da semana programada. Ao final de uma lição, você receberáuma nota de acordo com o seu desempenho. Responda com atenção às perguntas de cada lição,pois elas serão consideradas na sua nota final. Caso sua nota numa determinada lição for menordo que 6.0, sugerimos que você faça novamente esta lição.

Ao final do curso será disponibilizada a avaliação referente ao curso. Tanto as notas das liçõesquanto a da avaliação serão consideradas para a nota final. Todos os módulos ficarão visíveispara que possam ser consultados durante a avaliação final.

Aconselhamos a leitura da "Ambientação do Moodle"para que você conheça a plataforma deEnsino a Distância, evitando dificuldades advindas do "desconhecimento"sobre a mesma.

Os instrutores estarão a sua disposição ao longo de todo curso. Qualquer dúvida deverá serenviada no fórum. Diariamente os monitores darão respostas e esclarecimentos.

2.7 Programa

O curso Ruby on Rails oferecerá o seguinte conteúdo:

• Iniciando o Ruby on Rails;

• Gerando migrações e modelos de dados;

• MVC e CONTROLLER;

• Layouts e Roteamento;

• Scaffolding e Validações;

• Segundo controller;

• Extendendo um modelo de dados e Helpers.

• Relacionamentos;

• Belongs to

• has many, has one, e has many, through

22

Page 24: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

2.8 Avaliação

Toda a avaliação será feita on-line.Aspectos a serem considerados na avaliação:

• Iniciativa e autonomia no processo de aprendizagem e de produção de conhecimento;

• Capacidade de pesquisa e abordagem criativa na solução dos problemas apresentados.

Instrumentos de avaliação:

• Participação ativa nas atividades programadas.

• Avaliação ao final do curso.

• O participante fará várias avaliações referente ao conteúdo do curso. Para a aprovação eobtenção do certificado o participante deverá obter nota final maior ou igual a 6.0 de acordocom a fórmula abaixo:

• Nota Final = ((ML x 7) + (AF x 3)) / 10 = Média aritmética das lições

• AF = Avaliações

2.9 Bibliografia

• Site oficial: http://www.rubyonrails.org;

• Rails para sua diversão e lucro.

• http://forum.rubyonbr.org/forums/4/topics/2503

23

Page 25: Ruby on rails

Capítulo 3

Iniciando o Ruby on Rails

Essa lição tem uma parte introdutória ao Rails e a instalação do mesmo.

3.1 Introdução

Ruby on Rails é um poderoso Framework para aplicações web. O curso é voltado para desen-volvedores já familiarizados com algum ambiente de desenvolvimento Web, seja usando Linux ouWindows. Por razões de tempo, o curso assume que o desenvolvedor já tenha conhecimento depelo menos uma linguagem de programação, de um banco de dados relacional qualquer e queseja capaz de estabelecer um ambiente para essa linguagem e banco de dados em sua plata-forma de escolha, ou seja, que tenha o conhecimento técnico para acompanhar os passos dadosno curso sem maiores problemas. O curso não assume, porém, que o desenvolvedor saiba fazero mesmo para o Rails e explica isso nos detalhes necessários.

Um certo conhecimento do Ruby é requerido, embora a linguagem seja simples e poderosao suficiente para ser inteligível para qualquer desenvolvedor com uma experiência razoável emoutra linguagem de alto nível. Apesar disso, esse tutorial pode não ser tão fácil para desenvolve-dores que estejam dando os seus primeiros passos em programação e em especial programaçãopara a Web?embora eu acredite que desenvolvedores iniciantes possam se beneficiar muito como contato com uma linguagem mais poderosa e com um framework mais elaborado.

Foi usado nesse curso o MySQL na elaboração do tutorial por ele ser um banco de fácil insta-lação, mas você pode usar o que mais se adaptar ao seu ambiente, principalmente, considerandoque o Rails esconde a maior parte da complexidade nessa área e que ele suporta os principaisbancos de dados existentes no mercado. Para começar, vamos assumir que você tenha umbanco de dados instalado e que esteja pronto para instalar o Rails e quaisquer outras bibliotecasnecessárias.

Como é bom que você já tenha se familiarizado com a linguagem Ruby (seria útil que você játenha feito o nosso curso Linguagem Ruby), a de se supor que você já tenha instalado em suamáquina o Ruby.

O Ruby é uma linguagem de alto nível, tão completa quanto uma linguagem poderia ser. Suasintaxe é bem simples, e lembra Ada e Eiffel, com algumas pitadas de Perl em alguns pontos.

3.2 Instalação

Para verificar a versão instalada do Ruby, use o seguinte comando:

24

Page 26: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

ronaldo@minerva: $ ruby -v ruby 1.8.4 (2005-12-24) [i486-l inux]Uma vez que o Ruby esteja instalado, é hora de fazer o mesmo com o Rails. O procedimento

é bem simples:Como usuário root digite: apt-get install railsSimples assim.Novas versões do Rails são instalados por um processo similar. Se você já baixou alguma

versão alguma vez, use o mesmo comando acima para atualizar a sua distribuição para a versãomais recente, lembrando, principalmente, de que a versão 1.1.6 corrige um sério problema desegurança nas versões anteriores e

deve ser a única usada em um servidor público. Com os passos acima finalizados e um bancode dados disponível, você está pronto para começar o desenvolvimento em Rails.

A primeira coisa a fazer, então, é criar o diretório de trabalho da aplicação. Isso é feito com ocomando abaixo:

ronaldo@minerva: /tmp$ rails gtdcreatecreate app/controllerscreate app/helperscreate app/modelscreate app/views/layoutscreate config/environmentscreate componentscreate dbcreate doccreate libcreate lib/taskscreate logcreate public/imagescreate public/javascriptscreate public/stylesheetscreate script/performancecreate script/processcreate test/fixturescreate test/functionalcreate test/integrationcreate test/mocks/developmentcreate test/mocks/testcreate test/unitcreate vendorcreate vendor/plugins...O comando rails gera o esqueleto completo de uma aplicação, pronta para rodar. Esse co-

mando foi criado em seu sistema quando você instalou as bibliotecas necessárias.No esqueleto gerado, cada parte da aplicação tem um local específico para ser colocado.

Isso deriva de uma das filosofias por trás do Rails que pode ser descrita pela frase ?convençãoao invés de configuração?. Convenção, nesse caso, significa que há um acordo entre o frameworke você, o desenvolvedor, de modo que você não precisa de preocupar com certos detalhes que,de outra forma, teriam que se descritos em um arquivo de configuração.

25

Page 27: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

No Rails, basta executar uma determinada ação ou criar um determinado arquivo e as coi-sas funcionarão da maneira que você deseja e espera automaticamente, sem necessidade dequalquer trabalho adicional. A princípio isso pode parecer um pouco restritivo, mas na práticafunciona perfeitamente. E sendo extremamente flexível, o Rails permite que você mude qualquercoisa que precise, quando precisar.

Agora que temos uma aplicação básica, vamos rodá-la e ver o resultado.Uma das vantagens do Rails é que o ciclo de codificar-rodar-e-testar é bem rápido. Basta fazer

uma alteração para ver imediatamente o resultado, sem a necessidade de recarregar processos,esperar a compilação da aplicação ou enviá-la para um servidor especialmente preparado. Omáximo que você terá

que fazer no Rails é reiniciar o servidor Web no caso de alguma mudança fundamental naconfiguração básica da aplicação, algo que ocorre com pouca freqüência.

Para facilitar a vida, o Rails vem com seu próprio servidor Web, utilizando as bibliotecas dopróprio Ruby. Para rodar o servidor, basta usar um dos scripts utilitários presentes em cadaaplicação gerada pelo Rails (veja o diretório script sob o esqueleto da aplicação).

O comando, executado dentro do diretório da aplicação, é o seguinte:ronaldo@minerva: /tmp/gtd$ script/server=> Booting WEBrick...=> Rails application started on http://0.0.0.0:3000style="text-align: justify; font-family: courier new,courier,monospace; font-weight: bold;»=>

Ctrl-C to shutdown server; call with –help for options[2006-09-20 10:31:40] INFO WEBrick 1.3.1[2006-09-20 10:31:40] INFO ruby 1.8.4 (2005-12-24) [i486-linux][2006-09-20 10:31:40] INFO WEBrick::HTTPServer#start: pid=8262 port=3000Como você poder ver, o comando inicia uma instância do servidor WEBrick, capaz de servir

aplicações Rails sem necessidade de qualquer configuração adicional. O servidor roda local-mente e recebe requisições na porta 3000. Sendo um servidor simples e embutido, o WEBricknão é capaz de receber requisições

simultâneas, mas permite que testemos perfeitamente a nossa aplicação. Acessando o ende-reço da aplicação você tem o seguinte:

26

Page 28: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 1

Como mostrado acima, temos uma aplicação inicial rodando que, inclusive, mostra quais sãoos próximos passos para continuar o desenvolvimento da mesma.

3.3 Configurando Banco de Dados

Vamos acatar a sugestão da página inicial e criar e configurar o banco de dados da aplica-ção. Esse primeiro passo é muito importante porque o Rails usa as informações provenientedo schema do banco de dados para gerar automaticamente arquivos e configurações adicionais.Esse é mais um exemplo de convenção ao invés de configuração. Dessa forma, garantir que obanco está funcionando corretamente, configurado para uso da aplicação, é o passo inicial dequalquer desenvolvimento em Rails.

O arquivo de configuração de banco de dados se chamada database.yml e está localizado nodiretório config, junto com os demais arquivos de configuração da aplicação.

Esse arquivo, removendo os comentários (as linhas iniciadas com #), tem o seguinte formatodevelopment:adapter: mysql27

Page 29: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DFdatabase: gtd\_developmentusername: rootpassword:host: lo alhosttest:adapter: mysqldatabase: gtd\_testusername: rootpassword:host: lo alhostprodu tion:adapter: mysqldatabase: gtd\_produ tionusername: rootpassword:host: lo alhostA extensao .yml se refere ao formato do arquivo, que utiliza uma linguagem de domínio cha-

mada YAML. Por hora, basta saber que é uma linguagem de serialização de dados favorecida pordesenvolvedores Ruby e Rails.

Uma aplicação Rails geralmente utiliza três bancos de dados, como demonstrado acima, umpara cada ambiente de desenvolvimento padrão. Um banco de dados é utilizado para o desen-volvimento, onde todas as mudanças são aplicadas. Esse banco tem seu correspondente emum banco de produção, onde modificações somente são aplicadas uma vez que estejam comple-tas. O arquivo permite configurar, inclusive, um banco remoto para onde suas modificações finaisserão redirecionadas?embora geralmente seja melhor utilizar um outro método de implantação,como veremos mais adiante. O terceiro banco mostrado acima é um banco de testes, utilizadopelo Rails para a execução de unit testing. Esse banco deve ser mantido necessariamente àparte já que todos os dados e tabelas presentes no mesmo são excluídos e recriados a cadateste completo efetuado na aplicação.

Vamos criar o banco agora:ronaldo�minerva:~/tmp/gtd\$ mysql -u root -pEnter password:Wel ome to the MySQL monitor. Commands end with ; or g.Your MySQL onne tion id is 8 to server version: 5.0.22-Debian\_0ubuntu6.06.2-logType 'help;' or 'h' for help. Type ' ' to lear the buffer.mysql> reate database gtd;Query OK, 1 row affe ted (0.56 se )mysql> reate database gtd\_test;Query OK, 1 row affe ted (0.53 se )mysql> use gtd;Database hangedmysql>Modificando o arquivo de configuração para desenvolvimento local (database.yml), teríamos

algo assim:

28

Page 30: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DFdevelopment:adapter: mysqldatabase: gtdusername: rootpassword:host: lo alhostso ket: /var/run/mysqld/mysqld.so ktest:adapter: mysqldatabase: gtd\_testusername: rootpassword:host: lo alhostso ket: /var/run/mysqld/mysqld.so kprodu tion:developmentNo caso acima, eu configurei os banco de produção e desenvolvimento para apontarem para

o mesmo local já que o desenvolvimento está limitado à minha máquina. Essa é uma estratégiainteressante a usar quando queremos testar uma aplicação nas duas situações localmente semnos preocuparmos em copiar o banco a todo momento?mesmo que o Rails permita isso compouco mais do que alguns comandos. Eu precisei também acrescentar o método de transportesocket à configuração já que em minha máquina, especificamente, o MySQL não permite cone-xões de rede em sua configuração padrão. Outros tipos bancos de dados são configurados demaneira similar, mudando o parâmetro adapter e quaisquer outras configurações necessárias.Uma maneira mais simples de gerar uma arquivo de configuração específico para o banco de da-dos que você está usando é já invocar o comando para gerar o esqueleto da aplicação passandoum parâmetro para isso. Por exemplo:

rails –database=oracle gtd

O comando acima gera o mesmo esqueleto da aplicação, com a única diferença de que oarquivo database.yml será um pouco diferente para levar em conta as diferenças do Oracle. Vol-tando à nossa aplicação, como um arquivo de configuração importante foi mudado, o servidorWeb precisa ser reiniciado. Isso acontece porque ele somente lê essas configurações no iníciode execução. Esse é um dos raros casos em que um servidor de desenvolvimento precisa serreiniciado já que o Rails recarrega praticamente qualquer modificação feita na aplicação quandoestá no modo de desenvolvimento.

Agora temos uma aplicação e um banco de dados configurado. Com isso já podemos iniciaro processo de desenvolvimento propriamente dito.

Para acesso ao banco de dados, o Rails usa classes de dados conhecidas como models, oumodelos de dados. Essas classes mascaram os detalhes s̈órdidosd̈o acesso ao banco de dadosprovidenciando uma interface fácil e intuitiva ao desenvolvedor. Em qualquer aplicação, teremospelo menos um modelo de dados para utilizar na mesma. E a maneira mais fácil de fazer isso écriar automaticamente a tabela no banco de dados, utilizando um dos scripts do Rails.

O Rails automaticamente entende que todas as tabelas criadas no banco para uso em mode-los, ou classes de dados, satisfarão a duas condições: terão um nome plural em inglês e terão

29

Page 31: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

uma chave primária surrogada inteira e auto-incrementada chamada id. É possível mudar ambasas condições, mas inicialmente ficaremos com elas para não ter que modificar o que o Rails gerae usa automaticamente.

Para facilitar o desenvolvimento, a aplicação desenvolvida está em inglês, que espero sejasimples o suficiente mesmo para a compreensão do leitor que não domine plenamente este idi-oma. Considerando a profusão de termos em inglês em nossa área acho que isso não será umproblema. O uso do inglês evita que tenhamos que pensar em alguns detalhes de tradução eglobalização enquanto estamos aprendendo o Rails. Mais no fim do tutorial explicaremos comomudar algumas das coisas que o Rails por como padrão.

30

Page 32: Ruby on rails

Capítulo 4

Gerando Migrações e Modelos deDados

Essa lição tem como objetivo mostrar como alterar qualquer natureza no banco de dados.

4.1 Gerando Migrações e Modelos de dados I

Para manter portabilidade e facilitar o controle de versionamento do banco de dados, o Railsutiliza algo chamado migrations (migrações). São scripts em Ruby descrevendo modificações dequalquer natureza no banco de dados. Migrações são a maneira mais fácil de acrescentar tabelase classes de dados ao Rails e é o que utilizaremos agora. A primeira tabela que vamos criar seráa tabela de contextos. Contextos, no GTD, são, basicamente, os ambientes onde uma ação seráexecutada, como, por exemplo, casa, escritório, e-mail, telefone, etc. Vamos gerar então umamigração para essa primeira modificação no banco:ronaldo�minerva:~/tmp/gtd\$ s ript/generate migration reate_ ontexts reate db/migrate reate db/migrate/001_ reate_ ontexts.rb

O script generate é uma das ferramentas cruciais do Rails que utilizaremos ao longo de todonosso curso, sendo usado, como o nome indica, para criar basicamente qualquer estrutura queprecisamos em uma aplicação. O seu primeiro parâmetro é o tipo de objeto que estamos ge-rando, seu segundo parâmetro é o nome desse objeto e quaisquer outros parâmetro adicionaisindicam opções de geração. No caso acima, estamos gerando uma migração, cujo nome é cre-ate_contexts.

No Rails, existem muitas convenções quanto ao uso de nomes, que são seguidas peloscomandos do mesmo, com conversões automáticas quando necessário. No caso acima, porexemplo, o nome que usamos será automaticamente convertido em um nome de classe, queno Ruby são expressas com capitalização alternada, como você poderá ver abaixo. O resultadodo comando é um arquivo novo, prefixado pela versão da migração, que foi criado no diretóriodb/migrate. Esse prefixo é usado para controlar quais alterações um banco precisa sofrer parachegar a uma versão específica. Editando o arquivo, temos o seguinte: lass CreateContexts < A tiveRe ord::Migrationdef self.up

31

Page 33: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DFenddef self.downendendO Rails criou uma classe derivada (<) da classe ActiveRecord::Migration com dois métodos.

Esses métodos, por estarem prefixados por self, indicam no Ruby que os mesmos podem serchamados diretamente na classe (métodos estáticos na terminologia do C++, C# e do Java), semnecessidade de uma instância. O método self.up é utilizado para efetuar as modificações. Oseu oposto, self.down, é usado para desfazer essas modificações caso você esteja revertendopara uma versão anterior do banco. Dependendo da forma como os métodos forem escritos, elesserão realmente complementares. Obviamente é possível que modificações sejam efetuadas quenão são reversíveis. Nesse caso, o Rails emitirá um erro se uma migração para uma versão maisbaixa for tentada. Vamos editar o arquivo agora para incluir a tabela que desejamos criar: lass CreateContexts < A tiveRe ord::Migrationdef self.up reate_table : ontexts do |t|t. olumn :name, :stringendenddef self.downdrop_table : ontextsendend

Para efetuar suas migrações, o Rails utiliza uma linguagem de domínio que permite especificaroperações de banco de dados de forma abstrata, que não está presa a um servidor específico.É possível executar operações diretamente via SQL no banco, mas isso não é recomendado porquebrar essa abstração.

No caso acima, o método create_table dentro de self.up serve para especificar a tabela queserá criada. Esse método recebe um bloco como parâmetro (indicado pela palavra-chave do) queespecifica as colunas que serão adicionadas.

Blocos são uma das partes mais interessantes do Ruby e vale a estudar um pouco o que elespodem fazer já que o uso dos mesmos é extenso em qualquer aplicação na linguagem, incluindo oRails. No Ruby, os blocos são valores de primeira classe, que pode ser atribuídos e manipuladoscomo qualquer outro objeto.

Um outro detalhe a manter em mente é o uso extensivo de símbolos no Ruby. No caso acima,o próprio nome da tabela é denotado por um símbolo. Símbolos são similares a strings, com adiferença fundamental que são internalizados e existem com uma única instância que permite umuso transparente e eficiente dos mesmos.

Voltando ao nosso código, como a tabela terá somente uma coluna inicialmente, que é o nomedo contexto, somente precisamos de uma linha, identificando o nome da coluna e seu tipo. Mais àfrente veremos outros tipos e opções. O método self.down, como explicando, realiza a operaçãoinversa destruindo a tabela.

Agora é hora de aplicar essa migração ao banco, subindo sua versão. Para isso, temos outilitário rake, que é parte do Ruby. O rake é o equivalente Ruby do make, sendo capaz de realizartarefas descritas em um Rakefile, da mesma forma que o make faz uso de um Makefile. O Rails

32

Page 34: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

vem com seu próprio Rakefile que permite a execução de várias de suas tarefas necessárias,incluindo db:migrate, que aplica as migrações ainda não efetuadas a um banco. Rodamos ocomando da seguinte forma:ronaldo�minerva:~/tmp/gtd$ rake db:migrate(in /tmp/gtd)== CreateContexts: migrating ==================================================-- reate_table(: ontexts)-> 1.2089s== CreateContexts: migrated (1.2092s) =========================================

Se observamos o banco agora, teremos o seguinte schema:ronaldo�minerva:~/tmp/gtd$ mysql -u root -p gtdmysql> show tables;+---------------+| Tables_in_gtd |+---------------+| ontexts || s hema_info |+---------------+2 rows in set (0.00 se )Duas tabelas foram criadas, contexts e schema_info. A primeira é a que especificamos. A

segunda descreve informações do banco para o Rails, para controlar o versionamento do mesmo.A tabela contexts ficou assim:diego�debianvm:~/tmp/gtd$ mysql -u root -pmysql> des ribe ontexts;+-------+--------------+------+-----+---------+----------------+| Field | Type | Null | Key | Default | Extra |+-------+--------------+------+-----+---------+----------------+| id | int(11) | NO | PRI | NULL | auto_in rement || name | var har(255) | YES | | NULL | |+-------+--------------+------+-----+---------+----------------+2 rows in set (0.00 se )

Como você pode ver, não é preciso especificar a chave primária surrogada, que é automati-camente criada pelo Rails. A tabela schema_info possui os seguintes dados:ronaldo�minerva:~/tmp/gtd$ mysql -u root -p gtdmysql> sele t * from s hema_info;+---------+| version |+---------+| 1|+---------+1 row in set (0.00 se )

33

Page 35: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Esse número de versão será incrementado ou decrementado dependendo da direção da mi-gração.

A migração também criou o arquivo db/schema.rb, contendo uma representação do banco emRuby, que pode ser usada para recriar a estrutura do mesmo em qualquer banco suportado peloRails, usando uma outra tarefa do rake.

O arquivo é auto-gerado e não deve ser modificado manualmente. Seu formato pode ser vistoabaixo:# This file is autogenerated. Instead of editing this file, please use the# migrations feature of A tiveRe ord to in rementally modify your database, and# then regenerate this s hema definition.A tiveRe ord::S hema.define(:version => 1) do reate_table " ontexts", :for e => true do |t|t. olumn "name", :stringendend

Note apenas que essa representação é limitada às tabelas em si e não inclui chaves es-trangeiras, triggers ou stored procedures. O próximo passo agora é criar a classe de dadoscorrespondente à tabela. Para isso, usamos o comando abaixo:ronaldo�minerva:~/tmp/gtd\$ s ript/generate model ontextexists app/models/exists test/unit/exists test/fixtures/ reate app/models/ ontext.rb reate test/unit/ ontext_test.rb reate test/fixtures/ ontexts.ymlexists db/migrateAnother migration is already named reate_ ontexts: db/migrate/001_ reate_ ontexts.rb

O Rails utiliza uma estratégia de desenvolvimento (conhecidas como patterns de maneirageral) chamada de MVC (Model-View-Controller). Essa estratégia separa os componentes daaplicação em partes distintas que não só facilitam o desenvolvimento como também facilitam amanutenção. O que estamos vendo no momento são os models, que correspondem à camada deacesso ao banco de dados, implementada no Rails por um componente denominado ActiveRe-cord. O comando acima gera toda estrutura de suporte ao modelo, incluindo testes e migraçõespara o mesmo. Como geramos a nossa migração inicial manualmente, o Rails nos informa quejá existe uma com o mesmo nome que ele pretendia gerar e que ele está pulando esse passo.O arquivo responsável pela implementação do modelo está em app/model/context.rb e contémapenas o seguinte: lass Context < A tiveRe ord::Baseend

Essas duas linhas, apoiadas pelo Rails, já providenciam uma riqueza de implementação quenos permite recuperar, inserir e atualizar dados no banco, e executar uma série de outras opera-ções complexas sem a necessidade de qualquer comando SQL direto.

Vamos criar agora um novo modelo correspondendo à tabela de projetos. Vamos deixar queo Rails gere a migração dessa vez. O comando é:

34

Page 36: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DFronaldo�minerva:~/tmp/gtd\$ s ript/generate model proje texists app/models/exists test/unit/exists test/fixtures/ reate app/models/proje t.rb reate test/unit/proje t_test.rb reate test/fixtures/proje ts.ymlexists db/migrate reate db/migrate/002_ reate_proje ts.rbVocê pode ver que o Rails gerou um template da migração, e basta editá-la: lass CreateProje ts < A tiveRe ord::Migrationdef self.up reate_table :proje ts do |t|# t. olumn :name, :stringendenddef self.downdrop_table :proje tsendendO arquivo final seria, com base no que pensamos até o momento: lass CreateProje ts < A tiveRe ord::Migrationdef self.up reate_table :proje ts do |t|t. olumn :name, :stringt. olumn :des ription, :textendenddef self.downdrop_table :proje tsendendVocê não precisa se preocupar em criar todos os campos inicialmente. Você pode sempre

usar outra migração para isso. Rodando o comando rake para carregar as migrações mais umavez, temos o seguinte:ronaldo�minerva:~/tmp/gtd\$ rake db:migrate(in /home/ronaldo/tmp/gtd)== CreateProje ts: migrating ==================================================-- reate_table(:proje ts)-> 0.1863s== CreateProje ts: migrated (0.1865s) =========================================

O resultado final do banco seria:

35

Page 37: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DFronaldo�minerva:~/tmp/gtd\$ mysql -u root -p gtdmysql> show tables;+---------------+| Tables_in_gtd |+---------------+| ontexts || proje ts || s hema_info |+---------------+3 rows in set (0.00 se )mysql> des ribe proje ts;+-------------+--------------+------+-----+---------+----------------+| Field | Type | Null | Key | Default | Extra |+-------------+--------------+------+-----+---------+----------------+| id | int(11) | NO | PRI | NULL | auto_in rement || name | var har(255) | YES | | NULL | || des ription | text | YES | | NULL | |+-------------+--------------+------+-----+---------+----------------+3 rows in set (0.00 se )mysql> sele t * from s hema_info;+---------+| version |+---------+| 2|+---------+1 row in set (0.00 se )E o arquivo db/schema.rb agora contém:# This file is autogenerated. Instead of editing this file, please use the# migrations feature of A tiveRe ord to in rementally modify your database, and# then regenerate this s hema definition.A tiveRe ord::S hema.define(:version => 2) do reate_table " ontexts", :for e => true do |t|t. olumn "name", :stringend reate_table "proje ts", :for e => true do |t|t. olumn "name", :stringt. olumn "des ription", :textendendPara terminar, vamos gerar a tabela e o modelo de ações:ronaldo�minerva:~/tmp/gtd$ s ript/generate model a tionexists app/models/exists test/unit/

36

Page 38: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DFexists test/fixtures/ reate app/models/a tion.rb reate test/unit/a tion_test.rb reate test/fixtures/a tions.ymlexists db/migrate reate db/migrate/003_ reate_a tions.rbA migração para o modelo seria: lass CreateA tions < A tiveRe ord::Migrationdef self.up reate\_table :a tions do |t|t. olumn :des ription, :stringt. olumn :done, :booleant. olumn : reated_at, :datetimet. olumn : ompleted_at, :datetimet. olumn : ontext_id, :integert. olumn :proje t_id, :integerendenddef self.downdrop_table :a tionsendendCom isso, já temos agora o suficiente em nosso banco de dados para trabalhar um pouco em

nossa aplicação.Um recurso muito útil do Rails é o console, que serve tanto para experimentar com as clas-

ses de dados como para executar uma série de outras funções úteis de teste e manutenção daaplicação. Para acessar o controle, use o seguinte comando:ronaldo�minerva:~/tmp/gtd\$ s ript/ onsoleLoading development environment.>>

Obs.: Para executar esse comando é necessário que você tenha instalado o pacote irb.A exemplo de um console de comando do DOS ou do próprio MySQL, o console do Rails

permite que você insira um comando e veja seus resultados imediatamente:Por exemplo:>> Context. reate(:name => '�Home')=> #<Context:0xb74fbb3 �new_re ord=false, �errors=#<A tiveRe ord::Errors:0xb74 f668�base=#<Context:0xb74fbb3 ...>, �errors={}>, �attributes={"name"=>"�Home", "id"=>1}>O comando acima cria e salva um novo contexto, recebendo como parâmetro o nome do

mesmo, descrito por seu único atributo. O resultado, visto logo depois do comando, é uma repre-sentação textual da classe.

Todos objetos em Ruby, e isso inclui desde números inteiros a classe complexas, possuemuma representação textual. Na maior parte dos casos, o Ruby gera uma representação textual

37

Page 39: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

automática para classes que não especificam a sua própria representação e é isso o que vocêestá vendo acima.

Em uma aplicação, você provavelmente precisará criar um objeto e manipulá-lo antes desalvá-lo. Isso também é possível, como demonstrado abaixo:>> ontext = Context.new=> #<Context:0xb7781bd0 �new_re ord=true, �attributes={"name"=>nil}>>> ontext.name = '�Work'=> "�Work">> ontext.save=> true

No exemplo acima, o objeto é criado, uma de suas propriedade é atribuída e depois dissoo objeto é salvo. A persistência de dados só ocorre na invocação do método save. Outrasoperações são possíveis:>> Context.find(:all)=> [#<Context:0xb76e76d4 �attributes={"name"=>"�Home", "id"=>"1"}>, #<Context:0xb76e7698�attributes={"name"=>"�Work", "id"=>"2"}>℄>> Context.find(2)=> #<Context:0xb76e2328 �attributes={"name"=>"�Work", "id"=>"2"}>>> Context.find(:first, : onditions => ['name like ?', '�Home'℄)=> #<Context:0xb76d6b �attributes={"name"=>"�Home", "id"=>"1"}>

A primeira chamada acima retorna todos os registros correspondente aos contextos. Vemosque o resultado é um array, delimitado por []. A segunda chamada, por sua vez, retorna o objetoespecificado pelo id passado, ou seja, por sua chave primária surrogada. O terceiro método, porfim, retorna o primeiro registro satisfazendo as condições passadas.

O método find possui vários outros parâmetros que servem para gerar queries complexas semesforço, ordenando o resultado, limitando a quantidade de itens retornados, e, para usos maisavançados, criando joins automaticamente. Veremos exemplos extensos disso mais adiante.

A sofisticação das classes geradas pelo ActiveRecord se estende ao ponto da criação demétodos automáticos para várias situações. Algumas delas exploraremos depois, mas vale apena notar aqui pelo menos uma delas: métodos de busca e criação automáticos.

Por exemplo, é possível executar o comando abaixo para encontrar um contexto pelo seunome:>> Context.find_by_name('�Home')=> #<Context:0xb76 dfb8 �attributes={"name"=>"�Home", "id"=>"1"}

O método find_by_name não existia até ser chamado. Isso pode ser visto verificando a exis-tência do método, usando respond_to?, que é um método presente em todas as classes Rubye, ao receber um símbolo que especifica o nome do método a ser testado, devolve uma valorindicando se a classe suporta o método ou não:>> Context.respond_to?(:find_by_name)

38

Page 40: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF=> false>> Context.find_by_name('�Home')=> #<Context:0xb796f0 4 �attributes={"name"=>"�Home", "id"=>"1"}>Como é fácil perceber, a classe não possui aquele método até que ele seja invocado. Uma

classe de dados Rails aceita combinações quaisquer de seus atributos e de algumas operaçõespara gerar esse tipo de métodos.

Um exemplo é find_or_create_by_name, que procuraria um registro e o criaria caso nãofosse encontrado, com o nome passado como parâmetro. Se a classe possuir mais atribu-tos, poderíamos muito bem usar algo como find_or_create_by_name_and_description ou aindafind_by_description_and_completed_at.

É claro que se usarmos isso para todas as chamadas, teremos métodos com nomes absurda-mente longos como find_or_create_by_description_and_created_at_and_completed_at_and_doneque ficariam bem melhor como chamadas separadas usando condições por questões de legibili-dade.

O que estamos fazendo aqui no console é basicamente o que faremos em uma aplicação noque tange à manipulação de dados. Como você pode ver, na maior parte dos casos, o Railsnão exige que o desenvolvedor escreva código SQL, gerando o código necessário por trás dascenas, com toda eficiência possível. E caso seja necessário, há ainda duas possilidades: usarparâmetros mais avançados do método find, que permitem a inserção de fragmentos de SQL emuma operação qualquer; ou ainda, usar métodos como find_by_sql que permitem um execuçãodireta no banco com integração plena com o Rails.

Usar as facilidades do Rails não significa que o desenvolvedor não precisa conhecer SQL;ao contrário, o conhecimento é fundamental, tanto para evitar problemas no uso do próprio Railscomo para todas outras tarefas que existem além do Rails.

Agora que já trabalhamos um pouco com o modelo de dados, vamos passar para a aplicaçãoem si.

39

Page 41: Ruby on rails

Capítulo 5

MVC e CONTROLLER

Essa lição tem como objetivo explicar o que se trata o MVC e como fazer o primeiro CON-TROLLER

5.1 MVC

Como mencionado anteriormente, o Rails utilizado a estratégia MVC. A parte correspondenteao Model existe nas classes de dados e é implementada pelo ActiveRecord. Um segundo compo-nente do Rails, o ActionPack, implementa o V e o C que são respectivamente View e Controller.

Um controller é uma classe responsável por receber as requisições feitas pela aplicação eexecutar as ações necessárias para atender essas requisições. Essas ações, ao serem executa-das, provavelmente causarão mudanças no banco que serão efetuadas, por sua vez, por uma oumais classes de dados.

Finalmente, o resultado será exibido através de uma view, que será retornada ao usuário naforma de HTML, imagens, XML, ou qualquer outra saída aceitável da aplicação.

As três partes da estratégia MVC são independentes no Rails como deveriam ser em qual-quer boa implementação das mesmas. Qualquer modelo de dados pode ser utilizado indepen-dentemente, inclusive em scripts que não tem a ver com a aplicação Web em si, como, porexemplo, tarefas agendadas. Os controllers também são independentes e podem ser usado parasimplesmente efetuar ações que nem mesmo retornam views, atualizando somente o banco oudisparando outro processo qualquer no servidor. As views são, de certa forma, a parte mais de-pendente já que, embora podendo ser utilizadas separadamente, não fazem muito sentido semutilizarem dados gerados por modelos de dados e controllers.

Para identificar qual controller será responsável por atender uma determinada solicitação daaplicação, o Rails utiliza uma mecanismo de roteamento de requisições automático que não ne-cessita de configurações complexas, mas que pode também ser customizado de acordo com asnecessidades de cada aplicação.

A regra principal de roteamento no Rails forma URLs segundo o padrão /controller/action/id,onde controller é a classe responsável por atender a requisição especificada por aquela URL,action é um método dentro da classe e id é um parâmetro opcional passado para identificar umobjeto qualquer sobre o qual a ação será efetuada. Como dito anteriormente, esse padrão podeser modificado e veremos mais sobre isso adiante no tutorial.

40

Page 42: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

5.2 O primeiro controller

Vamos começar criando um controller para lidar com a página inicial de nossa aplicação. Paragerar um controller, usamos o comando abaixo:ronaldo�minerva:~/tmp/gtd$ s ript/generate ontroller homeexists app/ ontrollers/exists app/helpers/ reate app/views/homeexists test/fun tional/ reate app/ ontrollers/home_ ontroller.rb reate test/fun tional/home_ ontroller_test.rb reate app/helpers/home_helper.rb

O nome passado para o comando é home, que será usado para compor todos os arquivosgerados pelo mesmo. Da mesma forma que nos modelos de dados, o Rails cria unit tests paraa classe gerada, que podem ser executados com o comando rake tests. Esse comando rodatodos os unit tests presentes na aplicação e qualquer boa aplicação desenvolvida segundo ametodologia Extreme Programming faz uso extensivo dessa técnica. Veremos esse assunto emmais detalhes em uma outra seção de nosso tutorial. Se você acessar a URL desse controlleragora, você vai receber a seguinte tela:

Figura 2

41

Page 43: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Repare que, nesse caso, eu informei somente /controller/ como a URL, sem passar nema parte correspondente a uma ação ou a um id. Nesse caso, o Rails assume que estamosinvocando a ação index sem id. Esse é um mais exemplo de convenção ao invés de configuração.Ao invés de ter sempre que especificar uma ação padrão em algum lugar, o Rails convencionaque a ação padrão é index, poupando o desenvolvedor sem afetar a aplicação.

Vamos olhar o arquivo app/controllers/home_controller.rb gerado por nosso comando. Comojá mencionamos, o Rails segue uma estrutura fixa de diretórios, poupando mais uma vez o de-senvolvedor. Arquivos relacionados ao banco vão em no diretório db, arquivos de configuraçãoem config e tudo relacionado a MVC vai em app. Dentro de app temos vários outros diretóriosque contém mais partes específicas da aplicação. Em alguns casos, o Rails também adicionaum sufixo ao arquivo, evitando colisões de nomes e problemas estranhos na aplicação, como é ocaso aqui. O arquivo do controller criado contém o seguinte: lass HomeController < Appli ationControllerend

A classe HomeController define quem que irá responder a requisições padrão em /home.Ela herda da classe ApplicationController, que está definida no arquivo application.rb, no mesmodiretório. Sendo assim, qualquer método criado na classe ApplicationController estará automati-camente disponível nos controllers gerados para a aplicação.

O uso de herança serve mais uma vez para beneficiar o desenvolvedor que pode utilizar ummodelo mental familiar para trabalhar sua aplicação, um modelo que é ao mesmo tempo simplese poderoso.

Para criarmos a ação index, basta adicionarmos um método à classe: lass HomeController < Appli ationControllerdef indexendendO princípio que usamos acima é comum a tudo em Rails. Basicamente tudo o que fazemos

em uma aplicação usando o mesmo consiste em extender alguma classe por meio da adição demétodos customizados. Recarregando a página no navegador, ficamos com o seguinte:

42

Page 44: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 3

Vamos que, dessa vez, o Rails identificou a ação a ser executada, mas, como a aplicaçãonão especificou nenhum retorno, houve um erro. Isso aconteceu porque o Rails tentou aplicarautomaticamente uma view para aquela ação do controller. Como a view ainda não existe, temoso erro. Antes de nos aventurarmos em uma view usando o mecanismo de templates do Rails,vamos retornar algum texto por contra própria: lass HomeController < Appli ationControllerdef indexrender :text => "Olá!"endend

O método render, disponível para qualquer controller ou view, gera saída na aplicação, saídaesta que depende de seus parâmetros. No caso acima, estamos renderizando texto puro, comoindicado pelo parâmetro text.

Nossa página agora ficaria assim:

43

Page 45: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 4

Temos a nossa primeira saída em uma aplicação Rails.Como escrever manualmente toda a saída não é o que queremos, vamos criar a nossa pri-

meira view. Para isso, criamos o arquivo app/views/home/index.rhtml. A extensão .rhtml indicaque esse arquivo contém HTML e código Ruby.

Uma outra extensão possível seria .rjs para gerar retorno em JavaScript, comumente utilizadoem aplicações Ajax. Há ainda .rxml, para gerar saída em XML. Outros mecanismos de tem-plate podem usar outras extensões, já que o Rails pode ser configurado para outros mecanismosalternativos.

Por hora, usaremos a linguagem de templates padrão do Rails. Vamos criar o seguinte ar-quivo, então:<p>Olá, mundo!</p>

Se recarregamos a página, notaremos que nada mudou. Isso acontece porque estamosusando render diretamente. Nesse caso, o Rails detecta que alguma saída já foi gerada e nãotenta gerar outra. Basta, então, remover a chamada a render método index, voltando o arquivo a: lass HomeController < Appli ationController

44

Page 46: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DFdef indexendendAgora, recarregando a página, temos:

Figura 5

Sem que haja necessidade de especificar qualquer coisa, o Rails está usando o arquivo ade-quado. Veja que não configuramos qualquer coisa. Criamos um controller, definimos um métodono mesmo, e criamos um arquivo que contém o que queremos que seja retornado por aquelemétodo. A simples existência desses arquivos representa uma cadeia de execução sem queprecisamos nos preocupar com que parte da aplicação faz isso ou aquilo.

Qualquer outra ação que fosse criada dentro desse controller seguiria o mesmo padrão. Onome da ação seria associado a um nome de arquivo dentro de um diretório em app/views cujonome seria o do próprio controller. O Rails também é inteligente ao ponto de responder a umaação mesmo que o método não exista, desde que a view esteja presente no diretório correto.

45

Page 47: Ruby on rails

Capítulo 6

Layouts e Roteamento

Essa lição tem como finalidade facilitar o desenvolvimento da parte visual de uma aplicação.Para isso, o Rails possui um conceito denominado layouts. E o roteamento é para satisfazer aURL que foi especificada.

6.1 Layouts

Na maioria das aplicações Web, as páginas variam somente no seu conteúdo principal, pos-suindo cabeçalhos, rodapés e barras de navegação em comum. Obviamente, um framework cujomaior objetivo é aumentar a produtividade do desenvolvedor não exigiria que o código para esseselementos tivesse que ser repetido em cada view. Um layout funciona como um arquivo raiz,dentro do qual o resultado de uma view é inserido automaticamente.

Mais uma vez favorecendo convenção ao invés de configuração, o Rails define um arquivo pa-drão de layout que é usado automaticamente por qualquer view a não ser que haja especificaçãoem contrário. O Rail também é inteligente o bastante para somente usar um layout em views coma mesma extensão.

O layout padrão para a aplicação fica em um arquivo chamado application.rhtml, dentro dodiretóri app/views/layouts, que não existe ainda.

Se você olhar agora o código gerado pela página que criamos até o momento, você verá oseguinte:

46

Page 48: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 6

Como você pode notar, ão estamos gerando nenhum dos elementos HTML geralmente vistosem uma página comum.

Vamos criar o arquivo application.rhtml no diretório especificado, com o seguinte conteúdo:<html><head><title>GTD</title></head><body><%= yield %></body></html>Temos no arquivo acima, o primeiro exemplo de uso de código Ruby dentro de uma view,

delimitado pelos marcadores <% e %>. Aqueles familiarizados com PHP e ASP reconhecerão oestilo de marcadores, com o uso de <%= objeto %> para retornar conteúdo.

No caso acima, o método especial yield retorna o conteúdo atual gerado pela ação, seja pormeio de uma view ou usando render diretamente. O método yield tem uma conotação especial

47

Page 49: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

no Ruby, servindo para invocar o bloco associado ao contexto. Inserido em um layout do Rails, obloco define a execução da ação, com seu conseqüente retorno de conteúdo.

A nossa página recarregada agora fica como mostrado abaixo:

Figura 7

Não parece muita coisa, mas, olhando o código, você verá o seguinte:

48

Page 50: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 8

É fácil perceber que o código do layout foi aplicado sobre o código da view, gerando a saídafinal da aplicação.

O que precisamos fazer agora é extender o nosso layout para incluir algumas amenidades. Onosso arquivo application.rhtml poderia ser mudado para o seguinte:<html><head><title>GTD</title><%= stylesheet_link_tag "default" %></head><body><%= yield %></body></html>

O método stylesheet_link_tag recebe o nome de uma stylesheet como parâmetro e gera umlink para a mesma. O nosso código gerado agora ficou assim:

49

Page 51: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 9

Note que mais uma vez o Rails assumiu um caminho padrão. Nesse caso, o arquivo é servidodiretamente da raiz da aplicação, que é o diretório public. Você pode criar um arquivo chamadodefault.css no diretório public/stylesheets para adicioná-lo à aplicação. Um exemplo dissoseria:body{ font-family: Verdana, Arial, sans-serif;font-size: 80%;}

Uma coisa a manter em mente é que o layout padrão da aplicação não é, de forma alguma,o único que pode ser gerado. Você pode criar tantos layouts quanto precisar, colocando-os nomesmo diretório, de onde estarão acessíveis a toda aplicação.

Para usar um layout diferente em um controler você poderia fazer algo assim: lass HomeController < Appli ationControllerlayout "home"50

Page 52: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DFdef indexendendO layout cujo nome é home seria especificado no arquivo app/views/layouts/home.rhtml ,

com a mesma funcionalidade do arquivo application.rhtml.Uma outra possibilidade que o Rails oference é sobrescrever um layout para uma única ação,

diretamente no método render. Um exemplo seria: lass HomeController < Appli ationControllerlayout "home"def indexenddef index_spe ialrender :a tion => "list", :layout => "home_spe ial"endendVocê pode também suprimir inteiramente um layout usando :layout => false.Finalmente, você pode usar maneiras específicas de determinar o layout de uma página. Por

exemplo: lass HomeController < Appli ationControllerlayout : hoose_layoutdef indexendprote teddef hoose_layout( urrent_user.admin?) ? "administration" : "normal"endendNo caso acima, dependendo do retorno da chamada ao método admin?, a aplicação poderia

usar o layout definido no arquivo administration.rhtml ou no arquivo normal.rhtml. Como podemosver não há muitos limites para o que pode ser feito.

6.2 Roteamento

Se você acessou a URL raiz da aplicação, você terá notado que ela não mudou, apesar docontroller que criamos. Isso acontece porque o Rails define um arquivo index.html que servecomo padrão. Se removermos esse arquivo do diretório public, ficaremos com a seguinte página:

51

Page 53: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 10

Esse erro indica que o Rails não consegue encontrar um roteamento que satisfaça a URL queespecificamos. Para resolvermos o problema, vamos editar o arquivo config/routes.rb, que defineesses roteamentos. Esse arquivo contém o seguinte codigo:A tionController::Routing::Routes.draw do |map|# The priority is based upon order of reation: first reated -> highest priority.# Sample of regular route:# map. onne t 'produ ts/:id', : ontroller => ' atalog', :a tion => 'view'# Keep in mind you an assign values other than : ontroller and :a tion# Sample of named route:# map.pur hase 'produ ts/:id/pur hase', : ontroller => ' atalog', :a tion => 'pur hase'# This route an be invoked with pur hase_url(:id => produ t.id)# You an have the root of your site routed by hooking up ''# -- just remember to delete publi /index.html.# map. onne t '', : ontroller => "wel ome"# Allow downloading Web Servi e WSDL as a file with an extension# instead of a file named 'wsdl'map. onne t ': ontroller/servi e.wsdl', :a tion => 'wsdl'

52

Page 54: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF# Install the default route as the lowest priority.map. onne t ': ontroller/:a tion/:id'endComo os comentários dizem, esse arquivo define roteamentos de URLs para controllers. Cada

roteamento é examinado quando uma URL é processada pelo Rails, da primeira para a última,sendo a ordem de declaração a definição de prioridade.

Como você pode ver no arquivo acima, a URL padrão que mencionamos, /controller/action/idé a última a ser definida, sendo a aplicada caso não haja instruções em contrário. O roteamentono Rails é bem rico e trabalharemos alguns dos conceitos do mesmo mais adiante. No momento,queremos apenas resolver o problema da página inicial.

Essa página é representada no arquivo acima pela linha abaixo, comentada no momento.# map. onne t '', : ontroller => "wel ome"Para mapear o controller home como nossa página inicial, basta modificar a linha para dizer o

seguinte:map. onne t '', : ontroller => "home"A rota vazia é um sinônimo para a rota raiz e estamos dizendo, com o código acima que se amesma for invocada, a requisição deve ser servida pelo controller que definimos anteriormente.

Note a mistura de aspas simples e aspas duplas nesse arquivo. No Ruby, elas são relativa-mente intercambiáveis e são apenas duas das várias formas de representar texto nessa lingua-gem. A diferença é que as aspas duplas permitem a interpolação de variáveis como veremosmais adiante no tutorial. Eu tendo a favorecer o uso de aspas duplas, nas isso é uma opçãopessoal.

Agora, recarregando nossa página inicial temos:

53

Page 55: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 11

Note que agora o mesmo controller serve dois roteamentos. Essa é apenas uma das possibi-lidades mais simples que estão disponíveis com o roteamento do Rails.

Agora que temos um conhecimento básico de models, controllers e views, podemos combinaro três.

54

Page 56: Ruby on rails

Capítulo 7

Scaffolding e Validações

Para ajudar na rápida prototipação de aplicações, o Rails possui algo chamado de scaffolding.E Validações, como o próprio nome indica, são um modo de garantir a integridade dos dados emuma aplicação.

7.1 Scaffolding

O scaffolding provê uma estrutura básica para operações CRUD (Create, Retrieve, Updateand Delete), ou seja, aquelas operações básicas que temos na manipulação de dados, gerandointerfaces rápidas que podem apoiar o desenvolvimento até que você insira a codificação neces-sária.

Para experimentar com isso e expandir um pouco nossa aplicação, vamos criar um novocontroller, que servirá para administrar os nossos contextos:ronaldo�minerva:~/tmp/gtd$ s ript/generate ontroller ontextsexists app/ ontrollers/exists app/helpers/ reate app/views/ ontextsexists test/fun tional/ reate app/ ontrollers/ ontexts_ ontroller.rb reate test/fun tional/ ontexts_ ontroller_test.rb reate app/helpers/ ontexts_helper.rb

Inicialmente, esse controller é como o que criamos anteriormente, para a home da aplicação,e não possui nenhuma ação pré-definida.

Vamos editar o seu arquivo, em app/controllers/contexts_controller.rb , acrescentando umalinha de código: lass ContextsController < Appli ationControllers affold : ontextend

Agora, acesse a URL desse controller, digitando http://localhost:3000/contexts no navega-dor que você estiver usando:

55

Page 57: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 12

Sem nenhuma linha de código além da inserida acima, temos listagem, inserção, atualizaçãoe remoção de contextos.

Clique em New context para experimentar:

56

Page 58: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 13

Depois, clicando em Create, temos:

57

Page 59: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 14

E, finalmente, clicando em Show, vemos:

58

Page 60: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 15

Note, que na tela acima, o id do objeto já aparece na URL. Nesse caso é 5 porque eu jáexperimentara antes com outros objetos.

O scaffolding é muito útil na geração de controllers básicos de administração para dar apoioà prototipação e ao início do desenvolvimento, principalmente para cadastros, permitindo que odesenvolvedor se foque no que é mais importante para a aplicação. O inconveniente é que asações geradas são bem limitadas e não podem ser editadas ou traduzidas.

Para expandir um pouco a funcionalidade, existe outra forma de geração de scaffolding quepermite uma flexibilidade maior na edição das páginas geradas, que podem, inclusive, ser utiliza-das como base para as páginas finais.

Para isso, use o comando abaixo:ronaldo�minerva:~/tmp/gtd$ s ript/generate s affold ontextexists app/ ontrollers/exists app/helpers/exists app/views/ ontextsexists test/fun tional/dependen y modelexists app/models/59

Page 61: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DFexists test/unit/exists test/fixtures/identi al app/models/ ontext.rbidenti al test/unit/ ontext_test.rbidenti al test/fixtures/ ontexts.yml reate app/views/ ontexts/_form.rhtml reate app/views/ ontexts/list.rhtml reate app/views/ ontexts/show.rhtml reate app/views/ ontexts/new.rhtml reate app/views/ ontexts/edit.rhtmloverwrite app/ ontrollers/ ontexts_ ontroller.rb? [Ynaq℄ yfor e app/ ontrollers/ ontexts_ ontroller.rboverwrite test/fun tional/ ontexts_ ontroller_test.rb? [Ynaq℄ yfor e test/fun tional/ ontexts_ ontroller_test.rbidenti al app/helpers/ ontexts_helper.rb reate app/views/layouts/ ontexts.rhtml reate publi /stylesheets/s affold. ssNote que, para a geração de um scaffold por esse comando, o nome do modelo de dados é

que deve ser passado como parâmetro, e não o nome do controller. O Rails é capaz de derivarum do outro.

Esse comando gera a classe do modelo de dados, o controller e views para cada ação CRUDnecessária. No caso acima, como o model já existia, ele não foi criado, e foi necessário sobres-crever alguns arquivos.

Há duas coisas a serem notadas aqui. Primeiro, o scaffold gerou o seu próprio layout, comsua própria stylesheet. Por causa disso, o layout original da aplicação foi perdido. Uma soluçãoaqui será remover o arquivo app/views/layouts/contexts.rhtml , já que não precisamos dele,e adicionar a stylesheet scaffold.css ao arquivo application.rhtml. Segundo, as views criadascontinuam razoavelmente limitadas, como veremos adiante. Abrindo o novo arquivo gerado docontroller, temos o seguinte: lass ContextsController < Appli ationControllerdef indexlistrender :a tion => 'list'end# GETs should be safe (see http://www.w3.org/2001/tag/do /whenToUseGet.html)verify :method => :post, :only => [ :destroy, : reate, :update ℄,:redire t_to => { :a tion => :list }def list� ontext_pages, � ontexts = paginate : ontexts, :per_page => 10enddef show� ontext = Context.find(params[:id℄)enddef new� ontext = Context.newend

60

Page 62: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DFdef reate� ontext = Context.new(params[: ontext℄)if � ontext.saveflash[:noti e℄ = 'Context was su essfully reated.'redire t_to :a tion => 'list'elserender :a tion => 'new'endenddef edit� ontext = Context.find(params[:id℄)enddef update� ontext = Context.find(params[:id℄)if � ontext.update_attributes(params[: ontext℄)flash[:noti e℄ = 'Context was su essfully updated.'redire t_to :a tion => 'show', :id => � ontextelserender :a tion => 'edit'endenddef destroyContext.find(params[:id℄).destroyredire t_to :a tion => 'list'endendO resultado final, ao ser executado, é muito parecido com o do método scaffold, com a maior

diferença sendo os arquivos de gerados enquanto que, do modo anterior, as ações eram dinami-camente criadas pelo Rails. Algumas amenidades, como paginação, também foram introduzidas.

Uma consideração deve ser feita aqui: o scaffolding é apenas um apoio à prototipação. Elenão deve ser usado como uma muleta, gerando páginas para modificação. Se você quer adminis-trações prontas, existem plugins para o Rails que permitem a geração de páginas bem avançadas.Esses plugins podem ser utilizados para criar administrações prontas a exemplo das geradas peloDjango (o framework equivalente ao Rails para o Python), mas mesmo elas tem suas limitaçõese inflexibilidades. A lição é sempre procurar o que é melhor para a sua aplicação. Os scaffoldingssão uma visão muito linear de uma aplicação e não levam em contra interfaces alternativas demelhor usabilidade.

De qualquer forma, o scaffolding gerado é uma boa oportunidade para ver o relacionamentoentre controllers, views e models. Se você tomar uma ação qualquer como exemplo, verá queela usa a classe de dados para o acesso ao banco e empacota os dados recebidos em variáveisfechadas que serão usadas por uma view, evitando que o código de apresentação se misturecom a lógica de negócio. Mesmo elementos do próprio controller, como paginação, são incluidosnessa discriminação. Veja a view de listagem, por exemplo, descrita no arquivo list.rhtml nodiretório app/views/contexts:<h1>Listing ontexts</h1><table><tr>

61

Page 63: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF<% for olumn in Context. ontent_ olumns %><th><%= olumn.human_name %></th><% end %></tr><% for ontext in � ontexts %><tr><% for olumn in Context. ontent_ olumns %><td><%=h ontext.send( olumn.name) %></td><% end %><td><%= link_to 'Show', :a tion => 'show', :id => ontext %></td><td><%= link_to 'Edit', :a tion => 'edit', :id => ontext %></td><td><%= link_to 'Destroy', { :a tion => 'destroy', :id => ontext }, : onfirm => 'Are you sure?',:post => true %></td></tr><% end %></table><%= link_to 'Previous page', { :page => � ontext_pages. urrent.previous } if� ontext_pages. urrent.previous %><%= link_to 'Next page', { :page => � ontext_pages. urrent.next } if � ontext_pages. urrent.next %><br /><%= link_to 'New ontext', :a tion => 'new' %>O método paginate, usado no controller esconde a complexidade de acesso ao banco fazendo

a busca dos dados, dividindo os itens retornados pelo número de itens requeridos por página eretornando duas variáveis que contém, respectivamente, uma lista das páginas (que pode serusada na view para gerar a numeração das mesmas) e os itens referentes àquela página. Sevocê cadastrar mais de 10 itens, verá isso em funcionamento, embora o scaffold gerado se limiteà navegação para frente e para trás na lista de páginas.

A view acima também exibe uma complexidade maior, utilizando vários métodos para a ge-ração do seu conteúdo. Notadamente, o método link_to que recebe como o texto do link e pa-râmetros adicionais para a geração da URL, que podem ser combinados de várias formas. Porexemplo:<%= link_to : ontroller => "home" %><%= link_to : ontroller => " ontexts", :a tion => "edit", :id => 2 %><%= link_to : ontroller => "login", :using => " ookies" %>

No primeiro caso acima, o método gera a URL raiz para um controller, que como vimos émapeada para a ação index. No segundo caso, uma URL completa é retornada. E no terceiro,um parâmetro using é adicionado à requisição, gerando a seguinte URL: /login?using=cookies.

O método link_to possui várias outras capacidades entre as quais gerar confirmações emJavaScript e criar forms para submissão confiável de links que modificam destrutivamente seusdados, como pode ser visto nos próprios arquivos geados nesse scaffold. Uma olhada na do-cumentação é recomendada para um esclarecimento maior dessas opções. Um outro conceitointeressante apresentado nesse scaffold é o de partials (parciais), que são fragmentos de pági-nas que podem ser compartilhados entre views, da mesma forma que um layout pode usar váriasviews. Veja, por exemplo, a relação entre os dois arquivos abaixo: Primeiro, a view para criaçãode um novo registro:

62

Page 64: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF<h1>New ontext</h1><%= start_form_tag :a tion => ' reate' %><%= render :partial => 'form' %><%= submit_tag "Create" %><%= end_form_tag %><%= link_to 'Ba k', :a tion => 'list' %>Agora, a view para edição de um registro:<h1>Editing ontext</h1><%= start_form_tag :a tion => 'update', :id => � ontext %><%= render :partial => 'form' %><%= submit_tag 'Edit' %><%= end_form_tag %><%= link_to 'Show', :a tion => 'show', :id => � ontext %> |<%= link_to 'Ba k', :a tion => 'list' %>Note que o código gerado é muito parecido (de fato os dois arquivos e suas ações relaciona-

das poderiam ser combinados em um único ponto de acesso) e em ambos os arquivos há umachamada ao método render, usando um partial. Esse partial se encontra no arquivo _form.rhtml,no mesmo diretório. Arquivos que definem partials sempre começam com _ para especificar quesão fragmentos. O conteúdo do arquivo é simples:<%= error_messages_for ' ontext' %><!--[form: ontext℄--><p><label for=" ontext_name">Name</label><br/><%= text_field ' ontext', 'name' %></p><!--[eoform: ontext℄-->

Nesse caso, a parte do formulário que é comum tanta à inserção de um contexto quanto à suaedição.

Partials são uma das grandes facilidades do Rails, sendo utilizados principalmente em aplica-ções Ajax para gerar somente os fragmentos do código necessários para atualizações de partesde uma página. Hoje, com o uso do templates RJS, eles se tornaram ainda mais importantes.

Se você utilizar o scaffold gerado, verá que o código é um pouco mais polido, com mensagensde sucesso e paginação, mas com pouco diferença do que poderia ser feito com uma única linhade código, como visto anteriormente. Por isso, a observação anterior de que scaffolds são utéis,mas não devem se tornar uma regra na aplicação. Um grande exemplo das limitações é o modocomo os scaffolds básicos do Rails geram as tabelas de listagens, fazendo invocações diretasaos atributos de um objeto em um loop com pouca possibilidade de customização.

Veja o fragmento de código abaixo, proveniente da view index.rhtml:<tr><% for olumn in Context. ontent_ olumns %><th><%= olumn.human_name %></th><% end %></tr>63

Page 65: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Os cabeçalhos da tabela de listagem são gerados fazendo uma iteração sobre as colunasde conteúdo do modelo de dados em questão, que não incluem colunas avançadas de relacio-namento e nem são capazes de fazer a tradução adequada de colunas que contenham valoresbooleanos, enumerados e tipos de dados próprios do banco. Além disso, o método human_nameé voltado para o idioma inglês e embora exista a possibilidade de adaptá-lo para outro idiomapor meio de plugins e outros métodos, as limitações dessas técnicas são logo aparentes e, secuidado não for tomado, podem levar a problemas de lógica na aplicação, tornando a mesmadesnecessariamente complexa e violando os princípios básicos de simplicidade sobre os quais oRails foi construído.

Aproveitando momentaneamente o código gerado, vamos avançar um pouco em nossa apli-cação, fazendo uso de mais uma característica no Rails.

7.2 Validações

O modelo de validações que o Rails fornece é bastante completo e pode ser facilmente ex-pandido pelo desenvolvedor caso ele necessite de algo não fornecido por padrão nas bibliotecas.

No caso da nossa classe de dados inicial, precisamos validar pelo menos o fato do usuárioter informado o nome do contexto ao cadastrá-lo.

Para isso, vamos abrir o arquivo da classe, que está em app/models/context.rb, e editá-lo,inserindo uma validação simples: lass Context < A tiveRe ord::Basevalidates_presen e_of :nameendSe você tentar inserir agora um contexto sem nome, verá o seguinte:

64

Page 66: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 16

A menos que o usuário informe o nome do contexto, o Rails não permitirá que o objeto sejasalvo. Note que a validação é feita no modelo de dados e refletida na view através dos métodosda mesma.

O método error_messages_for recolhe todas as mensagens de erro geradas durante uma va-lidação e gera o HTML necessário para exibi-las, que, no caso acima, ainda é formatado pelastylesheet scaffold.css criada anteriormente. Da mesma forma, os métodos responsáveis pelageração dos itens de formulário (como text_field, text_area, select e datetime_select, entre ou-tros) são capazes de verificar se o campo a que se referem não falhou em alguma validação eencapsular a exibição do campo em uma indicação de erro.

Múltiplas validações podem ser efetuadas em um mesmo campo. Por exemplo, para prevenira inserção de nomes duplicados, a seguinte condição poderia ser colocada na classe: lass Context < A tiveRe ord::Basevalidates_presen e_of :namevalidates_uniqueness_of :nameend

O resultado seria:

65

Page 67: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 17

As mensagens de erro geradas são padronizadas no próprio Rails, mas podem ser customi-zadas usando o seguinte formato: lass Context < A tiveRe ord::Basevalidates_presen e_of :namevalidates_uniqueness_of :name, :message => "must be unique"end

Note que o método error_messages_for é voltado para o inglês, formando frases que fazemmais sentido nesse idioma, enquanto o nosso português prefere frases mais elaboradas. É facilsubstituir o método acima por uma implementação mais interessante do mesmo que atenda aoportuguês e como veremos mais adiante, em outra seção do tutorial.

Um último passo seria atualizar esse scaffold para usar o layout que criamos. Para isso, re-mova o arquivo contexts.rhtml e atualize o arquivo application.rhtml (ambos no diretório app/views/layouts)para o seguinte:<html><head><title>GTD</title>

66

Page 68: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF<%= stylesheet_link_tag "default" %><%= stylesheet_link_tag "s affold" %></head><body><%= yield %></body></html>A mudança não é grande, mas significa que nosso scaffold agora usa o layout da aplicação,recebendo todas as modificações provenientes do mesmo. Cabe uma nota aqui sobre as açõesgeradas no scaffold e, por extensão, qualquer outra ação a ser criada em uma aplicação. Ummovimento dentro da comunidade do Rails hoje é o uso de métodos REST, que representam umainterpretação de um conjunto de padrões e filosofias de desenvolvimento Web. Esses métodosusam verbos HTTP mais específicos como PUT e DELETE para efetuar suas ações, ao invés deusar somente os usuais GET e POST. A próxima versão do Rails, ao que tudo indica, virá com umsuporte bem forte para esse tipo de uso. O presente tutorial não entrará nesses detalhes sobreisso, mas deixo ao leitor alguns recursos para que ele possa pesquisar mais sobre o assunto:http://www.xml. om/pub/a/2005/11/02/rest-on-rails.htmlhttp://pezra.barelyenough.org/blog/2006/03/another-rest- ontroller-for-rails/http://www.agilewebdevelopment. om/plugins/simplyrestful

Agora que vimos com o básico funciona no Rails, podemos partir para tentar a nossa própriaimplementação, aprendendo mais sobre com as coisas funcionam.

67

Page 69: Ruby on rails

Capítulo 8

Segundo Controller

Nessa lição, vamos criar o cadastro de projeto, que também é bem simples, de forma maismanual para entendermos como uma aplicação comum processa seus dados. Para facilitar,vamos combinar a criação e edição de um registro em uma única ação.

8.1 Início

O primeiro passo é atualizar o modelo de dados para efetuar validações. No caso no nossomodelo de dados para projetos (que está no arquivo app/models/project.rb), precisamos de va-lidações igualmente simples. A classe ficaria, assumindo que o atributo description é opcional,assim: lass Proje t < A tiveRe ord::Basevalidates_presen e_of :namevalidates_uniqueness_of :nameend

Um segundo passo é gerar um controller para lidar com as requisições relacionadas ao ca-dastro de projetos. Dessa vez, vamos usar o comando de geração do controllers especificandojá as ações que desejamos criar automaticamente. Basicamente, queremos as ações index, list,edit e delete. A ação index existe para não precisarmos de uma regra de roteamento explícitamas será meramente uma invocação da ação list. E ação edit resumirá todas as ações de criaçãoe inserção, simplicando o código. O comando para gerar controller é o seguinte:ronaldo�minerva:~/tmp/gtd$ s ript/generate ontroller proje ts index list edit deleteexists app/ ontrollers/exists app/helpers/ reate app/views/proje tsexists test/fun tional/ reate app/ ontrollers/proje ts_ ontroller.rb reate test/fun tional/proje ts_ ontroller_test.rb reate app/helpers/proje ts_helper.rb reate app/views/proje ts/index.rhtml reate app/views/proje ts/list.rhtml reate app/views/proje ts/edit.rhtml reate app/views/proje ts/delete.rhtml

68

Page 70: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Como você pode ver, os arquivos das views foram automaticamente criados, e se você abrir oarquivo do controller em si (app/controllers/projects_controller.rb) você verá o esqueleto quefoi gerado: lass Proje tsController < Appli ationControllerdef indexenddef listenddef editenddef deleteendend

Invocando o controller em seu navegador, você verá o seguinte:

Figura 18

Nós agora temos um controller que responde perfeitamente às nossas ações, com views jáassociadas, prontas para o uso.

69

Page 71: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Como a ação index é somente uma invocação da ação list, podemos remover o arquivo daview da mesma que está em app/views/projects/index.rhtml e começar a editar as ações. Ométodo index é muito simples: lass Proje tsController < Appli ationControllerdef indexlistrender :a tion => "list"enddef listenddef editenddef deleteendend

O que essas duas linhas nos dizem são: primeiro, invoque o método list, e depois renderize aview da ação list. Mesmo representando uma ação em si, list não passa de um método comumda classe. É somente a mágica do Rails que o torna uma ação, e, sendo assim, ele pode serinvocado normalmente, como qualquer outra método. A segunda linha é necessária porque omecanismo automático de identificação de views procuraria a view relacionada a index e não alist. Precisamos, então, explicitar a que será usada pela aplicação.

Antes de construirmos a ação list, precisamos providenciar um modo de inserirmos registrosdo banco. Para isso, precisamos criar a nossa ação edit. Temos duas situações possíveis: ou ousuário está criando um novo registro, ou ele está atualizando um registro existente. Obviamente,a única diferença entre esses dois métodos é a existência ou não do objeto. E a primeira coisaa ser feita tanto na criação quanto na edição de um objeto é exibir o formulário do mesmo parapreenchimento.

Como ações relacionadas somente a exibição geralmente são associadas ao método HTTPGET enquanto ações que modificam dados e dependem de formulários são invocadas a partir dométodo POST, vamos usar essa diferenciação para identificar se estamos salvando o objeto ousomente exibindo o formulário para edição. E mais, vamos usar a existência ou não do parâmetroid para identificar se estamos lidando com um novo objeto ou com um objeto já existente. Aprimeira versão do nosso método ficaria assim, então: lass Proje tsController < Appli ationControllerdef indexlistrender :a tion => "list"enddef listenddef editif params[:id℄�proje t = Proje t.find(params[:id℄)else�proje t = Proje t.newend

70

Page 72: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DFif request.post?endenddef deleteendendO segundo teste do método verifica se estamos recebendo uma requisição POST. Como men-

cionamos anteriormente, é isso que vamos usar para saber se estamos simplesmente exibindoo formulário (seja vazio ou não), ou se está na hora de persistir o objeto para o banco. Mais àfrente colocaremos esse código.

Caso você ache que o nome edit para esse método é meio enganoso quando um novo re-gistro está sendo criado, você pode usar regras de roteamento genéricas para especificar queinvocações a ações chamadas create, update, e save são redirecionadas para a mesma açãoedit. Separar ou não as suas ações é um decisão que depende obviamente do que você estáfazendo no momento. A tendência com o uso de REST, inclusive, é de ações bem específicas,que respondem a somente um tipo de verbo HTTP.

Agora que o nosso método começou a tomar forma, precisamos editar a view edit.html paraexibir o nosso formulário.

O arquivo presente está assim:<h1>Proje ts#edit</h1><p>Find me in app/views/proje ts/edit.rhtml</p>Precisamos indicar ao usuário se estamos editando um registro existente ou adicionando um

novo registro no próprio cabeçalho da página. Para isso podemos usar o seguinte código:<h1><% if �proje t.new_re ord? %>New<% else %>Editing<% end %> Proje t</h1>Note que estamos usando a variável @project que criamos no método edit. O @ indica uma

variável de instância, que o Rails automaticamente propaga para qualquer view sendo processadaa partir da ação em que a variável foi definida.

Na linha acima, estamos usando o método new_record?, presente em todas as classes dedados derivadas do ActiveRecord para identificar se estamos lidando com um novo registro oucom um registro já existente. Esse método retorna verdadeiro enquanto a classe não for salvapela primeira vez. Com base nisso, exibimos o nosso cabeçalho.

Se você rodar a página, invocando diretamente a URL /projects/edit, verá que já temos algofuncionando:

71

Page 73: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 19

Obviamente, se você tentar usar um id qualquer você verá um erro a menos que tenha inseridoaquele registro no banco de dados. O próximo passo agora é criar o formulário de dados. Paraisso, vamos editar a nossa view:<h1><% if �proje t.new_re ord? %>New<% else %>Editing<% end %> Proje t</h1><%= start_form_tag :a tion => "edit", :id => �proje t %><%= end_form_tag %>O método start_form_tag funciona de maneira muito similar o método link_to, explicado anterior-mente. No caso acima, estamos gerado um formulário apontando para a ação edit e passando oid do objeto que estamos usando, seja novo ou não.

Duas coisas são interessantes nesse chamada: primeiro, o Rails é inteligente o bastante paraperceber que você está passando o objeto como se fosse o seu id e gerar o código necessário au-tomaticamente; segundo, caso o objeto não tenha sido salvo, o Rails suprimirá automaticamenteo id sem que você precise fazer qualquer teste.

O outro método chamado, end_form_tag, simplesmente gera o fechamento do elemento formna página.

Com o cabeçalho do nosso formulário gerado, podemos agora criar campos para edição. Umprojeto possui um nome e um descrição. O primeiro atributo é um texto simples, de uma linha

72

Page 74: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

somente, enquanto o segundo é um texto que pode ter múltiplas linhas. Editando o nosso arquivo,teremos o seguinte:<h1><% if �proje t.new_re ord? %>New<% else %>Editing<% end %> Proje t</h1><%= start_form_tag :a tion => "edit", :id => �proje t %><p><label for="proje t_name">Name:</label><br><%= text_field "proje t", "name" %></p><p><label for="proje t_des ription">Des ription:</label><br><%= text_area "proje t", "des ription", :rows => 5 %></p><%= end_form_tag %>

Essa modificação nos dá o seguinte:

Figura 20

73

Page 75: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Os métodos text_field e text_area são responsáveis pela geração dos elementos de formu-lário vistos acima. O Rails possui dezenas desses métodos, capazes de gerar várias combina-ções possíveis para os tipos de dados suportados automaticamente e permitir extensões se vocêprecisar de algo mais customizado. E você sempre pode gerar o seu código manualmente emsituações especiais.

Veja que cada método recebe um objeto e um atributo a ser gerado. No caso desses métodos,você não precisa usar o @ diretamente: basta passar o nome do objeto e o método saberárecuperá-lo do controller que está sendo executado.

Se você olhar o HTML gerado, verá o seguinte:<p><label for="proje t_name">Name:</label><br><input id="proje t_name" name="proje t[name℄" size="30" type="text" /></p><p><label for="proje t_des ription">Des ription:</label><br><textarea ols="40" id="proje t_des ription" name="proje t[des ription℄" rows="5"></textarea></p>Veja que os métodos geram um formato similar de elementos, criando atributos name e id

específicos para facilitar a vida do desenvolvedor. O atributo id pode ser associado a um elementolabel como demonstrado e o atributo name é o que é enviado ao Rails, gerando automaticamenteuma tabela hash dos dados submetidos pelo formulário que a ação pode usar. Dentro de umcontroller você será capaz de usar params[:project] para acessar diretamente essa tabela hash,como você verá adiante. O próximo passo, agora, é adicionar ações para salvar ou cancelar aedição. Poderíamos ter algo assim:<h1><% if �proje t.new_re ord? %>New<% else %>Editing<% end %> Proje t</h1><%= start_form_tag :a tion => "edit", :id => �proje t %><p><label for="proje t_name">Name:</label><br><%= text_field "proje t", "name" %></p><p><label for="proje t_des ription">Des ription:</label><br><%= text_area "proje t", "des ription", :rows => 5 %></p><p><%= submit_tag "Save" %> or <%= link_to "Can el", :a tion => "list" %></p><%= end_form_tag %>

O formato acima é uma preferência de muitas aplicações Rails e reflete o fato de que a açãopara salvar deveria ser submetida pelo formulário enquanto a ação de cancelamento seria sim-plesmente um retorno a um estado anterior. Mais do que isso, o método acima evita que tenha-mos que descobrir qual botão foi pressionado em nosso formulário. Embora o código para issoseja bem simples, repetí-lo em cada formulário se tornaria rapidamente tedioso, além de forçaruma mistura de apresentação com lógica que queremos evitar a todo custo.

74

Page 76: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Esse é um padrão que você verá repetidamente em aplicações atuais e que faz bastantesentido do ponto de vista de usabilidade.

O resultado seria:

Figura 21

Temos agora um formulário completo funcionando. Precisamos simplesmente salvar o queserá submetido.

Voltando ao nosso controller, ficamos com o seguinte: lass Proje tsController < Appli ationControllerdef indexlistrender :a tion => "list"enddef listenddef editif params[:id℄�proje t = Proje t.find(params[:id℄)75

Page 77: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DFelse�proje t = Proje t.newendif request.post?�proje t.attributes = params[:proje t℄if �proje t.saveredire t_to :a tion => "list"endendenddef deleteendendTodo objeto que representa uma classe de dados no Rails possui um atributo chamado at-

tributes, que representa uma coleção das colunas da tabela equivalentes. Esse atributo aceitareceber uma tabela hash para preenchimento de seus valores. Como mencionado anteriormente,é aqui que os dados gerados pelo Rails na submissão do formulário vem a calhar, servindo paraassociação direta no objeto. Cada item existente em params[:project] no código acima, como,por exemplo, params[:project][:name], preencherá seu item correspondente sem necessidade decódigo adicional.

Continuando o código, tentamos salvar o objeto. Se isso tem sucesso (ou seja, se nenhumavalidação falha) redirecionamos para a página de listagem. Caso contrário, exibimos o formulá-rio novamente. Nesse caso, note que o Rails usará a mesma view, seguindo o comportamentopadrão, efetivamente mostrando o formulário preenchido com os dados postados. Isso aconteceporque os métodos text_field e text_area (e os demais) preenchem automaticamente os elemen-tos de formulário com os valores existentes no objeto que receberam. Isso torna a nossa tarefabem simples.

Se você tentar salvar um formulário sem preencher nada, verá o seguinte agora:

76

Page 78: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 22

Notamos que a validação está funcionando, mas nenhum erro é exibido. Para isso, precisamosda seguinte modificação em nossa view:<h1><% if �proje t.new_re ord? %>New<% else %>Editing<% end %> Proje t</h1><%= error_messages_for "proje t" %><%= start_form_tag :a tion => "edit", :id => �proje t %><p><label for="proje t_name">Name:</label><br><%= text_field "proje t", "name" %></p><p><label for="proje t_des ription">Des ription:</label><br><%= text_area "proje t", "des ription", :rows => 5 %></p><p><%= submit_tag "Save" %> or <%= link_to "Can el", :a tion => "list"%></p><%= end_form_tag %>

77

Page 79: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

O que nos dá:

Figura 23

Se você salvar um objeto agora, verá que somos redirecionados para a ação list que aindaprecisamos criar. Uma listagem é algo bem simples e queremos exibir, inicialmente, somente onome do projeto. Para isto, vamos criar algo bem básico:<h1>Proje ts</h1><table border="1" ellpadding="5" ellspa ing="1"><tr><th>Name</th></tr></table>

Isso nos dá um tabela simples. Agora, queremos exibir os registros criados. Precisamos,obviamente, buscar esses objetos no banco. Modificando o controller ficamos com algo assim: lass Proje tsController < Appli ationControllerdef index

78

Page 80: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DFlistrender :a tion => "list"enddef list�proje ts = Proje t.find :all, :order => "name"enddef editif params[:id℄�proje t = Proje t.find(params[:id℄)else�proje t = Proje t.newendif request.post?�proje t.attributes = params[:proje t℄if �proje t.saveredire t_to :a tion => "list"endendenddef deleteendendUma variável chamada @projects receberá uma lista de todos projetos ordenados pelo atri-

buto name. O método find, já tratado anteriormente, recebe como primeiro parâmetro uma espe-cificação do que retornar ou um id específico. No caso acima, queremos todos registros (all). Osegundo parâmetro, nomeado, recebe a especificação de ordenação.

Um detalhe da chamada acima. Muitas vezes, em código Ruby, você verá um seqüência deparâmetros nomeados no fim de uma chamada. O Ruby não possui parâmetros nomeados em si,mas é capaz de simular isso com o uso de uma tabela hash com último parâmetro de um método.Quando um método usa essa técnica, o Ruby automaticamente permite que os parâmetros sejamdeclarados de forma livre, pelo nome, e os combina no momento da chamada em uma tabela hashque é então passada como parâmetro. Assim, o primeiro parâmetro do método find acima é umsímbolo e o segundo parâmetro é uma tabela hash cujo único elemento é um par definido por umsímbolo e uma string.

Agora, já podemos usar esses objetos em uma view:<h1>Proje ts</h1><table border="1" ellpadding="5" ellspa ing="1"><tr><th>Name</th></tr><% �proje ts.ea h do |proje t| %><table border="1" ellpadding="5" ellspa ing="1"><tr><td><%= proje t.name %></td></tr><% end %></table>79

Page 81: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

No caso acima, o método find retorna um array que pode ou não conter itens.Uma coisa a manter em mente aqui é que, para caso de uso desse método, o retorno pode ser

diferente. Se você estiver usando find com :first, o retorno será um objeto ou o valor nil. Se vocêestiver passando um id específico, um objeto será retornado ou um erro será gerado. Lembre-sedisso sempre que usar o método em suas variações. As variações servem para cobrir as situa-ções mais comuns, e você sempre pode escrever seus próprios métodos usando as combinaçõesacima para obter o resultado que deseja.

Com dois projetos inseridos, o resultado é:

Figura 24

Podemos agora modificar a nossa view para permitir algumas ações sobre esses objetos. Porexemplo:<h1>Proje ts</h1><table border="1" ellpadding="5" ellspa ing="1"><tr><th>Name</th><th>A tions</th></tr>

80

Page 82: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF<% �proje ts.ea h do |proje t| %><tr><td><%= proje t.name %></td><td><%= link_to "Edit", :a tion => "edit", :id => proje t %> or<%= link_to "Delete", :a tion => "delete", :id => proje t %></td></tr><% end %></table>Como você dever ter notado pelas chamadas a link_to na view acima e em outros pontos do

código já exibido, o Rails é capaz de derivar os elementos da uma URL dos parâmetros que estãosendo usados no momento.

O resultado da aplicação da view acima é visto abaixo:

Figura 25

Clicando na edição de um dos itens acima, temos:

81

Page 83: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 26

Como é possível ver, o cabeçalho da página mudou para indicar uma edição e os camposjá vieram preenchidos. Você já pode alterar cada objeto sem problemas. Precisamos agora derefinar alguns aspectos de nosso controller.

Precisamos de um link para criar um novo registro. Isso é facilmente conseguido:<h1>Proje ts</h1><p><%= link_to "New Proje t", :a tion => "edit" %></p><table border="1" ellpadding="5" ellspa ing="1"><tr><th>Name</th><th>A tions</th></tr><% �proje ts.ea h do |proje t| %><tr><td><%= proje t.name %></td><td><%= link_to "Edit", :a tion => "edit", :id => proje t %> or<%= link_to "Delete", :a tion => "delete", :id => proje t %>82

Page 84: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF</td></tr><% end %></table>Um dos problemas em deixar que um registro seja removido com um simples invocação é

que o usuário não tem como reverter a ação. Podemos adicionar um mínimo de proteção com aseguinte alteração:<h1>Proje ts</h1><p><%= link_to "New Proje t", :a tion => "edit" %></p><table border="1" ellpadding="5" ellspa ing="1"><tr><th>Name</th><th>A tions</th></tr><% �proje ts.ea h do |proje t| %><tr><td><%= proje t.name %></td><td><%= link_to "Edit", :a tion => "edit", :id => proje t %> or<%= link_to "Delete", { :a tion => "delete", :id => proje t }, : onfirm => "Are you sure?" %></td></tr><% end %></table>

Note que, por estamos usando um terceiro parâmetro do método link_to, precisamos de adi-cionar chaves ao redor do segundo para que o Ruby não exerça o seu comportamento padrãoe concatene todos os parâmetros livres em um só. No caso acima, tanto o segundo parâmetroda chamada como o terceiro são tabelas hash, embora somente a segunda esteja explicitamenteindicada.

O resultado das modificações acima é o seguinte, como podemos ver ao clicar na ação pararemover o projeto:

83

Page 85: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 27

A ação delete também pode ser protegida de acesso GET diretos com o uso de outra carac-terística do Rails que é a transformação de links em formulários caso a ação seja bem simples.O resultado visual não é muito interessante e você pode observá-lo no código do scaffold que foigerado anteriormente para o modelo de dados de contextos.

Vamos agora inserir a ação para excluir registros, que é bem simples: lass Proje tsController < Appli ationControllerdef indexlistrender :a tion => "list"enddef list�proje ts = Proje t.find :all, :order => "name"enddef editif params[:id℄�proje t = Proje t.find(params[:id℄)else84

Page 86: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF�proje t = Proje t.newendif request.post?�proje t.attributes = params[:proje t℄if �proje t.saveredire t_to :a tion => "list"endendenddef deleteProje t.find(params[:id℄).destroyredire t_to :a tion => "list"endendComo essa ação não possui nenhuma view, o arquivo gerado delete.rhtml também pode ser

excluído.Uma outra alteração que pode ser feita é a exibição de mensagens de dados para algumas

ações. Para isso, vamos usar uma outra característica dos Rails, as variáveis flash. Essas variá-veis são similares a variáveis de sessão, mas somente persistem de uma página para outra. Umavez que a requisição para a qual foram propagadas acaba, elas são automaticamente removidasdo contexto de execução.

Vamos modificar nosso controller mais uma vez: lass Proje tsController < Appli ationControllerdef indexlistrender :a tion => "list"enddef list�proje ts = Proje t.find :all, :order => "name"enddef editif params[:id℄�proje t = Proje t.find(params[:id℄)else�proje t = Proje t.newendif request.post?�proje t.attributes = params[:proje t℄if �proje t.saveflash[:noti e℄ = "The proje t was su essfully saved"redire t_to :a tion => "list"endendend85

Page 87: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DFdef deleteProje t.find(params[:id℄).destroyflash[:noti e℄ = "The proje t was su essfully deleted"redire t_to :a tion => "list"endendPodemos agora usar essas variáveis em nossa view para a ação list, que é para onde as duas

ações em questão retornam:<h1>Proje ts</h1><% if flash[:noti e℄ %><p style=" olor: green; font-style: itali "><%= flash[:noti e℄ %></p><% end %><p><%= link_to "New Proje t", :a tion => "edit" %></p><table border="1" ellpadding="5" ellspa ing="1"><tr><th>Name</th><th>A tions</th></tr><% �proje ts.ea h do |proje t| %><tr><td><%= proje t.name %></td><td><%= link_to "Edit", :a tion => "edit", :id => proje t %> or<%= link_to "Delete", { :a tion => "delete", :id => proje t }, : onfirm => "Are you sure?" %></td></tr><% end %></table>Uma pequena observação que cabe aqui é o uso de símbolos ao invés de strings na referên-

cia a tabelas hash. Símbolos são sempre uma forma de acesso mais interessante por apontaremsempre para uma única instância e poderem ser otimizados pelo interpretador. O Rails geral-mente permite as duas formas de acesso para suas variáveis internas representadas com tabelashash, mas é sempre bom escolher um dos métodos de acesso e manter um padrão em relaçãoao mesmo. Isso facilita a legibilidade da aplicação e evita o aparecimento de erros provenienteda mistura dos dois tipos de acesso.

O resultado é o seguinte, após uma ação de edição:

86

Page 88: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 28

A variável flash[:notice] está disponível somente naquela requisição como você pode verificarrecarregando a página.

87

Page 89: Ruby on rails

Capítulo 9

Extendendo um modelo de dados eHelpers

Essa lição visa extender nosso modelo de dados para aproveitando o nosso controller. Emostrar o que são os Helpers.

9.1 Extendendo um modelo de dados

Algo que podemos fazer agora, para aproveitar o nosso controller é extender o nosso modelode dados. Vamos dizer que precisamos saber se um projeto está ativo ou não. O nosso modelode dados não contém esse atributo, mas podemos gerar uma migração para isso.ronaldo�minerva:~/tmp/gtd$ s ript/generate migration add_status_to_proje texists db/migrate reate db/migrate/004_add_status_to_proje t.rb

Essa é uma migração um pouco diferente porque adiciona uma coluna a um modelo de dados.Editando o arquivo da migração, teríamos algo assim: lass AddStatusToProje t < A tiveRe ord::Migrationdef self.upadd_ olumn :proje ts, :a tive, :booleanProje t.reset_ olumn_informationProje t.update_all "a tive = 1"enddef self.downremove_ olumn :proje ts, :a tiveendend

Nesse caso, estamos adicionando uma coluna e automaticamente atualizando para um valorque achamos mais indicado.

Como o nosso modelo está sendo modificado, precisamos usar o método reset_column_informationpara que o Rails releia as tabelas e recarregue os modelos imediatamente. Na verdade, isso nãoé estritamente necessário pelo uso do método update_all, mas é interessante saber que esse usopode ser necessário em alguns casos. O método update_all atualiza todos os registros usando o

88

Page 90: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

fragmento de SQL passado como parâmetro., pulando quaisquer validações existentes?portanto,use-o com cuidado.

Após rodar o comando rake db:migrate, veremos o nosso banco atualizado completamente.Note que, nesse caso, a migração para um versão anterior simplesmente remove a coluna.

9.2 Usando Helpers

Helpers são classes associadas a cada controller que contém funções utilitárias que são auto-maticamente exportadas para as views associadas ao mesmo. Cada controller tem o seu própriohelper e, a exemplo dos controllers, todos os helpers herdam métodos de uma classe base, queestá definida no arquivo app/helpers/application_helper.rb. Olhando no diretório desse arquivovocê pode ver que para cada controller, um helper já foi gerado.

Precisamos de um método que receba um valor booleano e retorne um representação hu-mana disso. Como talvez precisemos usar esse método em outras views que não as da classede projetos, seria mais interessante inserir o método no helper global da aplicação, modificandoo arquivo application_helper.rb presente no diretório app/helpers. Teríamos, então, algo assim:module Appli ationHelperdef yes_or_no?(value)value ? "Yes" : "No"endend

O método yes_or_no? está agora disponível para qualquer view na aplicação. Vamos usá-lo,modificando a nossa view:<h1>Proje ts</h1><% if flash[:noti e℄ %><p style=" olor: green; font-style: itali "><%= flash[:noti e℄ %></p><% end %><p><%= link_to "New Proje t", :a tion => "edit" %></p><table border="1" ellpadding="5" ellspa ing="1"><tr><th>Name</th><th>A tive</th><th>A tions</th></tr><% �proje ts.ea h do |proje t| %><tr><td><%= proje t.name %></td><td><%= yes_or_no?(proje t.a tive?) %></td><td><%= link_to "Edit", :a tion => "edit", :id => proje t %> or<%= link_to "Delete", { :a tion => "delete", :id => proje t }, : onfirm => "Are you sure?" %></td></tr><% end %></table>

89

Page 91: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Note que podemos agora chamar o método definido no helper e também que temos um novoatributo na classe.

Uma convenção especial do Rails é que, se um atributo representar um valor booleano, vocêpode acrescentar um ponto de interrogação após o método para que ele retorne verdadeiro oufalso diretamente. Isso acontece porque nem todos bancos de dados possuem campos boolea-nos nativos por padrão. No caso do MySQL, por exemplo, valores booleanos são representadospor campos inteiros contendo zero ou um. O uso da interrogação facilita a visualização e uso docampo.

Em várias situações, o Rails é capaz de detectar automaticamente o valor booleano, e o pontode interrogação não é estritamente necessário. Mas é uma boa convenção fazer isso. Não só ocódigo fica mais legível de imediato, como qualquer desenvolvedor Rails será capaz de dizer otipo de dados armazenado no campo.

O resultado agora é:

Figura 29

Obviamente, precisamos editar o fato do projeto estar ativo ou não. Vamos alterar nossa viewde edição:<h1><% if �proje t.new_re ord? %>New<% else %>Editing<% end %> Proje t</h1>

90

Page 92: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF<%= error_messages_for "proje t" %><%= start_form_tag :a tion => "edit", :id => �proje t %><p><label for="proje t_name">Name:</label><br><%= text_field "proje t", "name" %></p><p><label for="proje t_des ription">Des ription:</label><br><%= text_area "proje t", "des ription", :rows => 5 %></p><p><label for="proje t_a tive">A tive:</label><br><%= sele t "proje t", "a tive", [["Yes", true℄, ["No", false℄℄ %></p><p><%= submit_tag "Save" %> or <%= link_to "Can el", :a tion => "list"%></p><%= end_form_tag %>Essa é uma das maneiras de editar o atributo, usando um elemento select.O método select gera automaticamente o código HTML necessário, recebendo parâmetros

similares aos outros métodos e uma lista de valores a serem usados como escolhas. Essesvalores são informados como um array de arrays onde cada item representa um par, contendo adescrição e o valor a ser atribuído. No caso acima, temos descrições Yes e No, correspondendoa valores true e false.

O Rails define uma série de outros métodos capaz de gerar elementos select. Alguns des-ses métodos são especializados em gerar coleções aninhadas, por exemplo, enquanto outrosgeram diversos formatos de data. Antes de experimentar o método genérico acima, consulte adocumentação para ver se existe algum método que lhe atenda.

Lembre-se que, para valores booleanos, para que a atribuição funcione automaticamente, osvalores true e false devem ser necessariamente usados. Caso contrário, a conversão automáticade tipos do Ruby entra em ação e os dados não são processados apropriamente.

O resultado pode ser visto abaixo e testando você verá que ele funciona perfeitamente, alte-rando os valores de acordo com sua escolha sem necessidade de qualquer codificação adicional:

91

Page 93: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 30

Existem pelo menos duas outras diretas maneiras de fazer a edição de um valor booleano.Uma seria usando radio buttons e checkboxes. Testar essas maneiras fica como um exercíciopara o leitor, lembrando que o princípio é o mesmo.

Uma última coisa seria modificar o controller para gerar paginação automática. Isso é facil-mente conseguido com duas atualizações.

Uma no controller: lass Proje tsController < Appli ationControllerdef indexlistrender :a tion => "list"enddef list�proje t_pages, �proje ts = paginate :proje ts, :per_page => 5, :order => "name"enddef editif params[:id℄�proje t = Proje t.find(params[:id℄)92

Page 94: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DFelse�proje t = Proje t.newendif request.post?�proje t.attributes = params[:proje t℄if �proje t.saveflash[:noti e℄ = "The proje t was su essfully saved"redire t_to :a tion => "list"endendenddef deleteProje t.find(params[:id℄).destroyflash[:noti e℄ = "The proje t was su essfully deleted"redire t_to :a tion => "list"endendE outra na view:<h1>Proje ts</h1><% if flash[:noti e℄ %><p style=" olor: green; font-style: itali "><%= flash[:noti e℄ %></p><% end %><p><%= link_to "New Proje t", :a tion => "edit" %></p><table border="1" ellpadding="5" ellspa ing="1"><tr><th>Name</th><th>A tive</th><th>A tions</th></tr><% �proje ts.ea h do |proje t| %><tr><td><%= proje t.name %></td><td><%= yes_or_no?(proje t.a tive?) %></td><td><%= link_to "Edit", :a tion => "edit", :id => proje t %> or<%= link_to "Delete", { :a tion => "delete", :id => proje t }, : onfirm => "Are you sure?" %></td></tr><% end %></table><p><%= pagination_links(�proje t_pages) %></p>A modificação no controller usa um método que já mencionamos anteriormente, paginate,

para encapsular o processo de paginar os registros e gerar uma listagem de páginas (aqui sepa-radas em cinco registros por página). A modificação na view, por sua vez, simplesmente cria umalista de páginas com base na informação retornada pelo método de paginação.

93

Page 95: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Os métodos usados são razoavelmente simples, mas, infelizmente, estão entre dois dos maisineficientes do Rails e devem ser usados com cuidado. No momento, entretando, com a quan-tidade de dados que temos, eles não chegam a representar um problema. O resultado depoispode ser visto abaixo.

Visualizando a primeira página, temos:

Figura 31

E em seguida, a segunda página (note o parâmetro adicional da URL):

94

Page 96: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 32

Terminamos um controller completo - simples, mas funcional - e, inclusive, alteramos o modelode dados por trás do mesmo de acordo com nossa necessidade.

Agora já temos uma base para investigarmos o próximo passo de uma aplicação: relaciona-mentos entre classes de dados.

95

Page 97: Ruby on rails

Capítulo 10

Relacionamentos

Essa lição tem como objetivo mostrar o que são relacionamentos.

10.1 Início

Antes de começarmos a ver os relacionamentos, vamos unir o nosso projeto sob um layoutque pelo menos nos permita navegar mais facilmente. Nosso novo arquivo app/views/layouts/application.rbficaria assim:<html><head><title>GTD</title><%= stylesheet_link_tag "default" %><%= stylesheet_link_tag "s affold" %></head><body><h1 id="header">GTD</h1><ul id="menu"><li><%= link_to_unless_ urrent "&raquo; Contexts", : ontroller => " ontexts" %></li><li><%= link_to_unless_ urrent "&raquo; Proje ts", : ontroller => "proje ts" %></li></ul><div id=" ontents"><%= yield %></div></body></html>

E a stylesheet que estamos usando, default.css, dessa maneira:body{ font-family: Verdana, Arial, sans-serif;font-size: 100%;margin: 0;padding: 0;}h1#header{96

Page 98: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DFba kground- olor: # ; olor: white;padding: 10px 10px 15px;margin: 0;}ul#menu{ padding: 5px 10px;margin: 0;border-bottom: 1px solid # ;}ul#menu li{ display: inline;margin-right: 5px;font-weight: bold;}ul#menu a{ text-de oration: none;font-weight: normal;}div# ontents{ margin: 10px;}div# ontents h1{ font-size: 130%;font-style: itali ;}O resultado final seria:

97

Page 99: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 33

Não é perfeito por causa de alguns defeitos no código HTML (como o uso de dois elementosH1 na página, tabelas com estilos aplicados diretamente, etc) e pelo uso da stylesheet scaf-fold.css, mas já é uma melhoria (desconsiderando, é claro, o fato de que eu não entendo nadade design gráfico). Modificações do primeiro scaffold gerado para contextos ficam como um exer-cício para o leitor. O objetivo é demonstrar como podemos atualizar a aplicação sem mexer emqualquer dos controllers e views posteriormente geradas uma vez que elas tenham sido bemplanejadas.

Para exemplificarmos os relacionamentos, vamos começar com o cadastro de ações. Umaação pertence a um projeto e é executado em um determinado contexto. Isso é suficiente paracomeçarmos o nosso cadastro. Atualizamos primeiro o arquivo application.rhtml para incluir anossa nova área, temos:<html><head><title>GTD</title><%= stylesheet_link_tag "default" %><%= stylesheet_link_tag "s affold" %></head>

98

Page 100: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF<body><h1 id="header">GTD</h1><ul id="menu"><li><%= link_to_unless_ urrent "&raquo; Contexts", : ontroller => " ontexts", :a tion => "list"%></li><li><%= link_to_unless_ urrent "&raquo; Proje ts", : ontroller => "proje ts", :a tion => "list"%></li><li><%= link_to_unless_ urrent "&raquo; A tions", : ontroller => "a tions", :a tion => "list"%></li></ul><div id=" ontents"><%= yield %></div></body></html>Em segundo lugar, geramos o nosso controller. Vamos seguir o mesmo estilo do controller

usado para projetos:ronaldo�minerva:~/tmp/gtd$ s ript/generate ontroller a tions index list edit deleteexists app/ ontrollers/exists app/helpers/ reate app/views/a tionsexists test/fun tional/ reate app/ ontrollers/a tions_ ontroller.rb reate test/fun tional/a tions_ ontroller_test.rb reate app/helpers/a tions_helper.rb reate app/views/a tions/index.rhtml reate app/views/a tions/list.rhtml reate app/views/a tions/edit.rhtml reate app/views/a tions/delete.rhtmlPara facilitar, vamos duplicar a funcionalidade presente no cadastro de projetos. O nosso

controller de ações ficaria assim, inicialmente: lass A tionsController < Appli ationControllerdef indexlistrender :a tion => "list"enddef list�a tion_pages, �a tions = paginate :a tions, :per_page => 5, :order => "des ription"enddef editif params[:id℄�a tion = A tion.find(params[:id℄)else�a tion = A tion.newendif request.post?�a tion.attributes = params[:a tion℄if �a tion.saveflash[:noti e℄ = "The a tion was su essfully saved"redire t_to :a tion => "list"99

Page 101: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DFendendenddef deleteA tion.find(params[:id℄).destroyflash[:noti e℄ = "The a tion was su essfully deleted"redire t_to :a tion => "list"endendSeguido por nossa view de listagem, que ficaria assim:<h1>A tions</h1><% if flash[:noti e℄ %><p style=" olor: green; font-style: itali "><%= flash[:noti e℄ %></p><% end %><p><%= link_to "New A tion", :a tion => "edit" %></p><table border="1" ellpadding="5" ellspa ing="1"><tr><th>Des ription</th><th>Done</th><th>A tions</th></tr><% �a tions.ea h do |a tion| %><tr><td><%= a tion.des ription %></td><td><%= yes_or_no?(a tion.done?) %></td><td><%= link_to "Edit", :a tion => "edit", :id => a tion %> or<%= link_to "Delete", { :a tion => "delete", :id => a tion }, : onfirm => "Are you sure?" %></td></tr><% end %></table><p><%= pagination_links(�a tion_pages) %></p>Em, por fim, da nossa view de edição, que seria:<h1><% if �a tion.new_re ord? %>New<% else %>Editing<% end %> A tion</h1><%= error_messages_for "a tion" %><%= start_form_tag :a tion => "edit", :id => �a tion %><p><label for="a tion_des ription">Des ription:</label><br><%= text_area "a tion", "des ription", :rows => 5 %></p><p><label for="a tion_done">Done:</label><br><%= sele t "a tion", "done", [["Yes", true℄, ["No", false℄℄ %></p>

100

Page 102: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF<p><%= submit_tag "Save" %> or <%= link_to "Can el", :a tion => "list"%></p><%= end_form_tag %>Não tente usar essa view para salvar uma ação ainda. Há um erro sutil na mesma que discu-

tiremos mais adiante. As demais views geradas (index.rhtml e delete.rhtml) podem ser removidasda aplicação pois não serão usadas.

Obviamente, a view acima não inclui todos os atributos presentes na ação. Para incluirmosesses atributos, vamos considerar algumas coisas.

O atributos created_at representa a data de criação da ação. Atributos terminados em _at e_on são automaticamente reconhecidos pelo Rails como representando datas e tempos, respec-tivamente. Além disso, atributos com os nomes created_at, created_on, updated_at e update_onserão automaticamente atualizados pelo Rails de acordo com a necessidade. Todos quando oobjeto for criado e os dois últimos sempre que o objeto for salvo.

Como o Rails obviamente não tem controle sobre o banco de dados, o desenvolvedor é quedeve escolher o tipo de dados certo para os mesmos no banco, usando date no caso dos termina-dos em _at e datetime no caso dos terminados em _on (tipos de dados do MySQL, é claro-adapteconforme a necessidade). Apesar disso, usar um tipo mais ou menos preciso não causa erros eRails tentará conversões sempre que necessário.

No nosso caso, então, não precisamos dar qualquer valor ao atributo created_at para queele seja salvo. Na verdade, como esse é um campo que deveria ser salvo uma única vez, vamoscolocar alguma informação no modelo para que o mesmo não possa ser atribuído em formulários,mas somente em código direto. lass A tion < A tiveRe ord::Baseattr_prote ted : reated_atend

Esse declaração impede que esse atributo seja atualizado automaticamente por qualqueratribuição via formulários ou métodos provindos do servidor. Somente uma atualização direta viacódigo funcionará e como não precisamos disso, não temos que nos preocupar mais com esseatributo. O reverso dessa declaração seria attr_accessible.

O atributo completed_at representa a data em que a ação foi completada. Também não pre-cisamos editá-lo em nossa view, já que ele poderia ser atribuído quando o usuário marcasse aação como completada. Faremos então a mesma coisa com esse atributo, mudando o nossomodelo para: lass A tion < A tiveRe ord::Baseattr_prote ted : reated_at, : ompleted_atend

Como o campo é atribuído automaticamente, o que precisamos fazer é achar alguma formade modificar esse atributo quando o atributo done, que representa o fato da ação estar completa,for marcado como verdadeiro. A solução é sobrescrever o método de escrita do atributo done,algo que o Rails permite justamente para esse tipo de situação: lass A tion < A tiveRe ord::Baseattr_prote ted : reated_at, : ompleted_at

101

Page 103: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DFdef done=(value)if A tiveRe ord::Conne tionAdapters::Column.value_to_boolean(value)self. ompleted_at = DateTime.nowelseself. ompleted_at = nilendwrite_attribute("done", value)endendAqui, estamos usando o momento em que o atributo done é recebido e executando ações

extras. Note o sinal de igual, indicando que esse é um método de atribuição; no Ruby, qualquermétodo terminado com o sinal pode ser automaticamente usado como se fosse um atributo. Ométodo verifica se valor recebido foi verdadeiro usando um método interno do ActiveRecord.Essa chamada é necessária porque, exceto por nil e false, qualquer outro valor é consideradoverdadeiro em Ruby, incluindo zero e strings vazias. Se sim, o atributo completed_at é atualizadocom a data atual. Se não, o atributo é retornado a um valor nulo. Depois disso, o valor recebidopara o atributo done é passado sem modificações para a camada de banco de dados, usando ométodo write_attribute.

Mesmo que você não precise modificar campos em dependência um do outro, a técnica acimaé util para guardar valores convertidos (por exemplo, um valor que é informado em graus, masarmazenado em radianos ou vice-versa). O par de métodos write_attribute/read_attribute podeser usado em conjunção com métodos que sobrescrevem os métodos de acesso que o Rails gerapara permitir maior flexibilidade da manipulação de dados.

Finalmente, temos os atributos context_id e project_id, que representam as chaves estran-geiras que criamos no banco, que usaremos para os nossos primeiros relacionamentos entreclasses.

102

Page 104: Ruby on rails

Capítulo 11

Belongs to

Nessa lição falaremos de Belongs to.

11.1 Belongs to

No caso desses dois atributos, poderíamos dizer que uma ação pertence a um contexto e quepertence a um projeto. No Rails, representaríamos isso da seguinte forma: lass A tion < A tiveRe ord::Baseattr_prote ted : reated_at, : ompleted_atdef done=(value)if A tiveRe ord::Conne tionAdapters::Column.value_to_boolean(value)self. ompleted_at = DateTime.nowelseself. ompleted_at = nilendwrite_attribute("done", value)endbelongs_to : ontextbelongs_to :proje tend

Essas duas declarações são suficientes para criar uma série de facilidades no Rails que incluiatribuições e buscas. Veja que você não precisou dizer nada sobre relacionamento além do queele referencia. O Rails é capaz de deduzir a chave estrangeira quando ela for criada concate-nando o nome da tabela pai ao sufixo _id. Caso você tenha dado outro nome, você pode tambémdizer ao Rails qual seria a chave estrangeira usando algo assim:belongs_to :subje t, :foreign_key => " ategory_id"

Além dessa possibilidade, há outras opções na definição de um relacionamento que valem apena ser exploradas na documentação, incluindo outras condições limítrofes para o mesmo.

Para cada relacionamento criado, o Rails disponibiliza métodos que permitem testar a exis-tência de uma associação, defini-la, removê-la e buscá-la. Vamos usar o console para testarisso:

103

Page 105: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DFronaldo�minerva:~/tmp/gtd$ s ript/ onsoleLoading development environment.>> a tion = A tion. reate(:des ription => "A test a tion.")=> #<A tion:0xb745383 �new_re ord=false, �errors=#<A tiveRe ord::Errors:0xb7426490�base=#<A tion:0xb745383 ...>, �errors={}>, �attributes={" ontext_id"=>nil, " ompleted_at"=>nil,"proje t_id"=>nil, "done"=>nil, "id"=>6, "des ription"=>"A test a tion.", " reated_at"=>Thu Sep 2115:27:43 BRT 2006}>Temos uma ação já salva.>> a tion.proje t_id=> nil>> a tion. ontext_id=> nil>> a tion.proje t=> nil>> a tion. ontext=> nilCom o relacionamento, em adição aos atributos vindos do banco de dados temos agora outros

dois atributos que representam a relação.Podemos atribui-los normalmente:>> a tion. ontext_id = 1=> 1>> a tion. ontext=> #<Context:0xb77aaeb8 �attributes={"name"=>"�Home", "id"=>"1"}>Ou usar diretamente a relação:>> a tion.proje t = Proje t.find(1)=> #<Proje t:0xb77a2da8 �attributes={"name"=>"Build a House", "id"=>"1", "des ription"=>"Build adream house entirely planned by myself, in luding a interesting atti with a huge library.","a tive"=>"1"}>>> a tion.proje t_id=> 1Se quisermos, podemos criar também novos valores diretamente:>> a tion. ontext = Context. reate(:name => "�Phone")=> #<Context:0xb779a1d0 �new_re ord=false, �errors=#<A tiveRe ord::Errors:0xb7795a7 �base=#<Context:0xb779a1d0 ...>, �errors={}>, �attributes={"name"=>"�Phone", "id"=>3}>Relacionamentos são bem úteis, mas precisamos ter um pouco de cuidado em como os usa-

mos. Aqui entra a necessidade de conhecimento de SQL que mencionamos anteriormente. Vejao caso abaixo, por exemplo:>> A tion.find(6).proje t.name=> "Build a House"

104

Page 106: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Se executarmos a ação acima e e observamos o log de desenvolvimento gerado pelo Rails(que está em log/development.log), veremos o seguinte:A tion Load (0.002981) SELECT * FROM a tions WHERE (a tions.id = 6) LIMIT 1Proje t Load (0.001254) SELECT * FROM proje ts WHERE (proje ts.id = 1) LIMIT 1

Isso mostra que duas chamadas foram feitas ao banco para carregar o nome do projeto daação buscada, uma delas completamente desnecessária. Obviamente, se fizermos isso em umloop, com múltiplos objetos e relacionamentos, o nível de ineficiência subirá rapidamente.

Para isso, o Rails tem uma solução que resolve a maior parte dos problemas causados porisso, gerando joins automaticamente de acordo com o que o usuário precisa.

Por exemplo:>> A tion.find(6, :in lude => [:proje t, : ontext℄)=> #<A tion:0xb77696f0 � ontext=#<Context:0xb7768818 �attributes={"name"=>"�Phone", "id"=>"3"}>,�proje t=#<Proje t:0xb7768a48 �attributes={"name"=>"Build a House", "id"=>"1", "des ription"=>"Builda dream house entirely planned by myself, in luding a interesting atti with a huge library.","a tive"=>"1"}>, �attributes={" ontext_id"=>"3", " ompleted_at"=>nil, "proje t_id"=>"1", "done"=>nil,"id"=>"6", "des ription"=>"A test a tion.", " reated_at"=>"2006-09-21 15:27:43"}>Como você pode ver, tanto o contexto como o projeto foram automaticamente recuperados.

Se observamos o log, veremos o seguinte:A tion Load In luding Asso iations (0.232072) SELECT a tions.`id` AS t0_r0, a tions.`des ription`AS t0_r1, a tions.`done` AS t0_r2, a tions.` reated_at` AS t0_r3, a tions.` ompleted_at` AS t0_r4,a tions.` ontext_id` AS t0_r5, a tions.`proje t_id` AS t0_r6, proje ts.`id` AS t1_r0, proje ts.`name`AS t1_r1, proje ts.`des ription` AS t1_r2, proje ts.`a tive` AS t1_r3, ontexts.`id` AS t2_r0, ontexts.`name` AS t2_r1 FROM a tions LEFT OUTER JOIN proje ts ON proje ts.id = a tions.proje t_idLEFT OUTER JOIN ontexts ON ontexts.id = a tions. ontext_id WHERE (a tions.id = 6)Ao invés de duas declaração temos somente uma. Para relacionamentos múltiplos, isso pode

reduzir centenas ou milhares de chamadas ao banco em uma única chamada. Veja por exemploa diferença entre as duas chamadas abaixo, depois de criarmos três ações:>> A tion.find(:all). olle t { |a| [a.proje t.name, a. ontext.name℄ }=> [["Build a House", "�Home"℄, ["Plant a Tree", "�Work"℄, ["Write a Book", "�Home"℄℄

O objetivo da chamada é retornar uma coleção dos nomes dos projetos e contextos de cadaação existe no banco, em pares. A chamada acima gera os seguintes comandos:A tion Load (0.002738) SELECT * FROM a tionsProje t Load (0.002161) SELECT * FROM proje ts WHERE (proje ts.id = 1) LIMIT 1Context Load (0.000834) SELECT * FROM ontexts WHERE ( ontexts.id = 1) LIMIT 1Proje t Load (0.001220) SELECT * FROM proje ts WHERE (proje ts.id = 2) LIMIT 1Context Load (0.001116) SELECT * FROM ontexts WHERE ( ontexts.id = 2) LIMIT 1Proje t Load (0.001228) SELECT * FROM proje ts WHERE (proje ts.id = 3) LIMIT 1Context Load (0.001077) SELECT * FROM ontexts WHERE ( ontexts.id = 1) LIMIT 1

Agora, vamos mudar a chamada para:

105

Page 107: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF>> A tion.find(:all, :in lude => [:proje t, : ontext℄). olle t { |a| [a.proje t.name, a. ontext.name℄}=> [["Build a House", "�Home"℄, ["Plant a Tree", "�Work"℄, ["Write a Book", "�Home"℄℄Veremos, ao executá-la, que somente uma chamada será feita:A tion Load In luding Asso iations (0.004515) SELECT a tions.`id` AS t0_r0, a tions.`des ription`AS t0_r1, a tions.`done` AS t0_r2, a tions.` reated_at` AS t0_r3, a tions.` ompleted_at` AS t0_r4,a tions.` ontext_id` AS t0_r5, a tions.`proje t_id` AS t0_r6, proje ts.`id` AS t1_r0, proje ts.`name`AS t1_r1, proje ts.`des ription` AS t1_r2, proje ts.`a tive` AS t1_r3, ontexts.`id` AS t2_r0, ontexts.`name` AS t2_r1 FROM a tions LEFT OUTER JOIN proje ts ON proje ts.id = a tions.proje t_idLEFT OUTER JOIN ontexts ON ontexts.id = a tions. ontext_idÉ fácil ver que essa chamada é bem melhor do que as demais e isso considerando alguns

poucos registros. Em casos de modelos mais complexos, a partir do Rails 1.1 a declaraçãoinclude é recursiva. Você poderia ter algo como:�orders = Ordem.find :all, :in lude => [:items => { :produ t, :dis ount }, :salesperson℄

O código acima buscaria todas as ordens de compra presentes em um banco de dados, carre-gando automaticamente seus itens e a pessoa que a vendeu. Além disso, para os itens, tambémcarregaria os objetos descrevendo o produto associado e o desconto dado. As combinações sãoinfinitas e basta você manter a possibilidade em mente para aplicá-la em suas próprias aplica-ções.

Vamos agora introduzir nossa modificação final em nosso modelo de dados, as validações: lass A tion < A tiveRe ord::Baseattr_prote ted : reated_at, : ompleted_atdef done=(value)if A tiveRe ord::Conne tionAdapters::Column.value_to_boolean(value)self. ompleted_at = DateTime.nowelseself. ompleted_at = nilendwrite_attribute("done", value)endbelongs_to : ontextbelongs_to :proje tvalidates_presen e_of :des riptionvalidates_presen e_of : ontext_idvalidates_presen e_of :proje t_idendAgora que temos os nossos relacionamentos, podemos atualizar o nosso controller e as views

geradas para o mesmo. Primeiro, o controller: lass A tionsController < Appli ationControllerdef indexlist106

Page 108: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DFrender :a tion => "list"enddef list�a tion_pages, �a tions = paginate :a tions, :per_page => 5, :order => "des ription"enddef edit� ontexts = Context.find(:all, :order => "name"). olle t { |i| [i.name, i.id℄ }�proje ts = Proje t.find(:all, :order => "name"). olle t { |i| [i.name, i.id℄ }if params[:id℄�a tion = A tion.find(params[:id℄)else�a tion = A tion.newendif request.post?�a tion.attributes = params[:a tion℄if �a tion.saveflash[:noti e℄ = "The a tion was su essfully saved"redire t_to :a tion => "list"endendenddef deleteA tion.find(params[:id℄).destroyflash[:noti e℄ = "The a tion was su essfully deleted"redire t_to :a tion => "list"endendAs duas linhas acrescentadas criam arrays de pares com os nomes e identificadores dos

objetos buscados. Precisamos desses arrays para a nossa view de edição, como vemos abaixo:<h1><% if �a tion.new_re ord? %>New<% else %>Editing<% end %> A tion</h1><%= error_messages_for "a tion" %><%= start_form_tag :a tion => "edit", :id => �a tion %><p><label for="a tion_des ription">Des ription:</label><br><%= text_area "a tion", "des ription", :rows => 5 %></p><p><label for="a tion_done">Done:</label><br><%= sele t "a tion", "done", [["Yes", true℄, ["No", false℄℄ %></p><p><label for="a tion_ ontext_id">Context:</label><br><%= sele t "a tion", " ontext_id", � ontexts, :prompt => "-- Choose --" %></p><p><label for="a tion_proje t_id">Proje t:</label><br><%= sele t "a tion", "proje t_id", �proje ts, :prompt => "-- Choose --" %>107

Page 109: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF</p><p><%= submit_tag "Save" %> or <%= link_to "Can el", :a tion => "list"%></p><%= end_form_tag %>Selecionar os objetos e gerar as estruturas que precisamos no controller é um prática reco-

mendada porque mantemos a nossa view limpa e podemos fazer quaisquer ordenações e filtrossem nos preocuparmos com o design da aplicação. O parâmetro prompt, passado acima, permiteque um indicador da seleção apareça antes dos valores propriamente ditos.

O formulário resultante fica assim:

Figura 34

Agora voltamos ao pequeno problema que indicamos existir nesse controller anteriormente.Se você tentar salvar uma ação agora, verá que um erro acontece:

108

Page 110: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 35

Esse erro é causado por um detalhe. No Rails, a chave params[:action] já é tomada pelonome da ação que está sendo executada e como temos um objeto com esse nome, acabamoscom um problema. Entretanto, a solução é simples: basta renomear o nosso objeto. Podemos,por exemplo, chamá-lo de item.

Primeiro, mudamos o controller: lass A tionsController < Appli ationControllerdef indexlistrender :a tion => "list"enddef list�a tion_pages, �a tions = paginate :a tions, :per_page => 5, :order => "des ription"enddef edit� ontexts = Context.find(:all, :order => "name"). olle t { |i| [i.name, i.id℄ }�proje ts = Proje t.find(:all, :order => "name"). olle t { |i| [i.name, i.id℄ }if params[:id℄109

Page 111: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF�item = A tion.find(params[:id℄)else�item = A tion.newendif request.post?�item.attributes = params[:item℄if �item.saveflash[:noti e℄ = "The a tion was su essfully saved"redire t_to :a tion => "list"endendenddef deleteA tion.find(params[:id℄).destroyflash[:noti e℄ = "The a tion was su essfully deleted"redire t_to :a tion => "list"endendE depois mudamos a view:<h1><% if �item.new_re ord? %>New<% else %>Editing<% end %> A tion</h1><%= error_messages_for "item" %><%= start_form_tag :a tion => "edit", :id => �item %><p><label for="item_des ription">Des ription:</label><br><%= text_area "item", "des ription", :rows => 5 %></p><p><label for="item_done">Done:</label><br><%= sele t "item", "done", [["No", false℄, ["Yes", true℄℄ %></p><p><label for="item_ ontext_id">Context:</label><br><%= sele t "item", " ontext_id", � ontexts, :prompt => "-- Choose --" %></p><p><label for="item_proje t_id">Proje t:</label><br><%= sele t "item", "proje t_id", �proje ts, :prompt => "-- Choose --" %></p><p><%= submit_tag "Save" %> or <%= link_to "Can el", :a tion => "list"%></p><%= end_form_tag %>Esse é um caso raro, mas se você vir algum erro misterioso acontecendo do Rails onde tudo

deveria estar funcionando, verifique se não há um conflito.Como um detalhe extra, invertemos acima a ordem do Yes e No na seleção da situação ativa

para o correto que seriam uma ação ainda não concluída por padrão. Rodando agora a ação,

110

Page 112: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

temos o resultado desejado.Vamos agora modificar o controller mais uma vez em apoio à view de listagem dos dados.

Teríamos o seguinte: lass A tionsController < Appli ationControllerdef indexlistrender :a tion => "list"enddef list�a tion_pages, �a tions = paginate :a tions, :in lude => [:proje t, : ontext℄,:per_page => 5, :order => "a tions.des ription"enddef edit� ontexts = Context.find(:all, :order => "name"). olle t { |i| [i.name, i.id℄ }�proje ts = Proje t.find(:all, :order => "name"). olle t { |i| [i.name, i.id℄ }if params[:id℄�item = A tion.find(params[:id℄)else�item = A tion.newendif request.post?�item.attributes = params[:item℄if �item.saveflash[:noti e℄ = "The a tion was su essfully saved"redire t_to :a tion => "list"endendenddef deleteA tion.find(params[:id℄).destroyflash[:noti e℄ = "The a tion was su essfully deleted"redire t_to :a tion => "list"endendA inclusão dos relacionamentos irá melhorar a nossa view. Veja que precisamos agora espe-

cificar a condição de ordenação mais claramente, já que temos uma coluna chamada descriptionem duas tabelas.

Nossa view ficaria assim:<h1>A tions</h1><% if flash[:noti e℄ %><p style=" olor: green; font-style: itali "><%= flash[:noti e℄ %></p><% end %><p><%= link_to "New A tion", :a tion => "edit" %></p><table border="1" ellpadding="5" ellspa ing="1"><tr><th>Des ription</th>111

Page 113: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF<th>Completed</th><th>Proje t</th><th>Context</th><th>A tions</th></tr><% �a tions.ea h do |a tion| %><tr><td><%= a tion.des ription %></td><td><%= yes_or_no?(a tion.done?) %></td><td><%= a tion.proje t.name %></td><td><%= a tion. ontext.name %></td><td><%= link_to "Edit", :a tion => "edit", :id => a tion %> or<%= link_to "Delete", { :a tion => "delete", :id => a tion }, : onfirm => "Are you sure?" %></td></tr><% end %></table><p><%= pagination_links(�a tion_pages) %></p>Dando o seguinte resultado:

112

Page 114: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 36

Se quisermos, podemos também acrescentar um campo informando quando a ação foi com-pletada, formatando a data em que a mesma foi completada da maneira necessária à nossaaplicação, ou exibindo um marcador caso contrário. Nossa view poderia ficar como algo assim:<h1>A tions</h1><% if flash[:noti e℄ %><p style=" olor: green; font-style: itali "><%= flash[:noti e℄ %></p><% end %><p><%= link_to "New A tion", :a tion => "edit" %></p><table border="1" ellpadding="5" ellspa ing="1"><tr><th>Des ription</th><th>Completed</th><th>When</th><th>Proje t</th><th>Context</th><th>A tions</th></tr><% �a tions.ea h do |a tion| %><tr><td><%= a tion.des ription %></td><td><%= yes_or_no?(a tion.done?) %></td><td><%= (a tion.done?) ? a tion. ompleted_at.strftime("%m/%d/%y") : "-" %></td><td><%= a tion.proje t.name %></td><td><%= a tion. ontext.name %></td><td><%= link_to "Edit", :a tion => "edit", :id => a tion %> or<%= link_to "Delete", { :a tion => "delete", :id => a tion }, : onfirm => "Are you sure?" %></td></tr><% end %></table><p><%= pagination_links(�a tion_pages) %></p>

Com o seguinte resultado:

113

Page 115: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Figura 37

114

Page 116: Ruby on rails

Capítulo 12

Has many, has one, has many through

Além do relacionamento belongs_to, o Rails também possui um outro relacionamento cha-mado has_many, has one, e has many, through. Essa lição visa explicar cada um deles.

12.1 Has many

Como o próprio nome indica, esse relacionamento diz que um modelo possui muitos de outrosmodelos. Em nossa aplicação, nos temos duas instâncias imediatas disso: um contexto possuimuitas ações e um projeto possui muitas ações.

Modificando inicialmente o modelo de contextos, teríamos: lass Context < A tiveRe ord::Basevalidates_presen e_of :namevalidates_uniqueness_of :name, :message => "must be unique"has_many :a tionsendIsso é suficiente para estabelecer uma série de métodos adicionais que permitem que você

manipule as ações relacionadas a um contexto. Usando o console, podemos ver algumas delas:ronaldo�minerva:~/tmp/gtd$ s ript/ onsoleLoading development environment.>> ontext = Context.find(1)=> #<Context:0xb73ff1f8 �attributes={"name"=>"�Home", "id"=>"1"}>>> ontext.a tions. ount=> 1>> ontext.a tions=> [#<A tion:0xb73f296 �attributes={" ontext_id"=>"1", " ompleted_at"=>nil, "proje t_id"=>"3","done"=>"0", "id"=>"9", "des ription"=>"Sit down and write, every day, at least two hours a day."," reated_at"=>"2006-09-21 16:31:14"}>℄Se criarmos mais uma ação associada a esse contexto, teríamos algo assim:>> a tion = A tion. reate(:des ription => "Buy a grammar to help me with my English.", : ontext_id =>1, :proje t_id => 3)=> #<A tion:0xb78f5448 �new_re ord=false, �errors=#<A tiveRe ord::Errors:0xb78e8644

115

Page 117: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF�base=#<A tion:0xb78f5448 ...>, �errors={}>, �attributes={" ontext_id"=>1, " ompleted_at"=>nil,"proje t_id"=>3, "done"=>nil, "id"=>11, "des ription"=>"Buy a grammar to help me with my English."," reated_at"=>Thu Sep 21 16:52:40 BRT 2006}>>> ontext.a tions=> [#<A tion:0xb73f296 �attributes={" ontext_id"=>"1", " ompleted_at"=>nil, "proje t_id"=>"3","done"=>"0", "id"=>"9", "des ription"=>"Sit down and write, every day, at least two hours a day."," reated_at"=>"2006-09-21 16:31:14"}>℄>> ontext.a tions.reload=> [#<A tion:0xb77786 �attributes={" ontext_id"=>"1", " ompleted_at"=>nil, "proje t_id"=>"3","done"=>"0", "id"=>"9", "des ription"=>"Sit down and write, every day, at least two hours a day."," reated_at"=>"2006-09-21 16:31:14"}>, #<A tion:0xb7778690 �attributes={" ontext_id"=>"1"," ompleted_at"=>nil, "proje t_id"=>"3", "done"=>nil, "id"=>"11", "des ription"=>"Buy a grammar tohelp me with my English.", " reated_at"=>"2006-09-21 16:52:40"}>℄>> ontext.a tions. ount=> 2>> ontext.a tions.empty?=> falseNote que, como usando um objeto já carregado, a coleção de ações não foi recarregada

depois que uma nova ação foi adicionada. Isso acontece porque o Rails faz um cache de coleçõese a ação foi adicionada através de seu próprio constructor e não usando os métodos da coleção.O método reload pode ser usado para recarregar a coleção caso você precise.

Um outro exemplo seria:>> ontext.a tions << A tion. reate(:des ription => "Upgrade my word pro essor.", :proje t_id => 3)=> [#<A tion:0xb77786 �attributes={" ontext_id"=>"1", " ompleted_at"=>nil, "proje t_id"=>"3","done"=>"0", "id"=>"9", "des ription"=>"Sit down and write, every day, at least two hours a day."," reated_at"=>"2006-09-21 16:31:14"}>, #<A tion:0xb7778690 �attributes={" ontext_id"=>"1"," ompleted_at"=>nil, "proje t_id"=>"3", "done"=>nil, "id"=>"11", "des ription"=>"Buy a grammar tohelp me with my English.", " reated_at"=>"2006-09-21 16:52:40"}>, #<A tion:0xb7753160�new_re ord=false, �errors=#<A tiveRe ord::Errors:0xb77513b0 �base=#<A tion:0xb7753160 ...>,�errors={}>, �attributes={" ontext_id"=>1, " ompleted_at"=>nil, "proje t_id"=>3, "done"=>nil,"id"=>12, "des ription"=>"Upgrade my word pro essor.", " reated_at"=>Thu Sep 21 16:59:21 BRT 2006}>℄>> ontext.a tions.size=> 3Nesse caso, você pode ver que duas coisas aconteceram: primeiro, não foi necessário in-

formar o contexto, que é pego automaticamente no objeto; segundo: a coleção cresceu auto-maticamente, já que estamos manipulando os dados diretamente na mesma. Ao invés de usaros métodos acima para criar a ação, você pode usar diretamente os métodos build e create nacoleção para adicionar novos dados. O primeiro funciona para objetos já existentes na coleçãoe o segundo para adicionar um novo objeto. Uma coleção também possui um método find paraencontrar objetos dentro da mesma, de acordo com as mesmas regras do método find usado paraclasses de dados. Por fim, por razões de eficiência, toda coleção declara um método do objetopai para atribuição rápida de valores. No caso do objeto acima, o método é chamada action_idse pode ser usado para trocar completamente os dados de uma coleção.

Por exemplo: ontext.a tion_ids = [1, 2, 6℄116

Page 118: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Isso removeria qualquer ação associada àquele contexto, substituindo-as pelas ações identi-ficadas pelos ids acima.

A coleção toda pode ser substituída com objetos também, como mostrado abaixo: ontext.a tions = A tion.find(:all, : onditions => ["proje t_id = ?", 5℄)O contexto acima teria todas as suas ações substituídas pelas ações associadas ao projeto

cujo id é 5.Investigando a documentação você poderá encontrar mais detalhes sobre como cada método

funciona. Um exemplo disso é a destruição automática de registros quando um registro pai forexcluído. Isso pode fazer um objeto ter a funcionalidade equivalente de uma declaração cascadeem um banco de dados.

Uma coisa que deve ser notada é que muitos desse métodos causam o salvamento imediatodos dados (se o objeto pai já está salvo). Com a prática, você será capaz de identificar quais sãoesses métodos e agir apropriadamente caso queria outra ação, embora o comportamento padrãoseja o desejado na maior parte dos casos.

Não vamos utilizar esses métodos em nossa aplicação no momento porque veremos umaaplicação similar mais à frente com outro tipo de relacionamento parecido.

12.2 Has one

Dois outros relacionamentos existentes no Rails são has_and_belongs_to_many e has_one.O relacionamento has_one geralmente expressa o oposto do relacionamento belongs_to. Por

exemplo, digamos que temos duas tabelas: uma contendo cartões de crédito e outra contendo otitulares de cartões. Na tabela de cartão de crédito teríamos uma chave estrangeira apontandopara o seu titular. Teríamos então as seguintes classes: lass CreditCard < A tiveRe ord::Basebelongs_to :a ount_holderend lass A ountHolder < A tiveRe ord::Basehas_one : redit_ ardend

A diferença é que a chave estrangeira existe somente na tabela relacionada à classe Credit-Card, sendo automaticamente deduzida no relacionamento inverso.

Esse exemplo não é muito bom já que, em tese, uma pessoa poderia ter vários cartões decrédito. Mas a idéia é essa. Na prática, esse tipo de relacionamento é mais raro e geralmentetende a se transformar em relacionamentos do tipo has_many ou has_many_and_belongs_to.

O relacionamento has_many_and_belongs_to, por sua vez, representa a intermediação entreduas tabelas.

Por exemplo, a relação entre desenvolvedores e projetos em uma empresa. Um desenvol-vedor pode estar locado em múltiplos projetos e um projeto pode ter múltiplos desenvolvedores.Veremos mais detalhes desse relacionamento adiante quando fizermos mais modificações emnossa aplicação para suportar outras características.

117

Page 119: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

12.3 Has many, trough

Um tipo interessante de relacionamento, introduzido no Rails 1.1, é aquele relacionamentoem que uma tabela intermediária é usada para colapsar outra. Digamos, por exemplo, que vocêtenha modelos representando empresas, clientes e notas ficais e você queira descobrir todas asnotas fiscais emitidas por um empresa qualquer. É aqui que esse tipo de relacionamento entraem ação, permitindo que o código seja simplificado. Vamos experimentar um pouco, usando oconsole.

Digamos que você queria, em nossa aplicação, saber todos os contextos associados a umprojeto. A maneira simples seria fazer isso:ronaldo�minerva:~/tmp/gtd$ s ript/ onsoleLoading development environment.>> proje t = Proje t.find(3)=> #<Proje t:0xb78083f �attributes={"name"=>"Write a Book", "id"=>"3", "des ription"=>"","a tive"=>"1"}>>> proje t.a tions. olle t { |a| a. ontext.name }=> ["�Home", "�Home", "�Home"℄

Como você pode ver, o contexto é o mesmo para todas as ações associadas àquele projeto equeremos os contextos sem repetição. Uma solução simples seria:>> ontexts = proje t.a tions. olle t { |a| a. ontext.name }=> ["�Home", "�Home", "�Home"℄>> ontexts.uniq=> ["�Home"℄

O problema é que, além de usar mais código, essa solução é muito ineficiente por causadas comparações que precisam ser realizadas pelo método uniq. Podemos resolver o problemausando então a seguinte estratégia: lass Proje t < A tiveRe ord::Basevalidates_presen e_of :namevalidates_uniqueness_of :namehas_many :a tionshas_many : ontexts, :through => :a tions, :sele t => "distin t ontexts.*"end

Usando esse método tempos:>> proje t. ontexts. olle t { | | .name }=> ["�Home"℄Como você pode ver, muito mais fácil e interessante e, em quase todos os casos, mais efici-

ente.No relacionamento acima, temos os contextos de um projecto obtidos através (through) de

suas ações. Se não for necessário preocupar com cópias (como no exemplo das notas fiscais)poderemos inclusive descartar o parâmetro select que é um fragmento de SQL usado no lugardo gerado automaticamente pelo Rails.

118

Page 120: Ruby on rails

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasília/DF

Associações through são muito poderosas e podem ser utilizadas em várias situações paraagilizar o código e melhorar a legibilidade da aplicação.

Com isso, chegamos ao fim do nosso curso do básico de Ruby on Rails. Espero que venha aser útil para vocês.

119