Tutorial Rails
Tutorial Rails
Desenvolvido por
Eustáquio “TaQ” Rangel
Dedicado para a minha filhinha Ana Isabella e para a minha esposa Ana Carolina que falou que
me mata se eu não dedicar alguma coisa para ela depois de dedicar o livro de Ruby só para a
Ana Isabella. ;-)
"Rails", "Ruby on Rails", e o logotipo do Rails são marcas registradas de David Heinemeier Hansson. Todos os direitos
reservados. Obrigado ao David por autorizar o uso do logotipo do Rails. :-)
Revisão 2 – 20/05/2006
Este trabalho está licenciado sob uma Licença Creative Commons Atribuição-Uso Não-
Comercial-Compatilhamento pela mesma licença.
Para ver uma cópia desta licença, visite
http://creativecommons.org/licenses/by-nc-sa/2.5/br/
ou envie uma carta para
Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
Sumário
Além de tudo que escrevi ali em cima, gostaria de deixar claro algumas coisas antes de
começarmos a meter a mão na massa:
1. O David realmente me deixou usar o logotipo. Guardei o email de prova ehehe.
2. Minha esposa disse que esse tipo de dedicatória não vale, mas tudo bem, ficou
engraçado. ;-)
3. Fiz esse tutorial de maneira bem descompromissada de acordo com várias anotações que
eu havia feito. Não esperem um esmero de código, a intenção foi liberar algo rápido e
prático para dar uma noção do framework. Tem muita coisa que pode ser melhorada
depois de entender os conceitos básicos e sujar as mãos no código.
4. Por causa da razão acima podem ser encontrados vários códigos CSS meio estranhos, e
pior, algumas partes que nem usam CSS. Eu continuo usando tabelas em alguns pontos,
mas isso pode ser melhorado também.
5. Também não me preocupei muito com os layouts. Tem umas coisas bem feinhas por aí.
6. Deve ter um ou outro erro (espero que poucos!) perdidos por aí. Se alguém encontrar por
favor me avise (eustaquiorangel at yahoo.com) por favor!
7. Se alguém quiser armazenar o tutorial para download no seu servidor, por favor me
informe o link para que eu possa mencionar no meu site.
8. Podem usar o tutorial para ler, imprimir, mandar para o amigo, etc, etc, etc, só não
podem usar para uso comercial, seus espertinhos. ;-)
9. Sugestões serão bem-vindas e podem ser enviadas para o meu email ( eustaquiorangel at
yahoo.com).
10. Espero que se divirtam. :-)
O que é o Rails?
Rails é um framework1 feito em Ruby que funciona no conceito MVC – Model, View, Controller -
onde é separado o modelo de dados, a interface do usuário e o controle lógico do programa,
permitindo que alterações em qualquer uma dessas partes tenham pouco impacto nas outras.
O que é Ruby?
Para explicar o que é Ruby, eu faço uma cópia descarada do mesmo texto que está no meu livro,
uma tradução livre do que Yukihiro “Matz” Matsumoto, seu criador, diz a respeito dela em
http://www.ruby-lang.org/en/20020101.html:
Recursos da linguagem
● Ruby tem uma sintaxe simples, parcialmente inspirada por Eiffel e Ada.
● Ruby tem recursos de tratamento de exceções, assim como Java e Python, para deixar
mais fácil o tratamento de erros.
● Os operadores do Ruby são açúcar sintático para os métodos. Você pode redefini-los
facilmente.
● Ruby é uma linguagem completa e pura orientada à objetos. Isso significa que todo dado
em Ruby é um objeto, não do jeito de Python e Perl, mas mais do jeito do SmallTalk: sem
exceções. Por exemplo, em Ruby, o número 1 é uma instância da classe Fixnum.
● Ruby tem herança única, de propósito. Mas entende o conceito de módulos (chamados de
Categories no Objective-C). Módulos são coleções de métodos. Toda classe pode importar
um módulo e pegar seus métodos. Alguns de nós acham que isso é um jeito mais limpo
do que herança múltipla, que é complexa e não é usada tanto comparado com herança
única (não conte C++ aqui, pois lá não se tem muita escolha devido a checagem forte de
tipo!).
● Ruby tem closures2 verdadeiras. Não apenas funções sem nome, mas com bindings de
variáveis verdadeiras.
● Ruby tem blocos em sua sintaxe (código delimitado por {...} ou do...end). Esses blocos
podem ser passados para os métodos, ou convertidos em closures.
1 Um framework pode ser definido como uma software estrutura de auxílio ao
desenvolvimento de outros softwares, visando prover agilidade e eficiência para que o
programador se livre da implementação de código repetitivo e ... chato.
2 Closures podem ser definidas como funções criadas dentro de outras funções, e que
referenciam o ambiente (variáveis) da função externa mesmo depois dela ter saído de
escopo, retendo a sua referência.
● Ruby tem um garbage collector que realmente é do tipo marca-e-limpa. Ele atua em
todos os objetos do Ruby. Você não precisa se preocupar em manter contagem de
referências em libraries externas. É melhor para a sua saúde.
● Escrever extensões em C para Ruby é mais fácil que em Perl ou Python, em grande parte
por causa do garbage collector, e em parte pela boa API de extensões. A interface SWIG
também está disponível.
● Inteiros em Ruby podem (e devem) ser usados sem contar sua representação interna.
Existem inteiros pequenos (instâncias da classe Fixnum) e grandes (Bignum), mas você
não precisa se preocupar em qual está sendo utilizado atualmente. Se um valor é
pequeno o bastante, um inteiro é um Fixnum, do contrário é um Bignum. A conversão
ocorre automaticamente.
● Ruby é altamente portável: ela é desenvolvida em sua maioria no Linux, mas funciona em
muitos tipos de UNIX, DOS, Windows 95/98/Me/NT/2000/XP, MacOS, BeOS, OS/2, etc.
Procure na sua distribuição (se você usa GNU/Linux) algum pacote do Ruby, e se você usa
Windows ou Mac, nos links correspondentes no site.
Se você quiser conhecer mais de Ruby, pode baixar o meu tutorial
http://beam.to/taq/tutorialruby.php
ou comprar o meu livro (compra,compra,compra! :-):
Atenção!
Vou basear o tutorial de Rais inteiro em ambiente GNU/Linux. Por favor, eu não utilizo Windows
e nem tenho computador com ele instalado para testar a instalação nesse ambiente. Mesmo se
tivesse alguma disponível, eu prefiro ficar longe. :-)
Para instalar o Rails, antes vamos precisar instalar a linguagem Ruby, que pode ter seu download
feito em
http://www.ruby-lang.org
Lá encontramos os fontes da linguagem. Se você utiliza alguma distribuição que utiliza pacotes,
por favor verifique se o pacote do Ruby se encontra disponível.
Depois precisamos do RubyGems, que é um gerenciador de pacotes para o Ruby. Pode ter seu
download feito em
http://docs.rubygems.org/
Após instalados o Ruby e o RubyGems, podemos instalar o pacote do Rails facilmente com o
comando
gem install rails –include-dependencies
Digite rails no seu console e verifique o que acontece. Se não acontecer nada, houve algum
problema. Verifique a sua instalação.
Falando em console, acostume-se a digitar coisas lá, pois vamos o utilizar muito durante o
tutorial.
Vamos usar nos nossos exemplos o banco de dados MySQL, que é de longe o banco de dados
gratuito mais comum tanto em GNU/Linux como Windows.
Criando as tabelas básicas dos produtos e usuários (sinta-se livre para criar essas tabelas com a
ferramenta que quiser):
[taq@~]mysql -u taq -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 9 to server version: 4.1.14
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
Convém notar na última tabela criada, produtos, que eu criei algumas foreign keys referenciando
os campos de outras duas tabelas, tipos e categorias. Uma convenção do Rails é usar a palavra
id em certos campos chaves, como o identificador único de uma tabela (no caso, todas as
tabelas usam esse campo) e nos nomes dos campos que servem de referências para outras
tabelas (tipo_id e categoria_id).
Criando o projeto
Tudo pronto no banco de dados, vamos criar o projeto. Vá para o diretório onde você quer que o
servidor web tenha acesso no seu projeto (no meu caso, /var/www/htdocs) e execute o comando:
[taq@/var/www/htdocs]rails livraria
create
create app/controllers
create app/helpers
create app/models
create app/views/layouts
create config/environments
create components
create db
create doc
create lib
create lib/tasks
create log
...
Agora vamos configurar o Rails para acessar o banco de dados que criamos (abra o arquivo com
o seu editor favorito, eu uso o vim):
[taq@/var/www/htdocs/livraria]vim config/database.yml
development:
adapter: mysql
database: livraria_development
username: taq
password: ******
Agora vamos inicializar o nosso servidor web. Para esse tutorial, vamos utilizar o WEBrick, que já
vem instalado:
[taq@/var/www/htdocs/livraria]ruby script/server
=> Booting WEBrick...
=> Rails application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options
[2006-04-19 18:14:41] INFO WEBrick 1.3.1
[2006-04-19 18:14:41] INFO ruby 1.8.4 (2005-12-24) [i686-linux]
[2006-04-19 18:14:41] INFO WEBrick::HTTPServer#start: pid=4174 port=3000
Se vocês repararem no arquivo de configuração do banco de dados, vão ver que ele tem
definidos development:, test: e production:, com configurações individuais para cada um.
Isso nos permite usar diferentes bancos de dados para cada característica de nosso projeto, além
de ter diferenças de performance. Para o passo de criar o site, vamos usar o default
development. Se precisarmos utilizar test ou production, basta rodar
Escolha uma das três opções ou deixe em branco para utilizar o development, que é o default.
Scaffold
Criando o primeiro scaffold3, vamos começar com gerenciamento de usuários, afinal, todo
sistema precisa disso:
● ruby – Sem a linguagem Ruby instalada não conseguiremos fazer nada. Esse comando
chama o interpretador da linguagem passando os parâmetros, que são ...
● scaffold – O parâmetro para o programa indicando que queremos que ele gere um
scaffold.
3 Um scaffold é um meio de criar código para um determinado modelo (que indicamos) através
de um determinado controlador (que indicamos também). É um meio de começarmos
rapidamente a ver os resultados no nosso navegador web e um método muito rápido de
implementar o CRUD (Create, Retrieve, Update, Delete) na sua aplicação. Lembrando que o
scaffold cria código que, fatalmente, vai ser alterado depois, a não ser que você deseje
manter o nível muito básico de interface e controle padrão de campos que ele proporciona.
● Usuario – O modelo (model), ou seja, o objeto que vai cuidar dessa tabela especifica,
usuarios (notem o plural), no banco de dados.4
Aqui já aparece o controlador, a terceira parte do MVC. Nosso controlador vai ser chamado de
Admin::Usuario. Isso vai ser traduzido pelo Rails como uma estrutura de diretórios
<aplicação>/controlers/admin/usuario. Eu preferi manter toda a estrutura de administração do
site abaixo do diretório admin para dar uma separação mais lógica para a coisa.
Testando:
[taq@/var/www/htdocs/livraria]ruby script/server
=> Booting WEBrick...
=> Rails application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options
Modelos
Antes de darmos uma olhada nos visualizadores, vamos brincar um pouco com o modelo. Como
mencionei ali em cima, foi criado o modelo para a tabela de usuários, sendo que o modelo
chama Usuario. Reparem como que a tabela está no plural, e o modelo, no singular, com uma
letra maiúscula no início. Isso segue o modelo de mapeamento objeto-relacional (ORM6)
suportado pelo Rails. Vamos dar uma olhada no conteúdo do arquivo do modelo, que está em
<aplicacao>/app/models/usuario.rb:
A única coisa, por enquanto, em nosso modelo é a declaração da classe Usuario a partir da
classe ActiveRecord::Base. Mas isso vai mudar no decorrer do tutorial.
No nosso caso, a classe Usuario se relaciona com a tabela usuarios. Podemos ter nomes como
EncomendaCliente, separando as palavras com maiúsculas, que vão ser mapeados para tabelas
com as palavras separadas por um sublinhado, ficando encomendas_clientes, por exemplo.
Inclusive, o Rails tenta converter de singular para plural utilizando o Active Support, que é um de
seus componentes. “Ei, mas como ele sabe converter para plural certas palavras especificas da
Agora que temos um modelo de Usuario definido (lembrem-se de não usar acentos nos seus
modelos ou tabelas!), podemos brincar um pouco com as caracteríscas do Active Record, outro
componente do Rails, antes de darmos mais uma olhada no navegador.
O Rails vem com um console que nos permite interagir com o modelo. Primeiro vamos conectar
no nosso banco de dados e verificar se existe algum usuário com o código 999:
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
Ok, já sabemos que não tem. Agora vamos abrir um console, criar um usuário novo já que
definimos o modelo, e salvar o dito cujo:
[taq@/var/www/htdocs/livraria]ruby script/console
Loading development environment.
u >> u = Usuario.new
=> #<Usuario:0xb72ef1fc @new_record=true, @attributes={"img"=>nil,
"nome"=>"", "admin"=>0, "senha"=>"", "email"=>""}>
>> u.id = 999
=> 999
>> u.nome = "Teste"
=> "Teste"
>> u.email = "[email protected]"
=> "[email protected]"
>> u.senha = "teste"
=> "teste"
>> u.admin = 0
=> 0
>> u.save
=> true
Legal hein? Tivemos um exemplo claro da ORM funcionando. Há um atalho para salvar um
registro, que é utilizar o método create que vai pegar como parâmetros uma hash com o
conteúdo do registro, tendo seus campos como chaves e os valores, como valores, dã:
>> u = Usuario.create(:nome=>"Outro teste!",:email=>"[email protected]",↵
:senha=>"outroteste", :admin=>0)
=> #<Usuario:0xb75a52fc @new_record=false,
@errors=#<ActiveRecord::Errors:0xb75a4140 @errors={},
@base=#<Usuario:0xb75a52fc ...>>, @attributes={"img"=>nil, "nome"=>"Outro
teste!", "admin"=>0, "id"=>1002, "senha"=>"outroteste",
"email"=>"[email protected]"}>
>> u.id
=> 1002
>> Usuario.find(1002)
=> #<Usuario:0xb7593818 @attributes={"img"=>nil, "nome"=>"Outro teste!",
"admin"=>"0", "id"=>"1002", "senha"=>"outroteste",
"email"=>"[email protected]"}>
O que fiz foi criar uma hash com os dados e passei para o método create, que me retornou um
objecto do tipo Usuario, com o id novo criado, no caso, 1002, e logo em seguida efetuei uma
pesquisa, que me retornou os dados inseridos.
Inclusive podemos pesquisar novamente e vamos ter como resultado o registro que acabamos
de salvar:
>> Usuario.find(999)
=> #<Usuario:0xb762ddc8 @attributes={"img"=>nil, "nome"=>"Teste",
"admin"=>"0", "id"=>"999", "senha"=>"teste", "email"=>"[email protected]"}>
Uma coisa bem interessante que o Active Record permite é usar métodos de procura dinâmicos,
que são métodos que são criados de acordo com os atributos do objeto em questão. Por
exemplo, temos no nosso modelo de Usuario os atributos id, nome, senha, email, admin e img.
Para procurar por nome, podemos usar:
>> u = Usuario.find_by_nome("Teste")
=> #<Usuario:0xb7605e04 @attributes={"img"=>nil, "nome"=>"Teste",
"admin"=>"0", "id"=>"999", "senha"=>"teste", "email"=>"[email protected]"}>
>> u = Usuario.find_by_email("[email protected]")
=> #<Usuario:0xb75fd8a8 @attributes={"img"=>nil, "nome"=>"Teste",
"admin"=>"0", "id"=>"999", "senha"=>"teste", "email"=>"[email protected]"}>
Ou seja, os campos da tabela foram mapeados como atributos do objeto, que podem ser
consultados com métodos dinamicamente criados com o poder do Ruby.
Só para mostrar a natureza dinâmica desse tipo de operação, podemos perguntar para o nosso
objeto do modelo se existe o método find_by_nome nele:
>> Usuario.respond_to?(:find_by_nome)
=> false
>> Usuario.respond_to?(:find_by_sql)
=> true
Não existe. Logo depois perguntei de find_by_sql, e ele está lá. Ou seja, o método dinâmico é
criado no momento de sua necessidade.
Visualizadores
Para personalizar as telas, vou alterar o arquivo de layout padrão do controlador. Agora entra a
segunda parte do MVC, que são os visualizadores (views).
<aplicação>/app/views/layouts/usuario.rhtml
<html>
<head>
<title>Admin::Usuario: <%= controller.action_name %></title>
<%= stylesheet_link_tag 'scaffold' %>
</head>
<body>
</body>
</html>
A primeira coisa que vou fazer é apagar (sim, exterminar sem misericórdia!) o arquivo acima
(usuario.rhtml) e criar um arquivo de layout padrão para o controlador admin todo, e depois vou
inserir indicações desse arquivo para os controladores abaixo do admin. No exemplo, vou criar o
arquivo admin.rhtml :
E indicar que o layout7 que o controlador usuario deve usar é o “admin”. Para isso, vou alterar o
controlador, que se encontra em
<aplicação>/app/controllers/admin/usuario
● A instrução @content_for_layout especifica que nesse ponto é que vai ser inserido o
conteúdo do arquivo da ação desejada (nesse caso, list, que vai estar em
aplicação/app/views/admin/tipo/list.rhtml).
<aplicação>/public/stylesheets/:
body {
margin:0;
}
#topo {
background:#c00000;
color:white;
width:100%;
height:100px;
margin:0;
}
#topo strong {
font:bold 24px verdana,arial,helvetica,sans-serif;
margin:25px;
float:left;
}
#conteudo {
margin:25px;
}
.novo {
padding-left:20px;
background:url(../images/mais.png) 3px center no-repeat;
font-weight:bold;
}
E agora em na view list.rhtml8, vai o “miolo” do arquivo, já devidamente modificado com o texto
traduzido:
<div id="conteudo">
<h1>Listando os usuários</h1>
<table>
<tr>
<% for column in Usuario.content_columns %>
<th><%= column.human_name %></th>
<% end %>
</tr>
<% for usuario in @usuarios %>
<tr>
<% for column in Usuario.content_columns %>
<td><%=h usuario.send(column.name) %></td>
<% end %>
<p>
<%= link_to 'Página anterior', { :page => ↵
@usuario_pages.current.previous } if @usuario_pages.current.previous %>
<%= link_to 'Próxima página', { :page => @usuario_pages.current.next } ↵
if @usuario_pages.current.next %>
</p>
<p>
<%= link_to 'Novo usuário', {:action => 'new'}, :class => 'novo' %>
</p>
</div>
Um ponto a ser observado aqui: o uso do método h, que é um atalho para html_escape, um
método para converter strings com caracteres especiais (como &) em suas entidades HTML
(nesse caso, &).
Apesar da demonização das tabelas hoje em dia, para esse caso elas servem muito bem, pois é
pouca coisa e não vamos nos ater em mexer muito no CSS por hora. O resultado disso tudo é:
Controladores
Uma coisa interessante pode-se notar no título da página: a ação está especificada como index.
Lembrem-se do conteúdo do controlador tipo:
def index
list
render :action => 'list'
end
No controlador estão especificadas todas as ações que acharmos necessárias. Sempre que não
for especificada uma ação, o Rails aciona a index, e nesse caso, index chama list.
Agora vamos clicar em “Novo usuário” e personalizar o arquivo new.rhtml, que se encontra em
<aplicação>/app/views/admin/usuario/:
<div id="conteudo">
<h1>Novo usuário</h1>
Uma observação importante ali na chamada de link_to: o primeiro parâmetro é o texto do link, o
segundo é uma hash onde fica a ação de destino do link e mais algumas opções de controle
(nesse caso, tendo o controlador omitido pois é o que estamos), e o terceiro é uma hash com as
propriedades HTML do link, onde nesse caso especifiquei que a classe CSS do link. Como são
duas hashes, tive que separar a primeira da segunda com as chaves ({}), do contrário iria ficar
as duas instruções na primeira hash. Por exemplo:
<%= link_to 'Voltar', :action => 'list', :class => 'voltar' %>
link_to('Voltar',{:action=>'list',:class=>'voltar'})
Ao passo que
link_to('Voltar',{:action=>'list'},{:class=>'voltar'})
é o correto, pois informamos as duas hashes ali. Então, preste atenção nesses detalhes.
.novo, .voltar {
padding-left:20px;
background:transparent url(../images/mais.png) 3px center no-repeat;
font-weight:bold;
}
a:hover {
color:red;
background-color:white;
}
.voltar {
background:url(../images/voltar.png) 3px center no-repeat;
}
.submit {
border:1px solid #c00000;
background:red;
color:white;
font-weight:bold;
padding:3px;
}
Tivemos que alterar o a:hover pois o Rails especifica um formato próprio no arquivo scaffold.css.
Como não queria daquela maneira, sobreescrevi a regra. Também adicionei duas pequenas
imagens em <aplicação>/public/images.
Vamos ter agora:
Ficou legal, mas reparem no campo admin e no campo img. Não era bem aquilo que
esperávamos ali. Antes de complicar um pouco mais (sim, vamos ter que sair um pouco do arroz-
com-feijão do scaffold nesse caso), vamos ver algumas coisas mais básicas definindo o que
precisamos para a tabela tipos, e já-já voltamos ao usuário, após absorver mais alguns outros
conceitos.
Vamos criar tudo que precisamos para interagir com a tabela tipos. Primeiro, o scaffold:
Validação
Um problema que há nessa tela é que se clicarmos no botão “Cria novo tipo”, e a descrição
estiver vazia, a operação vai ser concluída. Temos que especificar que o campo tem que conter
valor. Para isso alteramos nosso modelo (<aplicação>/models/tipo.rb):
Uma coisa chatinha ali é que o Rails ainda não é internacionalizado. Podemos fazer uma
“gambiarra” e traduzir as mensagens de erro. Para isso, temos que criar um arquivo para
sobrescrever algumas coisas do framework, nesse caso, as mensagens de erro. Crie um diretório
chamado <aplicação>/overrides e crie um arquivo chamado messages.rb com o seguinte
conteúdo:
module ActiveRecord
class Errors
@@default_error_messages = {
:inclusion => "não está incluído na lista",
:exclusion => "está reservado",
:invalid => "é inválido.",
:confirmation => "não corresponde á confirmação",
:accepted => "deve ser aceito",
:empty => "não pode estar sem conteúdo",
:blank => "não pode estar em branco",
:too_long => "muito longo (máximo %d caracteres)",
:too_short => "muito curto (mínimo %d caracteres)",
:wrong_length => "de comprimento errado (deveria ter %d caracteres)",
:taken => "já está em uso",
:not_a_number => "não é um número"
}
end
end
Isso vai sobreescrever as mensagens de erro do ActiveRecord. Para carregar o conteúdo desse
arquivo altere o arquivo <aplicação>/config/environment.rb inserindo a seguinte linha no final:
require "#{RAILS_ROOT}/app/overrides/messages"
Vamos tentar inserir novamente o registro, após reiniciarmos o servidor para carregar as novas
mensagens:
Ilustração 7: Algumas mensagens de erro traduzidas
Não ajudou tanto mas já deu para contornar a causa do erro e ver como podemos reescrever
algumas coisas do Rails de acordo com nossa necessidade. Vamos ver mais tarde mais algumas
conversões necessárias. Em tempo: para traduzir o resto das mensagens teríamos que alterar os
fontes do Rails, e não que seja difícil, mas a cada nova versão teríamos que aplicar esse “patch”.
Outro lugar que pode ser traduzido é o arquivo do controlador, onde existem algumas poucas
mensagens também.
Vamos criar alguns tipos, “Livro” e “CD”, e olharmos como ficou nossa tela de listagem:
Agora é criar o controlador e modelo das categorias e fazer a mesma coisa que nos tipos:
Relacionamentos
Agora vem uma parte interessante e diferente. Criar o modelo e o controlador para produto:
Digo interessante pois cada produto tem um tipo e uma categoria. Então vamos ter que alterar
algumas coisinhas. Primeiro nos modelos; temos que especificar que o cada tipo e categoria
podem estar presentes em vários produtos. Para isso abrimos <aplicação>/models/tipo.rb e
<aplicação>/models/categoria.rb e inserimos:
Depois temos que especificar que produto tem um tipo e uma categoria, ou seja, ele está
relacionado com essas tabelas (tanto que tem tipo_id e categoria_id como foreign keys), então
ele depende e pertence de certo modo à essas tabelas. Vamos usar:
“In general, the Foo model belongs_to :bar if the foo table has a bar_id foreign key column.”
Opa! Mas onde estão tipo e categoria? O scaffold não gera os objetos correspondentes à esses
campos no formulário da página, então vamos ter que fazer “na unha”.
A primeira coisa que teremos que fazer é alterar o controlador para informar que para cada novo
tipo e nova categoria (ou quando formos editar algum desses), vamos ter que pegar os valores
das tabelas correspondentes. No controlador de produto vamos inserir:
def new
@produto = Produto.new
@tipos = Tipo.find_all.collect{ |t| [t.descricao,t.id] }
@categorias = Categoria.find_all.collect{ |c| [c.descricao,c.id] }
end
def edit
@produto = Produto.find(params[:id])
@tipos = Tipo.find_all.collect{ |t| [t.descricao,t.id] }
@categorias = Categoria.find_all.collect{ |c| [c.descricao,c.id] }
end
Criei um Array com a descrição e o id de cada tipo e categoria. Esse array já está no formato
esperado pelo método select, que vai gerar um componente XHTML com todas as opções
disponíveis da tabela requerida. E como vamos inserir essa informação em ambos os forms, o de
criação e o de edição?
O Rails criou para nós vários arquivos no diretório de views, inclusive o arquivo _form.rhtml, que
vai ser compartilhado para a criação e edição. Justamente o que precisávamos. O conteúdo
default do arquivo é:
<%= error_messages_for 'produto' %>
<!--[form:produto]-->
<p><label for="produto_descricao">Descricao</label><br/>
<%= text_field 'produto', 'descricao' %></p>
<!--[eoform:produto]-->
<!--[form:produto]-->
<p><label for="produto_descricao">Descricao</label><br/>
<%= text_field 'produto', 'descricao' %></p>
<p><label for="produto_tipo">Tipo</label><br/>
<%= select("produto","tipo_id", @tipos ) %></p>
<p><label for="produto_categoria">Categoria</label><br/>
<%= select("produto","categoria_id", @categorias ) %></p>
<!--[eoform:produto]-->
Inserimos duas instruções novas para criarmos os dois elementos select XHTML. Agora, se
recarregarmos a URL de criação de um novo produto, vamos ter:
Como que eu listei ali a descrição de tipo e categoria, sendo que na tabela do produto só tenho o
id (tipo_id e categoria_id) de cada um? É aí que entra as regras do has_many e belongs_to que
especificamos acima. O Rails automaticamente criou propriedades tipo e categoria para cada
produto, então na listagem eu usei:
<td><%= link_to 'Mostra', :action => 'show', :id => produto %></td>
<td><%= link_to 'Edita', :action => 'edit', :id => produto %></td>
<td><%= link_to 'Apaga', { :action => 'destroy', :id => produto }, :confirm
=> 'Tem certeza?' %></td>
</tr>
<% end %>
Agora que vimos mais alguns conceitos, vamos voltar na tela do usuário. O scaffold quebra o
galho em muitas coisas, mas em algumas temos que meter a mão na massa e alterar o código
gerado. Vamos pegar o modelo do formulário do usuário, alterando os arquivos
<aplicação>/app/views/admin/usuario/new.rhtml
<div id="conteudo">
<h1>Novo usuário</h1>
e <aplicação>/app/views/admin/usuario/_form.rhtml para:
<!--[form:usuario]-->
<table>
<tr>
<td>Nome</td><td><%= text_field 'usuario', 'nome' %></td>
</tr>
<tr>
<td>Email</td><td><%= text_field 'usuario', 'email' %></td>
</tr>
<tr>
<td>Senha</td><td><%= password_field 'usuario', 'senha' %></td>
</tr>
<tr>
<td>Administrador</td><td><%= check_box 'usuario', 'admin' %></td>
</tr>
<tr>
<td>Imagem</td><td><%= file_field("usuario","img") %></td>
</tr>
</table>
<!--[eoform:usuario]-->
● :multipart => true – A grosso modo isso indica que estamos enviando mais do que
campos texto no nosso formulário, vamos enviar dados binários provenientes de um
arquivo. Preste atenção nisso, senão não conseguiremos enviar os dados binários!
● text_field – Vai gerar um campo texto no nosso formulário, tendo o seu conteudo
preenchido com o valor de @usuario.nome.
● password_field – Gera um campo texto similar ao anterior, mas com asteriscos no lugar
dos caracteres, para informarmos uma senha.
● check_box – Gera um campo tipo checkbox através do valor @usuario.admin
● file_field – Gera um campo texto com um botão para upload de arquivos. É através
desse campo que serão transmitidos os dados binários esperados pelo multipart.
Com tudo isso, vamos conseguir configurar um usuário com nome, email, senha, indicar se ele é
um administrador do sistema e ainda ter a opção de efetuar o upload de uma foto.
Vai ser parecido com isso:
Porém, na listagem:
1. Ei! De que adianta uma senha se ela é apresentada na listagem? Isso não pode aparecer
ali.
2. Ter o campo como administrador como “1” pode significar que o usuário em questão tem
esse tipo de permissão, mas fica meio confuso.
3. Essa imagem está esquisita. Não parece efetivamente que um arquivo binário foi gravado
ali.
<aplicação>/app/views/admin/usuario/list.rhtml:
<div id="conteudo">
<h1>Listando usuários</h1>
<table>
<tr>
<th>Nome</th>
<th>Email</th>
<th>Administrador?</th>
<th>Imagem</th>
</tr>
<% for usuario in @usuarios %>
<tr>
<td><%=h usuario.nome %></td>
<td><%=h usuario.email %></td>
<td><%=h usuario.admin==1 ? "Sim" : "Não" %></td>
<td><img src="<%=h url_for(:action => "img", :id => usuario.id) ↵
%>"/></td>
<td><%= link_to 'Mostra', :action => 'show', :id => usuario %></td>
<td><%= link_to 'Edita', :action => 'edit', :id => usuario %></td>
<td><%= link_to 'Apaga', { :action => 'destroy', :id => usuario },↵
:confirm => 'Are you sure?', :post => true %></td>
</tr>
<% end %>
</table>
<p>
<%= link_to 'Página anterior', {:page => ↵
@usuario_pages.current.previous } if @usuario_pages.current.previous %>
<%= link_to 'Próxima página', { :page => @usuario_pages.current.next } ↵
if @usuario_pages.current.next %>
</p>
<p>
<%= link_to 'Novo usuário', :action => 'new' %>
</p>
</div>
1. Especifiquei os nomes das colunas “na unha” agora, pois são poucas e quero fazer um
filtro, para, por exemplo, não mostrar a senha.
2. Os valores dos campos posso mostrar com usuario.campo, pois já estão hardcoded, ou
seja, não preciso mais usar o método send com uma string para recuperar o valor de um
campo.
3. Na imagem, usei <img src=””> com url_for usando como parâmetros action=”img” e
usuario.id
Arquivos binários
def img
@usuario = Usuario.find(params[:id])
send_data(@usuario.img,:type=>"image/png",:disposition=>"inline")
end
Recarregando a página de listagem de usuários, não vamos notar muita diferença. Isso por que a
nossa imagem foi gravada de maneira errada no banco de dados. Foi gravada como uma string,
e não como dados binários. Para consertar isso, vamos alterar o modelo do usuário, para quando
receber a informação de onde carregar os dados, ler e guardar no banco de dados. Para isso
vamos fazer algumas “jogadinhas”.
Para
Qual o motivo? Nem temos um campo chamado imagem em nossa tabela do banco de dados! E,
como não temos, o que vai ser feito com o que for recebido do campo do formulário? Vamos
alterar nosso modelo em <aplicacao>/app/models/usuario.rb e adicionar:
def imagem=(img_field)
self.img = img_field.read
end
O que vai acontecer é que quando processarmos o nosso formulário, o conteúdo do campo
imagem vai ser passado para um método chamado imagem=(i) no nosso modelo, que é o que
acabamos de definir. Após receber o conteúdo do campo, utilizamos o método read para ler o
conteúdo e armazenar em self.img, que referencia o campo img, que realmente existe em
nosso banco de dados. Vocês podem perguntar por que não reescrevemos o método img=(i),
afinal, ele já está no modelo, mas pensem o que iria acontecer:
def img=(img_field)
self.img = img.read
end
O método img carregaria os dados e chamaria img novamente (afinal, estamos atribuindo valor
para img com o sinal de =) e iríamos cair em chamadas recursivas infinitas (na verdade, não,
pois o script iria ser interrompido após um certo número – elevado – de chamadas recursivas).
Vamos apagar o cadastro que fizemos e adicionar os dados novamente. Por enquanto, insira uma
imagem PNG. Vamos ver como ficou:
Ah, agora sim! A imagem está corretamente grava em nosso banco de dados, e foi visualizada
corretamente também.
<div id="conteudo">
<h1>Listando usuários</h1>
<table>
<% for usuario in @usuarios %>
<tr>
<td rowspan="4"><img src="<%=h url_for(:action => "img", :id => ↵
usuario.id) %>"/></td>
</tr>
<tr>
<td><h2><%=h usuario.nome %></h2></td>
</tr>
<tr>
<td><%=h usuario.email %></td>
</tr>
<tr>
<td>
<%= link_to 'Mostra', :action => 'show', :id => usuario %>
<%= link_to 'Edita', :action => 'edit', :id => usuario %>
<%= link_to 'Apaga', { :action => 'destroy', :id => usuario },↵
:confirm => 'Are you sure?', :post => true %>
</td>
</tr>
<% end %>
</table>
<p>
<%= link_to 'Página anterior', { :page => ↵
@usuario_pages.current.previous } if @usuario_pages.current.previous %>
<%= link_to 'Próxima página', { :page => @usuario_pages.current.next } ↵
if @usuario_pages.current.next %>
</p>
<p>
<%= link_to 'Novo usuário', {:action => 'new'}, :class => 'novo' %>
</p>
</div>
Um problema que ocorre com upload de imagens (e outros arquivos binários) é que temos que
guardar o MIME type para enviar para o browser quando formos recuperar os dados. No nosso
caso, na ação img do controlador, está fixo como image/png.
Podemos armazenar outros tipos, é claro, mas teríamos que ter mais campos para armazenar o
conteúdo do MIME type. No nosso caso, vamos optar por armazenar somente imagens PNG. Para
criarmos um filtro eficiente, vamos inserir essa trava no modelo.
Para isso, temos que guardar em algum lugar o tipo de arquivo que foi feito o upload. Como não
temos nenhum lugar para guardar isso no banco de dados, vamos criar um atributo temporário
para guardamos o MIME type.
Com isso já estamos armazenando o MIME type. Mas como validar? Como optamos por
armazenar ou não uma imagem, temos que verificar se o usuário está efetuando upload de
algum arquivo, e podemos verificar isso no atributo filename, armazenando-o também.
Agora podemos fazer a validação. No modelo, podemos (re)definir o método validate, que será
protegido (protected) e será chamado para validar o que enviamos através do formulário. Vamos
escreve-lo:
protected
def validate
if self.filename.length>0 and self.content_type !~ /^image\/png/
errors.add(:img,"A imagem tem que ser um PNG! Foi enviado ↵
#{self.content_type} (#{self.filename}).")
end
end
public
def imagem=(img_field)
self.content_type = img_field.content_type
self.filename = img_field.original_filename
self.img = img_field.read
end
end
Para dar uma garibada na tela, vamos limitar os valores informados no formulário, verificando
que nome e email são valores únicos, os tamanhos do nome e senha e o formato do email.
Alteramos o modelo do usuário para:
<div id="conteudo">
<h1>Editando usuario</h1>
<%= form_tag({:action => 'update', :id => @usuario},:multipart => true) %>
<%= render :partial => 'form' %>
<%= submit_tag 'Salvar', :class => 'submit' %>
<%= end_form_tag %>
<p>
<b>Foto atual<b/><br/>
<img src="<%= url_for(:action=>"img",:id=>@usuario.id) %>"/>
</p>
<p>
<%= link_to 'Mostra', :action => 'show', :id => @usuario %> |
<%= link_to 'Volta', :action => 'list' %>
</p>
</div>
Agora que já temos um cadastro de usuários, podemos fazer uma tela de login. Vamos criar um
controlador de login para isso, com duas ações, login e logout:
[taq@/var/www/htdocs/livraria]ruby script/generate controller Login ↵
login logout
<div id="conteudo">
<h1>Identificação de usuário</h1>
<%= form_tag %>
<table>
<tr>
<td>Email</td>
<td><%= text_field("usuario","email") %></td>
</tr>
<tr>
<td>Senha</td>
<td><%= password_field("usuario","senha") %></td>
</tr>
</table>
<%= submit_tag "Entrar no sistema", :class => "submit" %>
<%= end_form_tag %>
</div>
Por enquanto, vamos usar o layout de admin, depois mudaremos isso. Essa view vai ser
acessada de duas maneiras: visualizando normalmente e tendo os dados do seu formulário
enviados. Vejamos como fica visualizando normalmente:
class LoginController < ApplicationController
layout "admin"
def login
end
def logout
end
end
Do jeito que está, essa tela não faz nada. O que queremos que ela faça é armazenar o id e o
status do usuário em uma sessão, se a senha estiver correta. Se o usuário processou o
formulário, vamos consultar e verificar a senha no banco de dados. Se estiver visualizando a tela,
vamos inicialmente zerar as informações da sessão. Podemos diferenciar se o formulário foi
processado ou a página visualizada utilizando request.get?:
class LoginController < ApplicationController
layout "admin"
def login
if request.get?
session[:user_id] = nil
session[:user_nome]= nil
session[:user_adm] = nil
@usuario = Usuario.new
end
end
def logout
end
end
Uma coisa interessante de se notar: na construção da condição do find, utilizei um array com
[" email = ? and senha = ?",@usuario.email,@usuario.senha]
Isso vai fazer com que, quando o comando for montado, seja “dado uma geral” nele para evitar
alguma coisa estranha que possa permitir algum ataque de SQL Injection 9. Seria fácil fazer esse
tipo de coisa se utilizássemos
“email = '#{@usuario.email}' and senha='#{@usuario.senha}'”
Do jeito que fizemos no primeiro (e correto) exemplo, estamos utilizando alguns bindings, nesse
caso, pontos de interrogação, indicando para o Rails onde inserir os valores das variáveis que
indicamos a seguir, dando tratamento adequando para as variáveis, para evitar que alguém
tente fazer alguma gracinha.
Se os dados foram encontrados usando find, já podemos armazenar os dados na sessão e
redirecionar o usuário para ... opa, para onde? Quero uma página inicial de administração, nesse
caso. Para o usuário “normal”, ainda não fizemos nada. Nesse caso, vamos criar uma ação
default para o controlador admin:
[taq@/var/www/htdocs/livraria]ruby script/generate controller Admin::Admin
exists app/controllers/admin
exists app/helpers/admin
create app/views/admin/admin
exists test/functional/admin
create app/controllers/admin/admin_controller.rb
create test/functional/admin/admin_controller_test.rb
create app/helpers/admin/admin_helper.rb
def index
render :action => 'index'
end
end
<div id="conteudo">
<h1>Tela de administração</h1>
<h2>Bem-vindo, <%= session[:user_nome] %></h2>
</div>
9 SQL Injection é uma técnica que permite “craquear” os comandos enviados para um banco
de dados através de alguns caracteres específicos para essa finalidade.
Agora já podemos alterar o layout de admin. Vamos inserir um menu em
<aplicação>/app/views/layouts/admin.rhtml:
<div id="menu">
<ul>
<li><%= link_to "Inicial",:controller=>"admin",:action=>"index" %></li>
<li><%= link_to "Tipos",:controller => "tipo" , :action => "list" %></li>
<li><%= link_to "Categorias",:controller=>"categoria",:action=>"list"
%></li>
<li><%= link_to "Produtos",:controller=>"produto",:action=>"list" %></li>
<li><%= link_to "Usuários",:controller=>"usuario",:action=>"list" %></li>
<li><%= link_to "Logout "+(session[:user_nome].nil? ? "" :
session[:user_nome]),{:controller => "../login" ,:action => "logout"},
:confirm => "Tem certeza que deseja fazer logout?" %></li>
</ul>
</div>
<div id="msgs">
<%= flash[:notice] %>
</div>
#conteudo {
float:left;
}
#msgs {
color:green;
font-weight:bold;
}
#menu ul {
padding:0;
margin:0;
float:left;
width:100%;
list-style:none;
background:black;
}
#menu li {
display:inline;
}
#menu li a {
padding:5px;
border:1px solid black;
text-decoration:none;
width:100px;
background:#eee;
float:left;
}
Vamos ter:
def logout
session[:user_id] = nil
session[:user_nome] = nil
session[:user_adm] = nil
redirect_to(:action => "login")
end
Agora uma coisa muito importante. Não podemos de modo algum dar acesso a qualquer um
nas nossas telas de administração de sistema. Já que temos os usuários cadastrados e métodos
eficientes de login e logout, podemos verificar se o usuário está logado e autorizado a usar as
telas de administração. Para isso vamos alterar os controladores, primeiro da aplicação toda
usando o arquivo <aplicação>/app/controllers/application.rb:
Definimos dois métodos, um para verificar se o usuário é um administrador e outro para verificar
se está logado no sistema.
Agora vamos alterar os controladores específicos, começando com tipo:
e por aí vai. Uma das coisas que você vai notar é que, na tela de login, enquanto não efetuado o
login, tentarmos clicar em alguma das opções, vamos ter um erro, pois não estamos dentro do
controlador admin para especificarmos as ações abaixo dele. Inclusive no caso de usarmos
controladores com ... subcontroladores, que seja esse o nome de um controlador que se
encontra em um subdiretório, é bom utilizarmos o caminho completo dos controladores na url_to,
levando em conta que a barra (/) vai significar o diretório “raiz” dos controladores
(<aplicacao>/app/controllers).
<div id="menu">
<ul>
<li><%= link_to "Inicial" ,:controller => "/admin/admin"
,:action => "index" %></li>
<li><%= link_to "Tipos" ,:controller => "/admin/tipo"
,:action => "list" %></li>
<li><%= link_to "Categorias",:controller =>
"/admin/categoria",:action => "list" %></li>
<li><%= link_to "Produtos" ,:controller => "/admin/produto"
,:action => "list" %></li>
<li><%= link_to "Usuários" ,:controller => "/admin/usuario"
,:action => "list" %></li>
<li><%= link_to "Logout "+(session[:user_nome].nil? ? "" :
session[:user_nome]),{:controller => "/login" ,:action => "logout"}, :confirm
=> "Tem certeza que deseja fazer logout?" %></li>
</ul>
</div>
Agora, quando vamos para o controlador admin, podemos acessar o controlador login
normalmente. Mas, como o processo de login não é exclusivo dos administradores do sistema,
vamos alterar o layout para o layout externo da nossa livraria, que vai começar a ser construído
agora.
Olhem o menu (após ajustar o arquivo CSS praticamente inserindo as regras de *_livraria nas
regras do menu do admin:
Ilustração 29: Tela de login de usuário na livraria, com menu
Ficou bonitinho, mas não esperem um clássico (ou mesmo uma coisa meio termo, vá lá) de
design. Vamos fazer somente para quebrar um galho. :-)
Uma coisa a ser notada é que eu utilizei o método pluralize na descrição do tipo. Esse método
não é um método padrão de Strings do Ruby, e sim um que foi adicionado à todas as Strings
processadas no Rails, através do Active Support e da tremenda flexiblidade do Ruby.
Clicando em Livros:
Oooops, esquecemos de personalizar as ações. A primeira coisa é alterar o controlador para que
ele use o layout livraria, depois personalizando
<aplicacao>/app/controllers/produto_controller.rb. Vamos indicar que queremos usar o layout
livraria:
e agora vamos configurar a ação list, primeiro verificando qual foi o id enviado no menu
(reparem acima que eu especifiquei o id do menu como tipo.descrição, onde vai ser enviado, por
exemplo, Livro), encontrando o id do tipo de acordo com a descrição na tabela Tipo e paginando
os produtos de acordo com esse tipo:
def list
tipo = Tipo.find(:first, :conditions => ["descricao = ?",params[:id]])
@produto_pages, @produtos = paginate :produtos, :per_page => 10, ↵
:conditions => ["tipo_id = ?",tipo.id]
end
A ação list vai retornar páginas com 10 produtos cada, que tenham como tipo_id o valor
encontrado como id do tipo (Livro,CD,etc.) enviado como parâmetro.
<div id="conteudo_livraria">
<h1>Listando <%= params[:id] %>s</h1>
Olhem nossos produtos aparecendo (após cadastrar mais alguns – sintam-se a vontade para
criarem quantos tipos, categorias e produtos quiserem):
Vamos dar mais uma incrementada nessa página. Sabemos que os produtos tem categorias,
então vamos criar uma listagem de categorias por produto. A primeira coisa que precisamos
fazer é providenciar uma listagem das categorias dos produtos do tipo requisitado. Temos duas
opções:
Fica a critério de cada um qual opção escolher. Vamos ver primeiro como ficaria a listagem das
categorias já filtradas, alterando a view list (e o arquivo CSS, a gosto do freguês, também):
<div id="categorias">
<h2>Categorias</h2>
<ol>
<% for categoria in @categorias %>
<li><%= link_to categoria, :action => "list", :categoria => categoria
%></li>
<% end %>
</ol>
</div>
<div id="conteudo_livraria">
<h1>Listando <%= params[:id] %>s</h1>
Vamos atentar nesse momento somente o parâmetro :categoria que enviei no método link_to:
ele vai criar uma URL com o tipo corrente e a categoria corrente. Vamos ver isso logo que
verificarmos como ficou nossa tela:
http://localhost:3000/produto/list/Livro?categoria=Ficção
O que podemos reparar é que na listagem do tipo, estamos vendo os produtos daquele tipo e de
todas as suas categorias. Vamos ver como filtrar isso logo, mas agora vamos ver como que
filtramos as categorias.
def list
tipo = Tipo.find(:first, :conditions => ["descricao = ? ↵
",params[:id]])
@categorias = Produto.find(:all,:conditions => ["tipo_id = ? ↵
",tipo.id]).map{ |e| e.categoria.descricao }.uniq.sort
@produto_pages, @produtos = paginate :produtos, :per_page => 10, ↵
:conditions => ["tipo_id = ?",tipo.id]
end
Destrinchando o que aconteceu ali, com uma aulinha rápida de Ruby (ei, comprem o meu livro!
;-):
1. Produto.find trouxe uma lista de todos (:all) os produtos que correspondiam á condição
especificada (tipo_id=<id>) em um array;
2. Utilizando map, selecionei os valores das descrições dos tipos;
3. Utilizando uniq, selecionei os valores únicos das descrições (sem repetir);
4. Utilizando sort, ordenei os valores;
O resultado disso tudo foi apresentado na view list, como mostrado acima.
def list
tipo = Tipo.find(:first, :conditions => ["descricao = ?",params[:id]])
Vejam que eu enviei a query completa (eu podia ter usado uma subquery ali mas a versão do
MySQL que tenho aqui não suporta isso) e um ponto bem importante, são retornados objetos
com uma propriedade descricao e não mais Strings como no nosso primeiro modo. Então temos
que alterar a view da list também:
<div id="categorias">
<h2>Categorias</h2>
<ol>
<% for categoria in @categorias %>
<li><%= link_to categoria.descricao, :action => "list", :categoria => categoria.descricao ↵
%></li>
<% end %>
</ol>
</div>
<div id="conteudo_livraria">
<h1>Listando <%= params[:id] %>s</h1>
...
Agora podemos limitar nossa listagem por categoria, se desejado. Vamos alterar o controlador de
produto:
def list
tipo = Tipo.find(:first, :conditions => ["descricao = ? ↵ ",params[:id]])
categoria = Categoria.find(:first, :conditions => ["descricao = ? ↵
",params[:categoria]])
# @categorias = Produto.find(:all,:conditions => ["tipo_id = ? ↵
",tipo.id]).map{ |e| e.categoria.descricao }.uniq.sort
@categoria = params[:categoria]
@categorias = Produto.find_by_sql ["select distinct a.descricao from ↵
categorias a, produtos b where a.id=b.categoria_id and b.tipo_id=? order by ↵
a.descricao",tipo.id]
conditions = categoria.nil? ? ["tipo_id = ?",tipo.id] : ["tipo_id = ? ↵
and categoria_id = ?",tipo.id,categoria.id]
@produto_pages, @produtos = paginate :produtos, :per_page => 10,↵
:conditions => conditions
end
Primeiro tentei retornar o id da categoria que talvez foi passada como parâmetro. Se não foi,
retorna nulo. Aproveitei e salvei como uma variável de instância (leia o livro de Ruby! ;-).
Depois verificando se houve ou não uma categoria como parâmetro, alterei a condição de
paginação dos produtos e a view de list:
<div id="conteudo_livraria">
<h1>Listando <%= params[:id] %>s</h1>
<% if ! @categoria.nil? %>
<h2><%= @categoria %></h2>
<% end %>
<% for produto in @produtos %>
...
Ou seja, se houve uma categoria especificada, vai ser mostrada ali na página. Os resultados são:
Agora temos que melhorar um pouco esses produtos, não? Que tal inserirmos o nome dos
autores e uma imagem do produto? Pelo que aprendemos até agora fica fácil, e vamos alterar o
banco de dados mesmo depois de ter criado o modelo. Vale lembrar que estamos fazendo uma
coisa mais descompromissada para efeito didático. Os nomes dos autores ficariam melhor se
armazenados em uma tabela de autores blá blá blá mas vocês entenderam né? :-)
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> alter table produtos add autor varchar(75) not null default "";
Query OK, 5 rows affected (0,14 sec)
Records: 5 Duplicates: 0 Warnings: 0
No caso do produto, estou supondo que vamos armazenar imagens maiores, então usei o tipo de
dado mediumblob para o campo da imagem.
def img
@produto = Produto.find(params[:id])
send_data(@produto.img,:type=>"image/png",:disposition=>"inline")
end
<div id="conteudo">
<h1>Novo produto</h1>
<%= form_tag({:action => 'create'},:multipart => true) %>
Outra coisa para alterarmos, especificamente no caso de produto, é inserirmos os arrays de tipo
e categoria na ação update:
def update
@produto = Produto.find(params[:id])
@tipos = Tipo.find_all.collect {|t| [t.descricao,t.id]}
@categorias = Categoria.find_all.collect {|c| [c.descricao,c.id]}
...
A razão disso é que inserimos um método validate também no modelo, para aceitar apenas PNGs
(sim, eu gosto de PNGs ;-):
protected
def validate
if self.filename.length>0 and self.content_type !~ /^image\/png/
errors.add(:img,"A imagem tem que ser um PNG! Foi enviado ↵
#{self.content_type} (#{self.filename}).")
end
end
if @produto.update_attributes(params[:produto])
flash[:notice] = 'Produto foi atualizado com sucesso.'
redirect_to :action => 'show', :id => @produto
else
render :action => 'edit'
end
O que acontece é que, se houver algum problema na atualização, será executado o método
render com a ação edit. Nesse ponto, a tela será recarregada, e se não constarem os arrays com
as opções dos selects, vai ser gerado um erro, pois os selects de tipo e categoria não poderão
ser montandos com valores nulos. Apesar da ação edit constar com
def edit
@produto = Produto.find(params[:id])
@tipos = Tipo.find_all.collect {|t| [t.descricao,t.id]}
@categorias = Categoria.find_all.collect {|c| [c.descricao,c.id]}
end
tivéssemos
Alterando o modelo de produto, da mesma maneira que fizemos com o modelo de usuario :
public
def imagem=(img_field)
self.content_type = img_field.content_type
self.filename = img_field.original_filename
self.img = img_field.read
end
<!--[eoform:produto]-->
<div id="conteudo">
<p><b>Descrição:</b> <%=h @produto.descricao %></p>
<p><b>Autor:</b> <%=h @produto.autor %></p>
<p><b>Tipo:</b> <%=h @produto.tipo.descricao %></p>
<p><b>Categoria:</b> <%=h @produto.categoria.descricao %></p>
<p>
<img src="<%=h url_for(:action => "img", :id => @produto.id) %>"/>
</p>
<p>
<%= link_to 'Editar', :action => 'edit', :id => @produto %> |
<%= link_to 'Voltar', :action => 'list' %>
</p>
</div>
<table>
<% for produto in @produtos %>
<tr>
<td><img width="50%" height="50%" src="<%=h url_for(:action => "img", ↵
:id => produto.id) %>"/></td>
<td valign="top">
<b><%=h produto.descricao %></b><br/>
<%=h produto.autor %><br/>
<%=h produto.tipo.descricao %><br/>
<%=h produto.categoria.descricao %><br/>
<%= link_to 'Mostrar', :action => 'show', :id => produto %>
<%= link_to 'Editar', :action => 'edit', :id => produto %>
<%= link_to 'Apagar', { :action => 'destroy', :id => produto },
:confirm => 'Tem certeza?', :post => true %>
</td>
</tr>
...
<div id="conteudo_livraria">
<h1>Listando <%= params[:id] %>s</h1>
<% if ! @categoria.nil? %>
<h2><%= @categoria %></h2>
<% end %>
<% for produto in @produtos %>
<p>
<img width="100px" style="float:left;margin:0 15px 15px 0;" ↵
src="<%=h url_for(:action => "img", :id => produto.id) %>"/>
<b><%= produto.descricao %></b><br/>
<i><%= produto.autor %></i><br/>
<%= link_to "Ampliar a capa", {:action => "img", :id => produto.id},
:popup => true %>
</p>
<hr style="clear:both;"/>
<% end %>
<p><%= pagination_links(@produto_pages) %>
</div>
A novidade ficou por conta do link para ampliar a capa, que foi destacado em vermelho na
listagem da view list.
def list
...
@produto_pages, @produtos = paginate :produtos, :per_page => 10, ↵
:conditions => conditions, :order => "autor,descricao"
end
Uma coisa a ser arrumada já. Vamos supor que acessamos a URL
http://localhost:3000/produto/list
pois não foi especificado nenhum id para listar. Para especificar um default, podemos usar no
controlador:
def list
params[:id] ||= "Livro"
tipo = Tipo.find(:first, :conditions => ["descricao = ? ↵
",params[:id]])
...
Isso vai redirecionar automaticamente para a seção de livros. Podemos dar uma monitorada nas
vezes que o usuário acessou a URL sem o id, usando o recurso dos loggers, que vão gravar
mensagens que apontamos no log do sistema. É muito fácil usar esse tipo de recurso, vamos
alterar a ação list novamente:
def list
logger.warn("Tentativa de listar produtos sem tipo definido") if ↵
params[:id].nil?
params[:id] ||= "Livro"
http://localhost:3000
Para alterarmos isso, primeiro temos que apagar o arquivo index.html no diretório
<aplicação>/public e inserir em <aplicacao>/config/routes.rb:
map.connect '', :controller => "produto", :action => "list", :id => "Livro"
Com isso estamos redirecionando a URL root para a ação list do controlador produto.
Pesquisando
Vamos implementar um campo de pesquisa agora na nossa livraria. Dei uma garibada no layout
de livraria:
<div id="topo_livraria">
<table width="100%">
<tr>
<td width="100px;"><%= image_tag("acme.png") %></td>
<td><h1>A livraria nos trilhos do sucesso!</h1></td>
</tr>
<tr>
<td colspan="2" align="right">
<%= start_form_tag :action => "search" %>
<label for="busca_str">Buscar</label>
<%= text_field "busca", "str" %>
<%= end_form_tag %>
</td>
</tr>
</table>
</div>
def search
@term = params[:busca][:str]
@tipos = Tipo.find(:all).map{|e| e.descricao}.uniq
@produtos = Produto.find(:all,:conditions => ["descricao like ? ↵
","%#{@term}%"],:order => "tipo_id,categoria_id,descricao")
end
O conteúdo do campo texto do formulário criado no topo do site será salvo em @term. Também
criamos uma lista de tipos de produtos disponíveis no site, para apresentar do lado esquerdo da
busca (para que replicar o menu logo acima não sei, mas já fica como exercício descobrir alguma
coisa de útil para por ali e não ficar vazio ;-) e em seguida procuramos todos os produtos que
“casam” com o que procuramos no campo texto. Vejam que eu usei os bindings de variáveis mas
fiz uma interpolação de expressão (aquele negócio com os #{}) usando o termo.
Também devemos criar uma view de search para que possa interpretar os resultados que
acabamos de criar no controlador. Inseri isso na view:
<div id="categorias">
<h2>Produtos</h2>
<ul>
<% for tipo in @tipos %>
<li><%= link_to tipo.pluralize, :action => "list" %></li>
<% end %>
</ul>
</div>
<div id="conteudo_livraria">
<h1>Busca de produtos - procurando '<%= @term %>'</h1>
<% for produto in @produtos %>
<p>
<img width="100px" style="float:left;margin:0 15px 15px 0;" src="<%=h ↵
url_for(:action => "img", :id => produto.id) %>"/>
<i>em <%= produto.tipo.descricao %>s</i><br/>
<b><%= produto.descricao %></b><br/>
<i><%= produto.autor %></i><br/>
<%= link_to "Ampliar a capa", {:action => "img", :id => produto.id}, ↵
:popup => true %>
</p>
<hr style="clear:both;"/>
<% end %>
</div>
Renderizações parciais
Podemos notar uma certa repetição ali, mostrando o produto. Vamos fazer o seguinte, retirar
aquele for e aprender sobre partials. O que seria isso? Podemos pensar como um desvio de
renderização da página corrente para outra página, com os parâmetros que quisermos enviar.
No caso do nosso produto, primeiro temos que definir um arquivo com o conteúdo do que será
renderizado. Os templates parciais tem que começar com um sublinhado (_) e ficar abaixo de
<aplicação>/app/views.
No nosso caso, vamos definir <aplicacao>/app/views/produto/_produto.rhtml:
<p>
<img width="100px" style="float:left;margin:0 15px 15px 0;" src="<%=h ↵
url_for(:action => "img", :id => produto.id) %>"/>
<b><%= produto.descricao %></b><br/>
<i><%= produto.autor %></i><br/>
<%= link_to "Ampliar a capa", {:action => "img", :id => produto.id}, ↵
:popup => true %>
</p>
<hr style="clear:both;"/>
Prestando atenção, vemos que é o código que estava dentro do for na list, e podemos inclusive
eliminar todo o código do for da view search que implementamos acima!
Vamos dar uma olhada como ficou o <aplicacao>/app/views/produto/list.rhtml agora:
<div id="categorias">
<h2>Categorias</h2>
<ol>
<% for categoria in @categorias %>
<li><%= link_to categoria.descricao, :action => "list", :categoria => ↵
categoria.descricao %></li>
<% end %>
</ol>
</div>
<div id="conteudo_livraria">
<h1>Listando <%= params[:id] %>s</h1>
<% if ! @categoria.nil? %>
<h2><%= @categoria %></h2>
<% end %>
<%= render(:partial => "produto", :collection => @produtos) %>
<p><%= pagination_links(@produto_pages) %></p>
</div>
Uau! Bem mais enxuto hein? O que acontece é que o render usa como parâmetro o partial que
vai renderizar (sem o sublinhado) e armazena o valor de cada item da coleção de objetos
(indicada por :collection) que enviamos em @produtos em uma váriavel com o nome do
template (indicado em :partial) em chamada produto também.
Para mostrar como podemos usar o partial com apenas um objeto, vamos primeiro fazer algumas
alterações nos nossos produtos. Vamos inserir um campo na tabela do banco de dados chamado
detalhes, onde vamos dar um pouco mais de detalhes sobre o produto:
alter table produtos add detalhes text
Isso vai nos dar um campo de tamanho considerável para uma boa descrição (65535 caracteres).
Agora vamos alterar <aplicacao>/app/view/admin/produto/_form.rhtml para contemplar o campo
novo, vamos criá-lo como uma área de texto com 100 colunas e 5 linhas:
<p><label for="produto_detalhes">Detalhes</label><br/>
<%= text_area "produto", "detalhes", :cols => "100", :rows=>"5" %>
Agora precisamos de um jeito de ver isso no site externo. Podemos configurar a ação show no
nosso controlador de produto:
def show
@produto = Produto.find(params[:id])
end
<%= link_to "Ampliar a capa", {:action => "img" , :id => produto.id}, ↵
:popup => true %><br/>
<%= link_to "Mais detalhes" , {:action => "show", :id => produto.id} %>
E finalmente alterando a view show. Nesse caso, poderíamos até já deixar essa view responsável
por mostrar o produto, mas fica mais prático criar uma partial que pode ser renderizada em
qualquer outro ponto que necessitamos, ajudando no DRY10 (apesar de falar do DRY, não me
preocupei muito com isso em certos pontos por achar que para efeito didático ás vezes é melhor
fazer uma “lambança” em certos pontos).
<p>
<img width="100px" style="float:left;margin:0 15px 15px 0;" src="<%=h ↵
url_for(:action => "img", :id => produtodetalhe.id) %>"/>
<b><%= produtodetalhe.descricao %></b><br/>
<i><%= produtodetalhe.autor %></i><br/>
<%= link_to "Ampliar a capa", {:action => "img" , :id => ↵
produtodetalhe.id}, :popup => true %>
<br style="clear:both;"/>
<%= produtodetalhe.detalhes.nil? ? “” : ↵
produtodetalhe.detalhes.split("\n").map {|v| v =~ /^<li>/ ? ↵
v : v+"<br/>" }.join %>
</p>
Aqui fiz uma jogadinha interessante: quando cadastramos os detalhes do produto, a quebra de
linha é armazenada no banco de dados com o caracter \n, que não serve para nada em uma
10 Don't Repeat Yourself, ou seja, não faça a mesma coisa mais de uma vez gerando
retrabalho e redundância. Não é aplicável em situações, tipo, na sua lua-de-mel.
página HTML, tendo que ser substituído por uma tag <br/> para que a linha seja quebrada. Só
que no caso do produto que cadastrei, usei tags HTML para negrito (<b>) em algumas partes e
uma lista ordenada (<ol>) para as músicas. Como cada item da lista (<li>) já gera uma quebra
de linha automática, eu tive que substituir o caracter \n com <br> apenas nas linhas que não
começam com <li>. O que fiz foi:
1. Primeiro, verificar se tem conteúdo nos detalhes, se não tiver, não imprime nada.
2. Criar um array “quebrando” a string do detalhe sempre que encontrar um \n
(detalhes.split(“\n”));
3. Usar map para processar todos os elementos do Array e retornar um Array novo;
4. Se o elemento corrente iniciar com <li>, fica sem alteração;
5. Se não, retorna o elemento com um <br/> no final;
6. Usamos o método join para transformar o Array em uma String.
<div id="conteudo_livraria">
<h1>Detalhes do produto</h1>
<%= render(:partial => "produtodetalhe", :object => @produto) %>
</div>
Ah-há, aqui vemos que ao invés de :collection, como visto anteriormente quando tínhamos vários
objetos, passamos o objeto retornado pela ação show e o enviamos para a partial com :object.
Agora, e se precisarmos dessa partial fora de produto, vamos imaginar, em uma ação onde se
lista os produtos no carrinho de compras? Para isso, podemos criar um diretório chamado,
digamos, partials, abaixo de <aplicação>/app/views, movemos _produtodetalhe.rhtml para lá e
alteramos a view show que acabamos de mostrar para:
<div id="conteudo_livraria">
<h1>Detalhes do produto</h1>
<%= render(:partial => "partials/produtodetalhe", :object => @produto) %>
</div>
Se a partial vê que foi passado um path (identificando pela presença de uma barra), procura
automaticamente abaixo de <aplicação>/app/view o diretório e a partial especificada. Assim
podemos criar partials comuns para toda a aplicação. Isso ajuda no DRY também. Podemos
mover o nosso partial _produtos.rhtml para esse diretório também e alterar as ações list e
search.
Não, não é aquele produto de limpeza que a sua mãe usava. Se você ficou fora do planeta em
2005 ou por acaso não leu nenhum site de tecnologia e não sabe o que é Ajax, a grosso modo é
a designação para Asynchronous Javascript and XML, uma técnica que permite desenvolver
aplicações interativas que alteram elementos de uma página sem precisar recarregar a mesma
usando a tradicional metodologia de envio dos dados através de um POST. Isso permite que
atualizemos apenas alguns determinados elementos de nossa página através de requisições
para scripts externos.
Para um exemplo fácil, vamos imaginar que os responsáveis pelo nosso site podem inserir
comentários nos produtos (não vamos deixar os usuários fazerem isso a não ser que tenhamos
pessoal para revisar as barbaridades que eles podem escrever lá, “kd vc kr me manda um scrap
blz vlw fuizzzzzz, argh” certo?) e queremos que esses comentários sejam mostrados quando o
produto for visualizado, através do click em um link. Antes de mais nada precisamos da estrutura
para manipular nossos comentários, através de uma tabela como:
create table comentarios (
id int auto_increment,
produto_id int not null,
data date,
comentario varchar(250) not null,
primary key(id)
);
Vamos usar um campo data para marcar quando o comentário foi inserido e ordenar em ordem
decrescente. Toda a estrutura de manutenção eu deixo por conta de vocês – basta seguir o que
fizemos com tipo e categoria. Só lembre de especificar as relações, em produto:
class Produto < ActiveRecord::Base
has_many :comentarios
...
e comentário:
class Comentario < ActiveRecord::Base
belongs_to :produto
end
Uma parte muito importante ali: estou pedindo que seja renderizado sem a aplicação do layout.
Faz sentido, se lembrarmos que estamos atualizando somente uma parte da tela e não
precisamos de todo o código que é gerado no layout, pois o nosso elemento ficaria praticamente
com toda a estrutura de uma página completa se deixássemos o layout!
Vamos dar uma olhada no nosso partial dos comentários:
<b>Comentário(s)</b><br/>
<% for comentario in produtocomentarios.comentarios.sort_by{|o|
o.data}.reverse %>
"<%= comentario.comentario %>"<br/>
<% end %>
<% if produtocomentarios.comentarios.size < 1 %>
<i>Sem comentários cadastrados.</i>
<% end %>
Um pequeno exercício com Ruby que fiz, em destaque em vermelho, foi ordenar os comentários
pela sua data (com sort_by) e inverter a ordem do array gerado (com reverse).
E agora no partial do Produto, onde vamos inserir um link para que sejam visualizados os
comentários do produto em questão:
<p>
<img width="100px" style="float:left;margin:0 15px 15px 0;" src="<%=h
url_for(:action => "img", :id => produto.id) %>"/>
<b><%= produto.descricao %></b><br/>
<i><%= produto.autor %></i><br/>
<%= link_to "Ampliar a capa", {:action => "img" , :id => produto.id},
:popup => true %><br/>
<%= link_to "Mais detalhes" , {:action => "show", :id => produto.id} %>
<p id="comentario_<%= produto.id %>">
<%= link_to_remote "Clique aqui para comentários", :update => ↵
"comentario_#{produto.id}", :complete => "new
Effect.Highlight('comentario_#{produto.id}')", :url => {:action => ↵
:comentarios, :id => produto.id} %>
</p>
</p>
<hr style="clear:both;"/>
Vamos dar uma analisada ali. Eu criei um novo parágrafo (<p>) dentro do parágrafo do produto,
com um id chamado “comentário_” concatenado com o id do produto (ou seja, comentario_1,
comentario_2, etc.).
Dentro do parágrafo criei um link com link_to_remote com o texto “Clique aqui para
comentários”, indiquei que o elemento que vai ser atualizado quando clicarmos no link é o que
acabamos de criar acima (comentario_<id>), e fiz uma firulinha, indiquei que quando o conteúdo
tiver sido completamente atualizado devemos acionar um dos efeitos de apresentação
disponibilizados pelo Rails, nesse caso, Effect.Highlight(<elemento>), que vai mostrar o
elemento indo de uma coloração amarela para branca.
Temos mais efeitos, segue uma lista descritiva:
• Effect.Appear - Mostra gradativamente o elemento.
• Effect.Fade - Apaga gradativamente o elemento.
• Effect.Puff - Apaga o elemento através de uma animação.
• Effect.BlindDown - Efeito “persiana abaixo”.
• Effect.BlindUp - Efeito “persiana acima”.
• Effect.SwitchOff - Apaga o elemento “dobrando” o mesmo.
• Effect.SlideDown - Desliza o elemento para baixo.
• Effect.SlideUp - Desliza o elemento para cima.
• Effect.DropOut - Apaga o elemento deslizando para baixo.
• Effect.Shake - Dá uma “tremida” no elemento.
• Effect.Pulsate - Faz o elemento “pulsar”, sumindo e aparecendo gradativamente.
• Effect.Squish - Apaga o elemento reduzindo seu tamanho até sumir.
• Effect.Fold - Apaga o elementod deslizando para cima e á esquerda.
• Effect.Grow - Mostra o elemento aumentando o seu tamanho.
• Effect.Shrink - Apaga o elemento reduzindo o seu tamanho.
• Effect.Highlight - Altera o background de amarelo para branco gradativamente.
Mas e aí, onde vamos pegar o conteúdo para atualizar toda essa estrutura que fizemos aí em
cima? Vamos pegar através do que foi indicado por :url, nesse caso a ação (:action)
comentarios (no mesmo controlador que estamos), enviando o id do produto atual com :id
(produto.id).
Mas calma! Antes de testar o que fizemos, temos que inserir o código milagroso que vai fazer
nossas requisições do Ajax e os efeitos do Javascript. Vamos alterar o layout da livraria:
<head>
<title>Livraria ACME</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<%= stylesheet_link_tag "livraria" %>
<%= javascript_include_tag "prototype", "effects" %>
</head>
Olhem os links para os comentários! Vamos clicar em algum (após inserir algum comentário no
produto, lógico) e teremos:
Agora o último detalhe: vamos inserir o partial dos comentários dentro do partial dos detalhes do
produto:
<p>
<img width="100px" style="float:left;margin:0 15px 15px 0;" src="<%=h
url_for(:action => "img", :id => produtodetalhe.id) %>"/>
<b><%= produtodetalhe.descricao %></b><br/>
<i><%= produtodetalhe.autor %></i><br/>
<%= link_to "Ampliar a capa", {:action => "img" , :id => ↵
produtodetalhe.id}, :popup => true %>
<br style="clear:both;"/>
<%= produtodetalhe.detalhes.nil? ? "" :
produtodetalhe.detalhes.split("\n").map {|v| v =~ /^<li>/ ? v : v+"<br/>" ↵
}.join %>
<%= render(:partial => "partials/produtocomentarios", :object => ↵
produtodetalhe) %>
</p>
Parecem bloquinhos de LEGO™ né? Dando uma olhada em como ficaram os detalhes do produto:
Usamos o Ajax para alterar dinamicamente um elemento da nossa página e ainda criamos mais
uma “pecinha” para a visualização do nosso produto. Bom. :-)
Finalizando
Lógico que em uma livraria virtual real precisamos de muito mais coisas: carrinho de compras,
opções de pagamento, cálculo de frete, etc. etc. etc. Isso vai um pouco além do escopo desse
tutorial (uau, já chegamos em mais de 50 páginas), mas espero que vocês tenham aproveitado o
que foi apresentado até aqui para ter uma idéia (boa, espero ;-) do que o Rails (e Ruby!) pode
fazer por vocês. A partir do ponto em que paramos vocês podem criar todas as features que eu
mencionei acima e muitas outras mais, além de fazer melhorias no layout e no CSS do que foi
apresentado, pois fiz meio rapidinho para poder escrever mais. :-)
É isso, obrigado por me acompanhar até aqui. ;-)
Índice alfabético
A
Active Record..................................................................................................... ..................10, 11
Ajax................................................................................................................................ ......61, 63
Apache................................................................................................................................... ......8
Application.rb................................................................................................................. ............40
B
Banco de dados..................................................... ..................6, 7, 8, 9, 10, 28, 29, 31, 37, 45, 48
Belongs_to................................................................................................................ ......20, 21, 23
Bindings de variáveis............................................................................................................. .4, 55
C
Charset............................................................................................................. .........................13
Check_box.................................................................................................................... ........24, 25
Controlador 9, 11, 12, 14, 15, 16, 19, 20, 21, 28, 30, 36, 38, 40, 41, 43, 45, 46, 48, 52, 53, 54, 55,
56
CRUD.............................................................................................................. ...........................61
D
Download........................................................................................................................ .............6
DRY....................................................................................................................... ...............58, 59
E
Edit.rhtml.................................................................................................................. .................20
Effect.............................................................................................................. ...........................62
F
File_field.............................................................................................. .....................24, 25, 28, 50
Find_by_sql....................................................................................................................... ....11, 46
Foreign keys.................................................................................................................... .......7, 20
G
GNU/Linux.............................................................................................................. ..................5, 6
H
H................................................................................................................................... .............14
Has_many............................................................................................... .............................20, 23
Hash........................................................................................................... ...............................15
Html_escape................................................................................................ ..............................14
J
Javascript................................................................................................... ..........................61, 63
L
Layout.................................................. ...........................11, 12, 13, 16, 36, 37, 38, 39, 41, 42, 44
Lighttpd.......................................................................................................... .............................8
Link_to............................................ .....................15, 24, 27, 30, 35, 39, 40, 41, 42, 45, 46, 50, 51
List.rhtml........................................................................................................... .............13, 16, 27
Livro.............................................................................. ..............1, 4, 5, 19, 44, 45, 46, 47, 53, 54
Loggers....................................................................................................................... ...............54
M
Métodos de procura dinâmicos................................................................................... ................11
MIME............................................................................................................. .......................30, 31
Modelo.............................................. .................4, 9, 10, 11, 17, 20, 24, 28, 29, 31, 33, 45, 48, 49
Multipart............................................................................................ .................24, 25, 35, 48, 49
MVC..................................................................................................................... ........................4
Mysql......................................................................................................................... .....6, 7, 8, 48
MySQL................................................................................................................ ..............6, 46, 48
N
New.rhtml................................................................................................................... ....14, 17, 24
O
ORM................................................................................................................... ....................9, 10
P
Partial..................................................................................... .....15, 35, 57, 58, 59, 61, 62, 63, 64
Partials.................................................................................................................. ...............56, 59
Password_field..................................................................................................... ...........24, 25, 36
Pluralize......................................................................................................................... .42, 43, 56
R
Render....................................................................... .......................12, 14, 15, 35, 38, 49, 57, 59
Request.get?............................................................................................................... ...............37
Reverse................................................................................................................... .............61, 62
Ruby........................................................ .1, 4, 5, 6, 8, 9, 10, 11, 16, 20, 36, 38, 43, 46, 47, 56, 65
S
Scaffold............................................................................................. ........8, 12, 15, 16, 20, 21, 24
Session........................................................................................... ....................37, 38, 39, 40, 41
Show.rhtml........................................................................................................................... ......20
Sort_by........................................................................................................................ .........61, 62
SQL Injection.............................................................................................................. ................38
T
Text_field........................................................................................................... .......22, 24, 36, 55
U
Upload.................................................................................................................... ........25, 30, 31
Url_to.................................................................................................................. .......................41
V
Visualizadores......................................................................................................... ...................12
W
Webrick.............................................................................................................................. ........13
WEBrick................................................................................................................................. ...8, 9
_form.rhtml........................................................................................................................21, 23, 24
.rhtml.......................................................................................................................... ...............28