[SP-pm] Catalyst: Começando...
Joenio Costa
joenio at gmail.com
Thu Feb 5 07:00:36 PST 2009
Apenas para lembrar o wiki do perl.org.br aceita marcação pod, então
caso deseje publicar o artigo lá basta dar um CTRL+C CTRL+V na seção
artigos.
Abraços,
2009/2/4 Thiago Rondon <thiago em aware.com.br>:
>
> Sim, para um aplicativo real, esta seria uma boa maneira de aplicar o
> Chained. Que tal um documento
> sobre praticas de utilização de URI/Chained com o Catalyst ?
>
> Mais um pouco, você poderia transformar um texto para adicionarmos no wiki
> também !
>
> No exemplo que coloquei no texto, é realmente pratico, fast-food, não vi
> necessidade de criar um base
> no Root.pm.
>
> -Thiago Rondon
>
> Lindolfo "Lorn" Rodrigues escreveu:
>>
>> Você pode mudar isso para Chained Action:
>>
>> sub index :Path :Args(0) {
>> my ($self, $c) = @_;
>>
>> $c->res->redirect(
>> $c->uri_for(
>> $c->controller->action_for('list')
>> )
>>
>> );
>> }
>>
>> sub root : Chained('base') Args(0) {
>> my ($self, $c) = @_;
>> $c->res->redirect(
>> $c->uri_for(
>> $c->controller->action_for('list')
>> )
>> );
>> }
>>
>> # Essa parte do Controller Telefone, pode ficar assim:
>> - sub base :Chained('/') CaptureArgs(0) PathPart('telefone') {
>> + sub base :Chained('/base') CaptureArgs(0) PathPart('telefone') {
>>
>>
>> Para isso preciso disso no Controller Root.pm, eu acho que assim fica mais
>> "extensivel" sua agenda.
>>
>> Root.pm
>>
>> sub base : Chained('/') CaptureArgs(0) PathPart('') {}
>>
>> sub root : Chained('base') PathPart('') Args(0){
>>
>> my ($self, $c) = @_;
>> # Quando o usuario acessar localhost:3000/ vai ser redirecionado
>> para localhost:3000/telefone, pois é # a unica parte da agenda que
>> está pronta $c->res->redirect('/telefone');
>>
>> }
>>
>> Usando Chained Action a partir do Root.pm, você consegue controlar coisas
>> desse tipo:
>>
>> sub favicon : Chained('base') PathPart('favicon.ico') Args(0) {
>> my ($self, $c) = @_;
>> # coloque aqui o codigo para servir o favicon.ico de sua
>> prefrencia
>>
>> }
>>
>> sub error_404 : Chained('base') PathPart('') Args {
>> my ($self, $c) = @_;
>> $c->response->body( 'Page not found' );
>> $c->response->status(404);
>>
>>
>> }
>>
>> O CODIGO ACIMA NÃO FOI TESTADO :)
>>
>> 2009/2/4 Thiago Rondon <thiago em aware.com.br <mailto:thiago em aware.com.br>>
>>
>>
>> Espero continuações... oops, contribuições. :)
>>
>> -Thiago Rondon
>>
>> Blabos de Blebe escreveu:
>>
>> Muito bom. Gostei das explicações nos comentários. Vai ter
>> continuação? Espero que sim.
>>
>> Parabéns
>>
>> 2009/2/3 Daniel de Oliveira Mantovani
>> <daniel.oliveira.mantovani em gmail.com
>> <mailto:daniel.oliveira.mantovani em gmail.com>>:
>>
>> Valeu Thiago!
>> 2009/2/3 Nelson Ferraz <nferraz em gmail.com
>> <mailto:nferraz em gmail.com>>
>>
>> Parece muito bom, Thiago! Obrigado pela contribuição!
>>
>> Por que não formata o documento em pod, e copia para o
>> wiki? Aqui vai um
>> começo:
>>
>> =head1 NOME
>>
>> Catalyst - O Elegante framework de programação Web em
>> Perl.
>>
>> =head1 DESCRICAO
>>
>> Catalyst é um framework elegante de programação MVC
>> para Web em Perl
>> (ambos gratuitos e de código aberto). Este tutorial
>> simples e talvez muito objetivo tem como missão
>> facilitar o
>> entedimento para os novos nesta ferramenta de forma
>> pratica, não
>> se prendendo muito a conceitos, motivos e paradigmas.
>>
>> =head1 INICIO
>>
>> =head2 Instalação do Catalyst.
>>
>> O Perl tem o CPAN, no qual é um repositorio farto de
>> modulos e
>> documentação, você pode utilizar o site
>> http://search.cpan.org/ para
>> navegar por ele, e toda distribuição do Perl vem com
>> um aplicativo
>> chamado "cpan" para você poder instalar os modulos
>> atravez da
>> linha de comando.
>>
>> $ export PERL_MM_USE_DEFAULT=1
>> $ cpan -i Task::Catalyst
>>
>> O Catalyst tem algumas dependencias, e por isto, além
>> do pacote do
>> Catalyst o "cpan" irá automaticamente instalar as suas
>> dependencias,
>> perceba que estamos utilizando o pacote
>> Task::Catalyst, no qual contém
>> uma serie de pacotes recomendadas para a utilização do
>> Catalyst.
>>
>> DICA: Sete a variavel de ambiente
>> PERL_MM_USE_DEFAULT=1 para o cpan
>> não lhe questionar sobre as dependencias e demais
>> perguntas, e
>> instalar elas sem lhe perguntar.
>>
>> Pronto, a instalação esta concluida, agora estamos com
>> o sistema apto
>> para programar utilizando o framework de programação
>> Catalyst.
>>
>> =head2 Iniciando o meu primeiro projeto.
>>
>> No pacote do Catalyst, vem acompanhando um aplicativo
>> que facilita a
>> nossa vida para montar o ambiente básico para
>> iniciarmos o nosso
>> projeto, chamado de "catalyst.pl", para isto basta
>> executar ele com o
>> primeiro argumento sendo o nome do projeto.
>>
>> $ catalyst.pl webapp
>> created "webapp"
>> created "webapp/script"
>> created "webapp/lib"
>> created "webapp/root"
>> created "webapp/root/static"
>> created "webapp/root/static/images"
>> created "webapp/t"
>> created "webapp/lib/webapp"
>> created "webapp/lib/webapp/Model"
>> created "webapp/lib/webapp/View"
>> created "webapp/lib/webapp/Controller"
>> created "webapp/webapp.conf"
>> created "webapp/lib/webapp.pm <http://webapp.pm>"
>> created "webapp/lib/webapp/Controller/Root.pm"
>> created "webapp/README"
>> created "webapp/Changes"
>> created "webapp/t/01app.t"
>> created "webapp/t/02pod.t"
>> created "webapp/t/03podcoverage.t"
>> created "webapp/root/static/images/catalyst_logo.png"
>> created "webapp/root/static/images/btn_120x50_built.png"
>> created
>> "webapp/root/static/images/btn_120x50_built_shadow.png"
>> created
>> "webapp/root/static/images/btn_120x50_powered.png"
>> created
>> "webapp/root/static/images/btn_120x50_powered_shadow.png"
>> created "webapp/root/static/images/btn_88x31_built.png"
>> created
>> "webapp/root/static/images/btn_88x31_built_shadow.png"
>> created
>> "webapp/root/static/images/btn_88x31_powered.png"
>> created
>> "webapp/root/static/images/btn_88x31_powered_shadow.png"
>> created "webapp/root/favicon.ico"
>> created "webapp/Makefile.PL"
>> created "webapp/script/webapp_cgi.pl"
>> created "webapp/script/webapp_fastcgi.pl"
>> created "webapp/script/webapp_server.pl"
>> created "webapp/script/webapp_test.pl"
>> created "webapp/script/webapp_create.pl"
>>
>> Pronto, agora estamos com nosso ambiente básico para
>> começar a
>> programar, vou explicar de forma rápida a estrutura
>> de diretorios criada para você entender melhor:
>>
>> $ ls webapp/
>> Changes lib/ Makefile.PL README root/ script/
>> t/ webapp.conf
>>
>> Veja, que temos quatro diretorios, que são eles:
>>
>> =over
>>
>> =item lib/
>>
>> Contém a parte da programação lógica, onde iremos
>> escrever nossos
>> modulos e códigos em Perl.
>>
>> =item root/
>>
>> Neste diretorio iremos colocar arquivos estáticos,
>> como templates,
>> javascript e css.
>>
>> =item script/
>>
>> Scripts para inicializar a nossa aplicação.
>>
>> =item t/
>>
>> Testes para nossa aplicação.
>>
>> =back
>>
>> Vamos falar um pouco do diretorio "script/", nele você
>> tem um script
>> que usaremos muito neste tutorial, webapp_server.pl, no
>> qual irá iniciar um servidor http na porta 3000 por
>> padrão, para
>> visualizarmos o nosso aplicativo:
>>
>> $ script/webapp_server.pl
>>
>> Depois de executar ele, você já pode acessar o
>> aplicativo pelo seu
>> browser favorito, pelo endereço:
>>
>> L<http://localhost:3000>
>>
>> =head2 Entendendo um MVC (Model-View-Controller)
>>
>> MVC é um padrão de arquitetura de software, uma das
>> maiores
>> dificuldades do entendimento de um framework de
>> programação
>> web como o Catalyst para os iniciantes é justamente
>> este conceito.
>>
>> Basicamente o significado deles é:
>>
>> =over
>>
>> =item Model:
>>
>> São as informações utilizadas pelo aplicativo, no qual
>> este modelo
>> pode ser um banco de dados, um arquivo texto e etc.
>>
>> =item Controller:
>>
>> Lógica do nosso aplicativo, onde podemos por exemplo
>> alterar as
>> informações (Model), filtrar e etc.
>>
>> =item View:
>>
>> Renderizar o Model e o Controller para o usuário, ou
>> seja como o
>> aplicativo será apresentado.
>>
>> =back
>>
>> Esta é uma forma rápida de apresentar os três
>> componentes do MVC,
>> sabendo disto, vamos começar a produzir nosso aplicativo.
>>
>> =head2 Nosso projeto: Agenda telefonica
>>
>> Vamos começar a estudar o nosso aplicativo, ele irá
>> gerenciar uma
>> lista de telefones, no qual iremos efetuar um simples
>> "CRUD"
>> (Create-Remove-Update-Destroy), no qual iremos
>> permitir efetuar a
>> criação, remover, atualizar e apagar dados de nossa lista.
>>
>> Vamos projetar nossa agenda telefonica, utilizando o
>> MVC da seguinte da
>> forma:
>>
>> =over
>>
>> =item Model:
>>
>> Iremos utilizar o MySQL, porém para efetuar a
>> conectividade com o
>> banco de dados, iremos utilizar um driver do DBI, no
>> qual o MySQL e tantos outros ele suporta, e é
>> interessante além de
>> utilizar o driver de comunicação com o banco de dados,
>> algum
>> ORM
>> (http://en.wikipedia.org/wiki/Object-relational_mapping)
>> para
>> facilitar a interface com o DB, existem alguns
>> disponiveis como
>> por exemplo o Class::DBI ou DBIx::Class para evitar
>> que você escreva
>> SQL no teu código, evitar alguns bugs e trazer
>> agilidade no
>> desenvolvimento, no nosso caso iremos utilizar o
>> DBIx::Class.
>>
>> =item View:
>>
>> Iremos utilizar o HTML como formato de apresentação
>> para nossos
>> usuarios, portanto teremos que carregar o modulo que
>> cuidará de manipular os dados que iremos jogar em
>> nosso arquivo de
>> template, existem alguns modulos disponiveis como o
>> HTML::Manson ou Template::Toolkit), iremos utilizar
>> neste caso o
>> Template::Toolkit.
>>
>> =item Controller:
>>
>> Aqui é o lugar onde iremos programar, captar os dados
>> no Model e
>> "espirrar" para a View, entre outras.
>>
>> =back
>>
>> =head2 Começando pelo Model.
>>
>> Bem, já dissemos que iremos utilizar o MySQL como
>> banco de dados, e
>> também já explicamos por que iremos utilizar o
>> DBIx::Class.
>>
>> Precisamos instalar ele:
>>
>> $ cpan -i DBIx::Class
>>
>> Certo, agora temos tudo o que precisamos para
>> trabalhar com o nosso
>> Model. Neste tutorial eu vou utilizar o exemplo de criar a
>> tabela direto pelo client do MySQL, e depois ler elas
>> pelo DBIx::Class.
>>
>> mysql> create database webapp
>> mysql> grant all on webapp.* to webapp em localhost
>> identified by
>> 'webapp';
>> mysql> flush privileges;
>> mysql> create table telefones (
>> id int not null auto_increment,
>> nome varchar(255),
>> telefone varchar(255),
>> primary key(id));
>>
>> Criado a nosso database e nossa tabela, agora vamos
>> para "importar"
>> nosso banco de dados para o DBIx::Class, eu não irei me
>> aprofundar no DBIx::Class neste tutorial, pois não é o
>> objetivo dele.
>>
>> Para importarmos este database para os schemas que o
>> DBIx::Class
>> utiliza, iremos executar um dos scripts facilitadores
>> do diretorio
>> scripts/ que se chama "webapp_create.pl" no qual
>> facilita algumas
>> tarefas como esta, a sintaxe utilizada é
>> auto-explicativa, é
>> basicamente:
>>
>> # script/webapp_create.pl model DB DBIC::Schema
>> webapp::Schema
>> create=static dbi:mysql:dbname=webapp webapp webapp
>> exists "/home/thiago/webapp/script/../lib/webapp/Model"
>> exists "/home/thiago/webapp/script/../t"
>> Dumping manual schema for webapp::Schema to directory
>> /home/thiago/webapp/script/../lib ...
>> Schema dump completed.
>> created
>> "/home/thiago/webapp/script/../lib/webapp/Model/DB.pm"
>> created "/home/thiago/webapp/script/../t/model_DB.t"
>>
>> Vejamos os argumentos deste script:
>>
>> =over
>>
>> =item model
>>
>> Estamos criando um model para o script webapp_create.pl
>>
>> =item DB
>>
>> O nome criado para este modem será DB (vide
>> lib/webapp/Model/DB.pm)
>>
>> =item DBIC::Schema
>>
>> Nome do helper para criar o Schema para o DBIx::Class
>>
>> =item create=static
>>
>> Significa que o schema será estatico, ou seja, a toda
>> alteração nas
>> tabelas, você precisa atualizar nos arquivos criados
>> para o schema.
>>
>> =item dbname
>>
>> Nome do database no MySQL.
>>
>> =item webapp
>>
>> Usuario para se conectar no banco.
>>
>> =item webapp
>>
>> Senha para se conectar no banco.
>>
>> =back
>>
>> Agora, de uma lida nos arquivos criados em
>> lib/webapp/Schema/* e
>> lib/webapp/Model/*
>>
>> Pronto, já temos o nosso Model pronto para o nosso
>> aplicativo.
>>
>> =head2 View - Template Toolkit.
>>
>> Iremos utilizar neste exemplo a Template::Toolkit,
>> iremos utilizar um
>> helper da mesma maneira que
>> usamos para o banco de dados:
>>
>> # script/webapp_create.pl view TT TT
>> exists "/home/thiago/webapp/script/../lib/webapp/View"
>> exists "/home/thiago/webapp/script/../t"
>> created
>> "/home/thiago/webapp/script/../lib/webapp/View/TT.pm"
>> created "/home/thiago/webapp/script/../t/view_TT.t"
>>
>> Pronto, o helper já criou os arquivos necessarios para
>> utilizarmos a
>> Template Toolkit em nosso projeto.
>>
>> =head2 Controller - Agora sim, programando!
>>
>> Estamos na melhor parte, é onde tudo acontece, iremos
>> criar 4 actions,
>> para criar, remover, listar e editar.
>>
>> Eu irei colocar o código que utilizei, e com ele
>> comentado explicando
>> passo-a-passo do que estou
>> fazendo nele. Não irei utilizar nenhum helper ou
>> facilitador nesta
>> parte, pois acredito que a melhor maneira
>> de se desenvolver o Controller é na 'unha'.
>>
>> Iremos criar um arquivo
>> lib/webapp/Controller/telefone.pm
>> <http://telefone.pm>, no qual ele
>> irá representar na URI "/telefone".
>>
>> Caso queira baixar todos os arquivos, clique aqui.
>>
>> package webapp::Controller::telefone;
>>
>> use strict;
>> use warnings;
>> use parent 'Catalyst::Controller';
>>
>> # Nos estamos utilizando um mecanismo muito
>> interessante neste
>> # exemplo, que recomendo a utilizacao, o Chained.
>> # Não iremos entrar discutir sobre ele, porem ele ajuda
>> # manusearmos os argumentos passados pela URI.
>> # Por tanto, vamos criar uma rotina "base" no qual
>> todas as
>> # requisições passaram por ela (Chained()).
>> #
>>
>> search.cpan.org/dist/Catalyst-Runtime/lib/Catalyst/DispatchType/Chained.pm
>>
>> <http://search.cpan.org/dist/Catalyst-Runtime/lib/Catalyst/DispatchType/Chained.pm>
>>
>> sub base :Chained('/') CaptureArgs(0)
>> PathPart('telefone') {
>> my ($self, $c) = @_;
>>
>> # stash é o local onde iremos colocar
>> informações para compartilhar
>> # com outras partes do nosso aplicativo, para
>> cada requisição o
>> stash
>> # tem uma vida.
>> # No caso, abaixo estamos atribuindo ao
>> "collection" no stash o
>> nosso
>> # model "DB::telefones", que corresponde a nossa
>> tabela telefones
>> que
>> # criamos no MySQL.
>>
>> $c->stash->{collection} =
>> $c->model('DB::telefones');
>> }
>>
>>
>> # Veja, aqui estamos criando uma URI list/, no qual
>> ela esta
>> vinculada a nossa
>> # função base, criada anteriormente (Chained('base')),
>> #
>> # Ou seja, para chamar a URL,
>> http://localhost:3000/telefone/list,
>> primeiramente
>> # ele vai passar na base e depois na list. (* E por
>> último, neste
>> nosso exemplo
>> # na action end do Root.pm, no qual nem iremos
>> comentar neste
>> tutorial).
>> #
>> # Veja que estamos dizendo Args(0), isto significa
>> que não preciso de
>> nenhum
>> # elemento extra na minha URI, sempre que esta
>> action for requisitada,
>> será
>> # /telefone/list
>>
>> sub list :Chained('base') Args(0) {
>> my ($self, $c) = @_;
>>
>> # "req" ou "request" são as informações que
>> estão no request da
>> # requisição, e no qual temos o metodo param que
>> retorna o valor
>> # do atributo que gostariamos.
>> my $key = $c->req->param('key') || "";
>>
>> # Aham, aqui estamos utilizando o DBIx:Class,
>> repare que não iremos
>> # escrever nenhum um código de SQL aqui, veja
>> abaixo que iremos
>> # utilizar uma função search_like(), no qual
>> iremos procurar pelo
>> # elemento $key nos dois campos (nome,
>> telefone), estou usando o
>> # $key, caso o usuario queira procurar por
>> alguma palavra chave.
>> my $items = $c->stash->{collection}->search_like({
>> nome => "\%$key\%",
>> telefone => "\%$key\%"
>> });
>>
>> # Aqui estou colocando no stash o key, para
>> mostrar na View por
>> qual
>> # palavra o usuario esta procurando, e o items
>> encontrados.
>> $c->stash->{key} = $key;
>> $c->stash->{items} = $items;
>> }
>>
>> # Esta função é para remover uma determinada coluna
>> do nosso banco de
>> dados,
>> # repare que aqui estamos utilizando Args(1), ou
>> seja estou esperando
>> um
>> # elemento na minha URI, além de /telefones/destroy,
>> eu só irei acessar
>> esta
>> # URI, se acessar por /telefones/destroy/N, onde N é
>> o ID do nosso
>> item.
>> # Veja que no código da função, estaremos novamente
>> usando uma
>> função do DBIx:Class,
>> # primeiramente irei procurar por este elemento com
>> find() e então
>> irei remover
>> # ele.
>>
>> sub destroy :Chained('base') :Args(1) {
>> my ($self, $c, $id) = @_;
>> my $row = $c->stash->{collection}->find({ id =>
>> $id });
>> $row->delete if $row;
>> }
>>
>> # Aqui, iremos editar o nosso item, onde também
>> esperamos um
>> argumento, que é o id
>> # do item, e iremos procurar ele.
>> # Repare que aqui, eu faço uma verificação, onde
>> quero saber se o
>> metodo utilizado
>> # para chamar esta requisição é POST, caso não seja
>> eu vou
>> "encarrar" (detach())
>> # esta função e vou passar para a próxima função da
>> cadeia. (end()
>> no Root.pm, neste
>> # caso).
>> # Resumindo, se o usuário não apertou "ALTERAR" no
>> html (no qual o
>> form esta enviando
>> # os dados via POST), eu irei mostrar a página com
>> os dados do
>> objeto N ($id).
>>
>> sub edit :Chained('base') :Args(1) {
>> my ($self, $c, $id) = @_;
>>
>> my $row = $c->stash->{row} =
>> $c->stash->{collection}->find({id =>
>> $id});
>> $c->stash->{template} = "telefone/create.tt
>> <http://create.tt>";
>>
>> $c->detach() unless $c->req->method eq 'POST';
>>
>> # Caso o usuario, tenha apertado o submit do
>> nosso form, para
>> alterar os dados,
>> # eu vou colocar eles em $parameters = {}, e vou
>> mandar o $row
>> (que é o objeto
>> # do find()) para o metodo update().
>>
>> my $parameters;
>> $parameters->{nome} = $c->req->param('nome');
>> $parameters->{telefone} =
>> $c->req->param('telefone');
>>
>> $row->update($parameters);
>>
>> # Agora, nao vou mandar ele para a mesma tela,
>> vou enviar para
>> uma template diferente.
>> $c->stash->{template} = "telefone/edit_ok.tt
>> <http://edit_ok.tt>";
>> }
>>
>> # Esta rotina é bem parecida com a do edit, com a
>> unica diferença,
>> que ao invez de atualizar
>> # eu vou inserir.
>>
>> sub create :Chained('base') :Args(0) {
>> my ($self, $c) = @_;
>> $c->detach() unless $c->req->method eq 'POST';
>>
>> my $parameters;
>> $parameters->{nome} = $c->req->param('nome');
>> $parameters->{telefone} =
>> $c->req->param('telefone');
>>
>> my $row = $c->stash->{collection}->new($parameters);
>> $row->insert;
>>
>> $c->stash->{template} = "telefone/create_ok.tt
>> <http://create_ok.tt>";
>> }
>>
>> # Caso, eu não especifique nada na URI de telefones,
>> ou seja, acessar
>> # http://localhost:3000/telefone eu irei
>> redirecionar para a action
>> list,
>> # que já comentamos sobre ela.
>> sub index :Path :Args(0) {
>> my ($self, $c) = @_;
>>
>> $c->res->redirect(
>> $c->uri_for(
>> $c->controller->action_for('list')
>> )
>> );
>> }
>>
>> 1;
>>
>>
>> Código pronto, porem precisamos das templates.
>>
>> Agora, crie estes arquivos no diretorio root/telefone:
>>
>> Este arquivo, é para a action "create", no qual é o
>> nosso formulario
>> para criar um item na nossa agenda.
>>
>> =head3 create.tt <http://create.tt>
>>
>> <h1>Criar</h1>
>>
>> <form method="POST">
>>
>> <table><tr><td>
>> Nome:
>> </td><td> <input type="text" name="nome" value="[%
>> row.nome %]"
>> size=20>
>> </td></tr><tr><td>
>> Telefone:
>> </td><td> <input type="text" name="telefone" value="[%
>> row.telefone %]" size=20>
>> </td></tr><tr><td colspan="2">
>> <center><input type="submit"></center>
>> </td></tr></table>
>>
>> </form>
>>
>> Este arquivo é a mensagem depois da criação do item.
>>
>> =head3 create_ok.tt <http://create_ok.tt>
>>
>> <h1>Telefone inserido com sucesso!</h1>
>>
>> Mensagem para o item apagado.
>> =head3 destroy.tt <http://destroy.tt>
>>
>> <h1>Telefone apagado</h1>
>>
>> Mensagem para item editado.
>>
>> =head3 edit_ok.tt <http://edit_ok.tt>
>>
>> <h1>Editado com sucesso.</h1>
>>
>> Listar itens da nossa agenda, e também dar ao usuário
>> a opção de procurar
>> neles.
>>
>> =head3 list.tt <http://list.tt>
>>
>> <h1>Listar</h1>
>>
>> <form>
>> Procurar por:
>> <input type="text" name="key" size="15">
>> <input type="submit" value=" Procurar " >
>> </form>
>>
>> [% IF key %]
>> <br/>
>> <b>Procurando por [% key %]</b>
>> <br/>
>> [% END %]
>>
>> <table><tr><td>
>> <b>Nome</b>
>> </td><td>
>> <b>Telefone</td>
>> </td><td>
>> <b>Ação
>> </td></tr>
>>
>> [% WHILE (item = items.next) %]
>> <tr><td>
>> [% item.nome %]
>> </td><td>
>> [% item.telefone %]
>> </td><td>
>> <a href="[% c.uri_for('edit', item.id
>> <http://item.id>) %]">Editar</a>
>> -
>> <a href="[% c.uri_for('destroy', item.id
>> <http://item.id>) %]">Remover</a>
>> </td></tr>
>> [% END %]
>>
>> </table>
>>
>> =head1 AUTOR
>>
>> Thiago Rondon <thiago em aware.com.br
>> <mailto:thiago em aware.com.br>>
>>
>> =head1 COLABORADORES
>> _______________________________________________
>> SaoPaulo-pm mailing list
>> SaoPaulo-pm em pm.org <mailto:SaoPaulo-pm em pm.org>
>> http://mail.pm.org/mailman/listinfo/saopaulo-pm
>>
>>
>> --
>> http://mantovanihouse.blogspot.com/
>>
>> -------(\_------------_/)-----------
>> -------)--(----------)--(-----------
>> ------(----(---------)----)----------
>> -------)----(-------)----(-----------
>> -------(----(-------)----)-----------
>> --------\_-(\\.---.//)-_/------------
>> ----------\)' -8--8- '(/--------------
>> -----------/------------\---------------
>> ----------(--)--------(--)--------------
>> ------------(_c__c_)----------------
>> ----------------------------------------
>>
>> _______________________________________________
>> SaoPaulo-pm mailing list
>> SaoPaulo-pm em pm.org <mailto:SaoPaulo-pm em pm.org>
>> http://mail.pm.org/mailman/listinfo/saopaulo-pm
>>
>>
>> _______________________________________________
>> SaoPaulo-pm mailing list
>> SaoPaulo-pm em pm.org <mailto:SaoPaulo-pm em pm.org>
>> http://mail.pm.org/mailman/listinfo/saopaulo-pm
>>
>>
>>
>>
>> _______________________________________________
>> SaoPaulo-pm mailing list
>> SaoPaulo-pm em pm.org <mailto:SaoPaulo-pm em pm.org>
>> http://mail.pm.org/mailman/listinfo/saopaulo-pm
>>
>>
>>
>>
>> --
>> --Lindolfo "Lorn" Rodrigues
>> www.slackwarezine.com.br <http://www.slackwarezine.com.br>
>> http://lornlab.org
>> http://sao-paulo.pm.org
>> use Catalyst;
>> ------------------------------------------------------------------------
>>
>> _______________________________________________
>> SaoPaulo-pm mailing list
>> SaoPaulo-pm em pm.org
>> http://mail.pm.org/mailman/listinfo/saopaulo-pm
>
> _______________________________________________
> SaoPaulo-pm mailing list
> SaoPaulo-pm em pm.org
> http://mail.pm.org/mailman/listinfo/saopaulo-pm
>
--
Joenio Costa
- Salvador Perl Mongers - Grupo de Usuário Perl de Salvador
- Colibre - Cooperativa de Tecnologias Livres
- PSL-BA - Projeto Software Livre Bahia
More information about the SaoPaulo-pm
mailing list