Blog do Urubatan
msgbartop
Desenvolvedor, Palestrante, Escritor, Nerd Assumido e Pai do Marcus :D
msgbarbottom

19 Aug 10 Slides, videos e código da minha palestra no FISL 2010

Bom, este post esta bastante atrasado, mas me lembraram hoje pelo twitter que eu não publiquei por aqui o material da minha palestra do FISL deste ano.
O título da palestra era:
Transformando os pepinos do cliente no código de testes da sua aplicação com OSS
Quase a mesma apresentação que fiz no Agile Brazil 2010, mesmo assunto, exemplos parecidos, mas melhorei um pouco a forma de apresentar, e em vez de tentar escrever código na hora eu utilizei videos para os exemplos :D
Quem quiser dar uma olhada nos slides, coloquei eles no SlideShare.

E os videos coloquei no Vimeo, o com exemplo Java aqui, e o com exemplo Rails aqui.

FISL 2010 – Rails Cucumber BDD Sample creation from Rodrigo Urubatan on Vimeo.

Rails + BDD sample created as a sample to my presentation at Forum Internacional de Software Livre 2010.

FISL 2010 – Java Cucumber BDD Sample creation from Rodrigo Urubatan on Vimeo.

Java + BDD sample created as a sample to my presentation at Forum Internacional de Software Livre 2010.

Bom, espero que seja útil, se tiverem dúvidas é só deixar um comentário …

PS.: não tenho o código comigo agora, assim que possível subo pro github e coloco o link para o código aqui :D

Tags: , , , , , ,

25 Jun 10 Material da minha palestra do Agile Brazil 2010

Para quem não sabe, eu apresentei hoje mais cedo, no Agile Brazil 2010, a palestra “Transformando os pepinos do cliente no código de testes da aplicação com Cucumber”.
O conteúdo da palestra ficou muito bom na minha opinião, mas a forma como eu apresentei, eu, durante a palestra comecei a achar chato …
Bom, espero que quem assistiu a paletra tenha aproveitado, e vou melhorar a forma de apresentar isto para o FISL, mas como eu sempre faço, o material da paletra esta sendo disponibilizado por aqui!
Os slides eu coloquei no slideshare:

Os videos da execução do cucumber estão aqui para quem quiser pegar:
Aplicação ASP.NET MVC 2 e Aplicação Java

E para quem quiser pegar o código dos exemplos, os links estão aqui:
Aplicação ASP.NET, Aplicação Java e Aplicação Ruby on Rails

A idéia que eu queria passar era que o cucumber pode ser utilizado para testar diversos tipos de aplicação, e acho que isto ficou claro :D

Espero que este material seja útil, e quem quiser comentar, sugerir, criticar, xingar, …
Pode usar o campo de comentários aqui do blog para isto.

Tags: , , , , , , , ,

18 Jun 10 Brincadeirinha com Kanban Board Online

Depois de ter algumas idéias mirabolantes, que acabei deixando de lado, resolvi brincar um pouquinho com javascript e fazer um mini protótipo de  um “Kanban Board”, sem usar nenhuma imagem, onde as tarefas fossem parecidas com aqueles alertas tipo “buble”, bom, o protótipo ficou beem feinho, mas deu pra tirar uma febre do meu CSS.
Para a parte Javascript usei o Jquery, a idéia básica é o seguinte:
Tenho 3 divs que representam 3 estados que qualquer tarefa pode estar: Backlog, Work In Progress e Done
Estas tem que estar uma ao lado da outra.
Dentro destas, quero ter blocos representando cada tarefa, e preciso conseguir mover estas tarefas entre os estados, para isto o JQuery facilita bastante a nossa vida, adicionei o jquery ui, e tudo o que eu precisei fazer foi o seguinte:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js"></script>
<script>
$(document).ready(function(){
    $("#backlog,#wip,#done").sortable({
        connectWith: '.connectedSortable',
        receive: function(event, ui) { 
            /*alert($(ui.item).attr('data-id') + ' -> ' + $(this).attr('id'));*/
            //Do something with the recently dropped item here
        }
    }).disableSelection();
 
});
</script>

Onde #backlog, #wip e #done são os IDs das divs principais …
Para posicionar estas divs coloquei elas dentro de um container de ID bublecontainers, e usei o seguinte CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
            #bublecontainers {
                background-color: gray;
                width: 600px;
                margin: 0 0 0 0;
                padding: 0 0 0 0;
            }
            #backlog, #wip, #done {
                width: 190px;
                margin-left: 5px;
                height: 340px;
                display: inline-block;
                float:left;
                border: solid 1px;
            }

E agora a parte importante, para fazer as tarefas parecerem bolhas, usei a seguinte estrutura HTML:

1
2
3
4
5
6
<div class="buble">
  <div class="buble-content">
Texto da task aqui
  </div>
  <div class="buble-arrow"></div>
</div>

E me aproveitei de como as bordas do HTML são montadas via CSS, para transformar isto em uma bolha, só precisei do CSS abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
.buble-content {
                width: 100%;
                top: 0;
                margin: 0 0 0 0;
                padding: 8px 8px 8px 8px;
                text-align:justify;
                overflow: hidden;
                border-radius:10px;
                -moz-border-radius:10px;
                -webkit-border-radius:10px;
                background-color:#FFF;
                border-style: outset;
                text-indent: 10px;
 
 
            }
            .buble
            {
                display:inline-block;
                width:90%;
            }
 
            .buble-arrow
            {
                border-color:#FFF transparent transparent transparent;
                border-style:solid;
                border-width:17px;
                margin: -4 0 0 0;
                height:0;
                width:0;
                position: relative;
                left: 30%;
            }

As bordas arredondadas só vão aparecer em browsers mais modernos que tenham algum suporte a CSS3 rounded corners.

O experimento até que foi divertido, mas o código tem diversas falhas ainda, serviu para brincar um pouco :D

E o resultado vocês podem ver nesta coisa horrenda aqui de baixo :D (deem uma folga, fiz isto em 20 minutos ais ou menos só pra brincar e ver se a minha idéia era viável :D )

Back Log
Work In Progress
Done
Create a virtual Kanban Board

Make this VKB usable

write a blog post about it

make it using no images, just CSS

Se quiserem o código completo, segue abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
<html>
    <head>
        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
        <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js"></script>
        <style>
            #bublecontainers {
                background-color: gray;
                width: 600px;
                margin: 0 0 0 0;
                padding: 0 0 0 0;
            }
            .buble-content {
                width: 100%;
                top: 0;
                margin: 0 0 0 0;
                padding: 8px 8px 8px 8px;
                text-align:justify;
                overflow: hidden;
                border-radius:10px;
                -moz-border-radius:10px;
                -webkit-border-radius:10px;
                background-color:#FFF;
                border-style: outset;
                text-indent: 10px;
 
 
            }
            .buble
            {
                display:inline-block;
                width:90%;
            }
 
            .buble-arrow
            {
                border-color:#FFF transparent transparent transparent;
                border-style:solid;
                border-width:17px;
                margin: -4 0 0 0;
                height:0;
                width:0;
                position: relative;
                left: 30%;
            }
            #backlog, #wip, #done {
                width: 190px;
                margin-left: 5px;
                height: 340px;
                display: inline-block;
                float:left;
                border: solid 1px;
            }
            #bltitle, #wiptitle, #donetitle {
                height: 15px;
                text-align: center;
                width: 190px;
                display: inline-block;
                font-size: 20;
                font-height: bold;
            }
        </style>
        <script>
            $(document).ready(function(){
                $("#backlog,#wip,#done").sortable({
                    connectWith: '.connectedSortable',
                    receive: function(event, ui) { 
                        /*alert($(ui.item).attr('data-id') + ' -> ' + $(this).attr('id'));*/
                        //Do something with the recently dropped item here
                    }
                }).disableSelection();
 
            });
        </script>
    </head>
    <body>
        <div id="bublecontainers">
            <div id="bltitle">Back Log</div>
            <div id="wiptitle">Work In Progress</div>
            <div id="donetitle">Done</div>
            <div id="backlog" class="connectedSortable">
                <div class="buble" data-id="buble1">
                    <div class="buble-content">
                        Create a virtual Kanban Board
                    </div>
                    <div class="buble-arrow"></div>
                </div>
                <div class="buble" data-id="buble2">
                    <div class="buble-content">
                        Make this VKB usable
                    </div>
                    <div class="buble-arrow"></div>
                </div>
                <div class="buble" data-id="buble2">
                    <div class="buble-content">
                        write a blog post about it
                    </div>
                    <div class="buble-arrow"></div>
                </div>
                <div class="buble" data-id="buble2">
                    <div class="buble-content">
                        make it using no images, just CSS
                    </div>
                    <div class="buble-arrow"></div>
                </div>
            </div>
            <div id="wip" class="connectedSortable">
            </div>
            <div id="done" class="connectedSortable">
            </div>
            <div style="clear:both"></div>
        </div>
    </body>
</html>

Tags: , ,

19 May 10 Agendamento de tarefas muito fácil com com Ruby on Rails

Em um projeto pequeno, que precisava rodar em um desktop, eu precisei de um agendador de tarefas, o sistema precisava rodar em windows, linux ou mac, então utilizar o Cron para isto não era uma opção, e eu precisava de algo semelhante ao Cron, rodar algum código a cada X tempo não era o suficiente, ou pelo menos tornaria a minha vida muito mais difícil.

Eu também não queria ter que iniciar um processo separado da aplicação para cuidar disto pois eram tarefas simples, e depois de algumas pesquisas, encontrei o Rufus-Scheduler que resolve o problema que eu tinha, então resolvi escrever este post para que eu lembre dele caso precise novamente.

Sempre lembrando que aqui não serão mostrados todos do Rufus-Scheduler, apenas uma forma simples de usa-lo, se tiver dúvidas deixe um comentário que farei o melhor para esclarece-las.

Este exemplo vai começar com uma aplicação simples, um scaffold de uma entidade de nome SimpleTask com os seguintes parâmetros:

rails generate scaffold SampleTask name:string cron:string times:integer done:boolean job_id:string
rake db:migrate

A premissa para este exemplo é a seguinte:
Quando o sistema for iniciado as tarefas ainda não completas tem que ser agendadas novamente, e toda vez que eu criar uma tarefa nova esta tem que ser agendada automaticamente.

Então vamos a implementação com o Rufus-Scheduler.

  • Apagar o arquivo public/index.html
  • No arquivo routes.rb adicionar a linha ‘root :to => “sample_tasks#index”‘
  • No Gemfile adicionar a linha ‘gem “rufus-scheduler”‘
  • Criar um arquivo config/initializers/scheduler_initialization.rb com a seguinte linha: MySampleScheduler.update_schedules

No model criado adicionar o seguinte código:

class SampleTask < ActiveRecord::Base
	after_create :schedule_me
	after_destroy :unschedule_me
 
	def schedule_me
		MySampleScheduler.schedule read_attribute(:id)
	end
	def unschedule_me
		MySampleScheduler.unschedule read_attribute(:job_id)
	end
end

Criar o arquivo lib/my_sample_scheduler.rb com o seguinte conteúdo:

class MySampleScheduler
	@@scheduler = Rufus::Scheduler::PlainScheduler.start_new
	def self.update_schedules
		@@scheduler.in '1m' do #delay initialization because of problems acessing rails models during rails initialization
			jobs = @@scheduler.cron_jobs
			tasks = SampleTask.all
			tasks.each do |task|
				job = jobs[task.job_id] if task.job_id
				job.unschedule if job && (task.done || job.cron_line!=task.cron)
				if !task.done
					schedule(task)
				end
			end
		end
	end
	def self.unschedule(job_id)
		@@scheduler.unschedule(job_id)
	end
	def self.schedule(task_id)
		task = SampleTask.find task_id
		job = @@scheduler.cron task.cron do |j|
			task = SampleTask.find task_id
			if task
				puts "#{task.name} executing at #{Time.now} ---- #{task.times}"
				task.times = task.times - 1
				task.done = task.times==0
				task.save
				unschedule task.job_id if task.done
			else
				puts "Task deleted #{task_id}"
				unschedule j.job_id
			end
		end
		task.job_id = job.job_id
		task.save
	end
end

Rodar a aplicação e já deve ser possível cadastrar tarefas.
Quando a aplicação for finalizada e novamente inicializada, as tarefas pendentes terão um delay de 1 minuto para re-iniciarem, isto foi necessário porque se acessarmos os models diretamente durante a inicialização do rails, muitas coisas ruins podem acontecer.

Não se esqueça que para este exemplo funcionar você precisa ter o Rufus-Scheduler instalado. Se você seguiu todos os passos, um “bundle install” do diretório do projeto deve resolver o problema.

Se quiser o código da aplicação que eu escrevi, ele esta disponível neste endereço:http://github.com/urubatan/rails_schedule_samples

Se você já usa o GIT pode baixar o código com o comando: git clone git://github.com/urubatan/rails_schedule_samples.git

Tags: , , , ,

18 May 10 HTTP e URL – Dois ilustres pouco conhecidos

Este post foi extraido de uma apostila de um curso que ministrei em 2007, eu queria passar este texto para um colega novo do trabalho e não tinha o texto disponível, então resolvi publicar isto aqui no blog que vai ser útil para mim e pode ajudar mais alguem também :D


Para que seja possível acessar qualquer página na internet, alem de outros recursos, como FTP, SSH e diversos outros serviços, foi definido um padrão de endereçamento chamado URL (Uniform Resource Locator), que possui o seguinte formato:

PROTOCOLO://[USUARIO[:SENHA]@]ENDERECO_SERVIDOR/CAMINHO_NO_SERVIDOR?parametro=valor&outro_parametro=outro_valor#ancoraNaPagina

Por exemplo:

http://www.google.com/search?q=ruby+on+rails

onde:

  • http é o protocolo
  • www.google.com é o endereço do host
  • /search é o caminho dentro do servidor
  • q=ruby+on+rails é o parâmetro de nome “q” com o valor “ruby on rails”

Como uma URL não pode conter espaços os espaços são substituídos pelo sinal “+”, isto se chama codificar o parâmetro.

Como uma URL não aceita nenhum caractere com código ASCII maior que 127, qualquer caractere nesta categoria precisa ser codificado. Esta codificação consiste em substituir os caracteres por um “%” mais 2 dígitos numéricos representando o código ASCII em hexadecimal do caractere desejado, como na tabela a baixo:

Caractere Código Hexadecimal
$ 24
& 26
+ 2B
, 2C
/ 2F
: 3A
; 3B
= 3D
? 3F
@ 40
Espaço 20
22
< 3C
> 3E
# 23
% 25
{ 7B
} 7D
| 7C
\ 5C
^ 5E
~ 7E
[ 5B
] 5D
` 60

Alguns destes caracteres são aceitáveis em uma URL, mas em algumas situações precisam ser codificados, como por exemplo o # que pode ser utilizado no final de uma URL para indicar ao browser qual parte da página mostrar, quando não for utilizado para este fim, deve ser codificado para evitar confusões.

Cabeçalhos HTTP

Cabeçalhos HTTP são uma forma de passar parâmetros extras, além da URL do browser para o servidor. Normalmente, estes parâmetros são utilizados para informar ao servidor as capacidades do browser e preferências do usuário, mas podem também ser utilizados para trocar dados adicionais além dos informados pelo usuário (que podem ser passados pela URL).

Os cabeçalhos HTTP são enviados com o seguinte formato, logo depois da URL e antes da primeira linha em branco na requisição:

nome: valor

O valor dos cabeçalhos deve ser codificados utilizando a mesma tabela de conversão das URLs.

Exemplo de Requisião HTTP

O usuário digita no browser a URL: http://www.google.com/search?q=ruby+on+rails

O browser abre uma conexão TCP/IP na porta 80 (padrão) do servidor www.google.com e envia o seguinte texto:

GET http://www.google.com/search?q=ruby+rails HTTP/1.0
User-Agent: Mozilla/4.73 [en] (X11; U; Linux 2.0.35  i686)
<Linha em branco no final>

O Servidor Responde:

HTTP/1.0 200 OK
Cache-Control: private
Content-Type: text/html; charset=ISO-8859-1
Set-Cookie: PREF=ID=059bd3c506c56f82:TM=1189888548:LM=1189888548:S=hV0hCZi26b1SuRY7; expires=Mon, 14-Sep-2009 20:35:48 GMT; path=/; domain=.google.com
Server: gws
Date: Sat, 15 Sep 2007 20:35:48 GMT
Connection: Close

<html><head>
<Resto do HTML da página>

Explicando o que foi enviado:

  • GET http://www.google.com/search?q=ruby+rails HTTP/1.0
    • GET é o método sendo utilizado
    • http://www.google.com/search?q=ruby+rails é a URL solicitada no servidor
    • HTTP/1.0 é a versão do protocolo sendo utilizada
  • User-Agent: Mozilla/4.73 [en] (X11; U; Linux 2.0.35  i686)
    • User-Agent é um cabeçalho enviado que informa ao servidor qual browser esta fazendo a requisição
  • Linha em branco marcando o final da transmissão.

Explicando a resposta:

  • HTTP/1.0 200 OK
    • HTTP/1.0 é a versão do protocolo
    • 200 é o código da resposta que informa que o processamento esta OK
    • OK é um texto livre escrevendo o status, cada servidor pode responder com uma descrição diferente relativa ao código
  • Nas linhas seguintes, antes do espaço, tem diversos HEADERS que podem ser utilizados pelo browser
  • Após a linha em branco, há o conteúdo da URL solicitada

Métodos HTTP

GET

O método GET é utilizado para buscar o conteúdo de uma URL e o conteúdo retornado para a mesma URL deve ser sempre o mesmo. Isso facilita que o browser possa guardar páginas em cache por algum tempo para melhorar a experiência do usuário.

POST

O método POST é utilizado para enviar dados para o servidor. Esse método não possui a obrigatoriedade de ter sempre o mesmo resultado para os mesmos dados enviados.

PUT

O método PUT é utilizado para criar recursos no servidor. Ele possui a característica do método GET, de ter a mesma resposta para o mesmo conteúdo enviado.

DELETE

Este método é utilizado para remover recursos do servidor.

HEAD

O método HEAD serve para solicitar ao servidor apenas o cabeçalho do conteúdo com os headers, para que o cliente decida se precisa requisitar o conteúdo completo ou não.

Os métodos mais utilizados pela maioria das aplicações são o GET para buscar dados e o POST para enviar dados. Os métodos PUT e DELETE raramente são suportados por uma aplicação, mas são bastante úteis quando se deseja implementar Web Services seguindo o padrão REST. Alguns outros protocolos utilizam métodos adicionais e até definem alguns novos, como por exemplo o protocolo WebDav.

Códigos de resposta HTTP

Os códigos de resposta HTTP possuem 4 famílias básicas que serão apresentadas abaixo. Mais informações sobre estes códigos de resposta podem ser encontradas nesta URL: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

  • 1XX – Códigos de informação, muito pouco utilizados
  • 2XX – Sucesso: o código mais utilizado desta família é o 200 que significa OK
  • 3XX – Redirecionamento: os códigos mais utilizados desta família são: 301 redirecionamento permanente e 307 redirecionamento temporário
  • 4XX – Erro do cliente: o código mais conhecido desta família é o 404 que significa recurso não encontrado
  • 5XX – Erro do servidor: o código mais conhecido desta família é o 500 que significa erro interno do servidor.

Cabeçalhos comuns

Como o protocolo HTTP não mantém estado portanto, cada request é independente. Para solucionar este problema os cookies podem informar ao servidor quem é o cliente que está acessando o recurso.

Segue uma lista de alguns cabeçalhos bastante utilizados.

  • Location: utilizado junto com os códigos 301 e 307 informando o novo endereço do recurso
  • Content-Type: informa o tipo MIME do recurso retornado ou enviado ao servidor
  • Set-Cookie: grava um cookie no browser
  • Cookie: envia os cookies armazenados no servidor
  • Cache-Control: informa se o browser pode ou não armazenar o recurso em cache
  • Content-Length: informa o tamanho do conteúdo enviado ou da resposta
  • Last-Modified: informa ao cliente quando foi a ultima alteração do recurso solicitado
  • Accept: informa ao servidor quais são os tipos de recursos que o cliente sabe processar
  • Allow: informa ao cliente quais as ações disponíveis para um recurso especifico
  • Content-Encoding: informa ao cliente ou ao servidor qual a codificação que está sendo utilizada para o conteúdo enviado ou recebido
  • Host: informa ao servidor qual o nome do host utilizado no request. Bastante útil para possibilitar a configuração de hosts virtuais no mesmo endereço IP

Bom, acho que era isto, claro que isto não é tudo sobre formação de URLs e sobre o protocolo HTTP, mas já é um bom começo.

Tags: , , , ,

10 May 10 Personalizando o código gerado pelo Rails 3


Eu sempre digo, tanto no meu livro, como em palestras ou conversas por ai, que o código gerado pelo Scaffold do rails (e praticamente qualquer outro gerador de código por ai) só serve para fazer um quick start do desenvolvimento.

Uma excessão a isto, na minha opinião pelo menos, é o código gerado pelo “scaffold” do Grails, pelo simples fato que ele sempre permitiu que a aplicação alterasse os templates que seriam utilizados para gerar o código. No rails isto sempre foi mais complicado, era preciso criar um generator e duplicar todo o generator que você queria personalizar para algumas vezes mudar quase nada no código gerado, ou seja, na maior parte das vezes isto não valeria a pena mesmo …

Mas com o Rails 3 seus problemas acabaramse! (alusão sem graça nenhuma ao seu Creiçom do Casseta e Planeta)

O Rails 3 permite que você personalize de forma bastante fácil os templates utilizados pelos generators do rails, basta criar uma pasta templates dentro do diretório lib do projeto, e neste diretório copiar para lá o template original do rails e fazer as alterações que você achar interessante para o seu projeto.

Os templates do rails ficam dentro da Ruby Gem “railties”, dentro de lib/generators.

Neste diretório existem os grupos de generators (erb, rails e test_unit) e dentro destes, um diretório para cada generator e dentro deste um diretório templates. O conteúdo deste diretório templates deve ser copiado para RAILS_ROOT/lib/templates/<grupo>/<nome do generator>.

E pronto, as alterações que você fizer na copia do seu projeto vão refletir no resultado da próxima vez que você executar o generator …

Acho que isto ficou meio confuso certo?

Então é o seguinte, vamos supor que queiramos alterar o código das views geradas pelo generator “scaffold”. O código original vai estar no diretório
RAILTIES_GEM/lib/rails/generators/erb/scaffold/templates
Este diretório contem os templates que vão gerar os arquivos .html.erb, para personalizar o código gerado para o seu projeto, basta copiar o conteúdo deste diretório para
RAILS_ROOT/lib/templates/erb/scaffold (isto mesmo, sem o diretório templates)
Alterar o que você quiser, e pronto, tudo vai funcionar …

Mas vamos combinar que ficar copiando estes arquivos da trabalho né? Isto me faz sentir falta de uma task de nome “install-templates” do grails, que copiava todos os templates de geração de código para o diretório da aplicação para que fossem personalizados. E como sou preguiçoso demais para ficar copiando arquivos a mão, criei uma task rake que faz isto. Não empacotei em um plugin por que achei muito simples, mas se alguem achar interessante posso fazer isto :D
Enquanto isto, quem quiser copiar o código desta task, esta disponível no gist, é só copiar o código e colocar em um arquivo .rake no diretório lib/tasks da aplicação.

E para usar a task é só rodar: rake templates:copy

Ahh, mas pra que serve isto?
Bom, para diversas coisas, como por exemplo fazer com que o código gerado pelo scaffold chegue mais próximo de um cadastro real da sua aplicação, ou para fazer um exemplo simples, eu personalizei o controller.rb do scaffold_generator para utilizar o novo Responder do Rails 3, o template ficou assim:

E o controlador gerado, mantem exatamente a mesma funcionalidade, mas em vez das 84 linhas padrão de um controlador gerado pelo scaffold do rails, ele tem apelas 51 linhas. O código gerado ficou assim:

Se você quiser saber mais sobre o Responder, o Akita publicou um post sobre isto hoje no blog dele.

Espero que este post ajude vocês a trabalhar menos daqui pra frente.

PS.: Achei legal testar o Gist, mas se vocês não gostaram e preferirem o código embedded nos posts do blog como eu sempre faço, é só avisar que volto a colar o código por aqui mesmo :D

Tags: , , , , , ,

27 Apr 10 Os 5 segredos para um sistema altamente escalável

Ontem enquanto caminhava do trabalho até o carro, conversando com um colega sobre infra estrutura para um sistema que ele estava pensando em construir. Qual arquitetura seguir, qual linguagem utilizar, qual framework, se rails era o suficiente para o que ele queria ou não, se devia usar ruby ou java, se ruby escala ou não escala, consegui convencer ele de que a resposta para a pergunta inicial dele é “depende”, mas também lembrei de alguns segredos que se aplicam em todos os casos que consigo lembrar agora para se criar um sistema web altamente escalável,por conseqüência, também são segredos bastante importantes para colocar a sua aplicação na “nuvem” e também são muito importantes para você escrever os serviços que farão parte da sua arquitetura SOA[bb] ou seus serviços REST.
Ou seja, sempre mantenha estas cinco regras em mente se o sistema que você esta desenvolvendo pode crescer um dia.

Sim, eu sei que estou sendo um pouco categórico demais, e que nenhuma regra se aplica a tudo e que existem excessões para o que estou escrevendo.

Stateless

Se você quer um sistema ou um serviço escalável, com certeza você quer que todas as requisições para este serviço sejam stateless.
Mas por que?
Simplesmente por que caso em um futuro próximo você precise rodar a sua aplicação em um cluster, você não prende um cliente a um nó do cluster, cada requisição pode ir para o nó do cluster que estiver com a menór carga naquele momento, fazendo com que o tempo de resposta aquela requisição seja o menor possível, mantendo o nó do cluster que vai atender a esta requisição ocupado o menor tempo possível.

Request Response Time

Acho que já falei um pouco sobre isto, mas imagine a seguinte situação, o sistema que você acabou de publicar bombou e agora em vez de ter que atender a 5 clientes (Você, seu melhor amigo, sua mãe, seu pai, sua namorada), agora o sistema tem que atender a 1k clientes simultâneos, e você pensa: Beleza, só preciso fazer um clusterzinho aqui, coloco mais duas maquinas e tudo beleza.
O problema é que o seu sistema antes estava com um “RRT” médio de 30s ou seja, com uma maquina só, você conseguia responder a duas requisições por minuto (considerando apenas um thread por máquina para facilitar o exemplo), agora com as duas maquinas novas você consegue atender a maravilhosos 6 requests por minuto. Ou seja, você tem um baita fiasco nas mãos.
Por isto o Request Response Time, ou seja, o tempo que a sua aplicação leva para atender a cada requisição tem que ser o mais baixo possível.
Por exemplo, enquanto escrevia este post criei uma aplicação rails com um cadastro de usuários de brinquedo, gerado pelo scaffold e o RRT é de 15.6ms (alto mas a maquina esta bastante carregada agora) o que permite que com uma thread seja possível atender a 64 requisições por segundo. Em um outro sistema que trabalhei, algumas requisições por diversos motivos chegaram a demorar 20s para retornar, mas isto foi resolvido claro :D
Bom, acho que deu para entender o por que o RRT é tão importante. Para clarear mais a idéia, imagine a seguinte situação que você já deve ter visto diversas vezes: Uma loja grande, com fila unica e 4 caixas atendendo. Se cada um dos caixas for muito demorado, você vai passar horas na fila, e não é economicamente viável ter um caixa por usuário. então a solução viável para este problema é otimizar o trabalho dos caixas para que cada caixa seja mais rápido e consiga atender a um maior número de clientes por minuto.

Cache

Cache é uma forma de melhorar muito o RRT, imagine que a sua mega aplicação seja uma página de classificados, em que a página inicial, que todos os clientes acessam ao chegar no site. Tenha uma lista com os produtos mais baratos anunciados.
Esta página precisa de uma consulta no banco de dados para ser renderizada, mas se você pensar bem os dados serão sempre os mesmos até que alguem delete um dos produtos listados ali ou cadastre outro mais barato.
Então faz todo o sentido “guardar uma copia” da página prontinha, e só mostrar aquela cópia para o próximo que acessar a página, e simplesmente deletar esta cópia quando uma das duas situações que pode alterar a página acontecer, ai no próximo acesso a página é renderizada novamente e a copia atualizada é guardada, e novamente ficamos utilizando apenas aquela cópia até que algo aconteça que possa fazer os dados ficarem desatualizados.
Isto é chamado de “cache”, na verdade esta é uma forma bem simples de cache. Estude aos recursos de caching do framework que você esta utilizando, do servidor de aplicações, do sistema operacional, do banco de dados.
Mas não ative tudo ao mesmo tempo, o correto uso de cache vai melhorar muito a performance da sua aplicação, mas o uso incorreto pode causar muitos problemas.

Remote Data

Muito pouca coisa é pior para um cluster do que uma aplicação que guarda dados localmente.
Imagine só o seguinte, naquela aplicação de classificados, você resolveu que as imagens correspondentes a cada anuncio ficariam gravadas na própria maquina do servidor, barbadinha, facilita o upload.
Ai é que mora o perigo.
Quando você precisar colocar isto pra rodar em um cluster, o segundo nó do cluster quando atendendo uma requisição, vai acreditar que as imagens estão naquele nó também, mas ele nem existia quando o anuncio foi criado, o upload foi feito pra outra maquina. Isto vai fazer com que o usuário veja uma página com a imagem quebrada.
Ahh, e como resolver isto?
Bom, existem diversas formas, uma bem simples é criar um servidor de “recursos estáticos”, ou seja, tudo o que é estático no site é servido por um endereço diferente, e quando for feito o upload de uma imagem, ela deve ser salva neste outro servidor. Também é possível utilizar um serviço como o Amazon S3 que vai fazer exatamente a mesma coisa que o seu servidor de recursos estáticos, mas de fora :D
Outra coisa a lembrar é do banco de dados, ele também não deve ficar na mesma maquina que o servidor da aplicação, ou o endereço do banco (seja ele SQL, document driven, …) deve ser configurável. Isto por que em um cluster, o ideal é que todos os nós acessem o mesmo banco de dados, ou no mínimo que todos os bancos do cluster estejam sincronizados.

Proxy Reverso

Um próxi reverso no contexto deste post vai ser utilizado para mascarar algumas das técnicas de caching, para implementar algumas técnicas de caching e principalmente para esconder do usuário que você tem diversos servidores, ele vai ser o “front-end” da sua aplicação, a mágica do cloud só pode acontecer atrás do proxy, e ele também pode esconder do usuário o fato de você usar um “servidor de recursos estáticos” sendo ele interno criado por você mesmo ou você usando um S3 da vida, o proxy reverso pode mascarar o endereço real da aplicação.
Mas nunca esqueça de um detalhe importante. Este proxy mesmo sendo vital para uma arquitetura cloud, ou um cluster menorzinho, ele acaba de adicionar um SPoF (Single Point Of Failure) se o proxy morrer, a sua mega aplicação esta fora do ar.

Bom, acho que era isto, sempre tem alguem perguntando como fazer um sistema escalável, como fazer um sistema robusto, como fazer um sistema para rodar em cluster ou na nuvem.
Este post não é a resposta final para todos os problemas, este post não responde estas perguntas para todos os casos, mas para a maior parte dos casos que me lembro agora, as dicas deste post são pontos imprescindíveis para qualquer aplicação ou arquitetura que pretenda ter um mínimo de escalabilidade.
Espero que este post seja útil para alguem, e se não concordarem com alguma coisa é só deixar um comentário.

Tags: ,

11 Jan 10 Ruby on Rails 101 – Encurtador de URLs = Novo Blog

Ok, o titulo do post não ficou legal, mas a idéia é que tem tanto encurtador de URLs por ai que eu resolvi fazer um em rails também para brincar um pouco, e como a implementação ficou muito simples, vou tentar transformar isto em um tutorial bem básico de Rails.
Mas vejam bem, a idéia é só mostrar o básico, não vou colocar mais um no ar, já tem um excelente feito pelo nosso amigo Manoel Lemos, o zapt.in onde ele esta adicionando recursos muito legais. Só peguei a idéia por que achei que se tornaria um tutorial mais divertido do que o famoso blog em rails :D

Primeiro, você vai precisar do Rails[bb] instalado, e para ter o Rails instalado você vai precisar do interpretador Ruby instalado, tem diversos posts sobre isto por ai, mas basicamente numa maquina windows, sugiro instalar o “Instant Rails”, num linux instale o Ruby e depois o Ruby Gems e logo depois execute o comando “gem install rails”.

Deste ponto em diante vou considerar que você já tem o rails instalado e funcionando.

Agora com o Rails instalado, vamos começar a desenvolver a aplicação, vou chamar de “us” para “URL Shortener”, como qualquer projeto rails, vamos começar digitando:

1
2
rails us
cd us

Uma aplicação rails tem inicialmente a seguinte estrutura de diretórios:

  • app
    • controllers
    • helpers
    • models
    • views
      • layouts
  • config
    • environments
    • initializers
    • locales
  • db
  • doc
  • lib
    • tasks
  • log
  • public
    • images
    • javascripts
    • stylesheets
  • script
    • performance
  • test
    • fixtures
    • functional
    • integration
    • performance
    • unit
  • tmp
    • cache
    • pids
    • sessions
    • sockets
  • vendor
    • plugins

Não vou explicar para que serve cada um deles, mas os mais importantes para este mini tutorial são:

  • app/controllers – onde vão ficar os controladores, o código que faz o meio de campo entre a lógica e a view.
  • app/models – onde vão ficar os models, a interface da aplicação com o banco de dados e toda a lógica
  • app/views – onde vamos renderizar os dados para os usuários
  • public/* – onde ficam os recursos estáticos, como imagens, estilos, javascripts
  • config – onde ficam as configurações da aplicação
  • scripts – scripts para poupar trabalho, gerar código, rodar servidores, …

A nossa aplicação vai ser composta de dois controladores, um model e algumas views.

O ideal seria começar escrevendo testes, mas como este é um post estilo “introdução ao rails” vou deixar os testes de lado.

A primeira coisa que vamos fazer é criar um cadastro básico de URLs, para isto vamos utilizar o gerador do rails, com o seguinte comando:

1
ruby script/generate scaffold url_info href:string clicks:integer

Este comando vai gerar uma série de arquivos, vamso dar uma olhada em alguns deles:

1
2
class UrlInfo < ActiveRecord::Base
end

Este é o conteúdo do arquivo app/models/url_info.rb, toda a implementação do nosso model para um cadastro simples e, por enquanto, sem validações.

E já podemos inclusive criar o banco de dados padrão da aplicação, o rails veio configurado por padrão para utilizar o banco de dados sqlite3, mas isto pode ser facilmente alterado, mas por enquanto vamos aceitar esta configuração e executar o comando:

1
rake db:migrate

Isto vai executar as migrations da aplicação, uma migration foi criada no último comando, vamos dar uma olhada rápida nela:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CreateUrlInfos < ActiveRecord::Migration
  def self.up
    create_table :url_infos do |t|
      t.string :href
      t.integer :clicks
 
      t.timestamps
    end
  end
 
  def self.down
    drop_table :url_infos
  end
end

Esta migration possui o código para criar uma tabela de nome “url_infos”, com um campo de nome “href” de tipo “string” e um campo “clicks” de tipo “integer”, o mapeamento do tipo ruby para o tipo SQL vai depender do banco de dados, do driver que o rails utilizar para acessar o banco.

Em uma migration é importatne sempre implementar os dois métodos, o self.up cria coisas no banco de dados, e o self.down apaga coisas do banco de dados, tudo o que for criado no self.up tem que ser apagado no self.down, desta forma permitindo que voltemos a qualquer versão da aplicação para corrigir algum bug se necessário.

No exemplo estamos utilizando os métodos create_table e drop_table da migration, mais informações sobre estes métodos podem ser obtidas nesta página da documentação do Rails.

A configuração de qual banco a aplicação esta acessando fica no arquivo config/database.yml que podemos ver abaixo com o conteúdo padrão:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# SQLite version 3.x
#   gem install sqlite3-ruby (not necessary on OS X Leopard)
development:
  adapter: sqlite3
  database: db/development.sqlite3
  pool: 5
  timeout: 5000
 
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  adapter: sqlite3
  database: db/test.sqlite3
  pool: 5
  timeout: 5000
 
production:
  adapter: sqlite3
  database: db/production.sqlite3
  pool: 5
  timeout: 5000

Este arquivo tem 3 sessões, correspondentes aos ambientes de desenvolvimento, testes e produção, ou seja, já podemos deixar estes 3 bancos de dados[bb] configurados, o que pode facilitar bastante a vida, ou complicar as vezes :D

Mas a idéia básica é que em cada ambiente é possível configurar qual o driver do banco de dados “adapter”, e os parâmetros deste driver, neste caso apenas o nome do banco é o suficiente, não vou entrar em maiores detalhes aqui por que não é a idéia deste post, quero fazer o encurtador de URLs funcionar antes de você dormir ou cansar de ler …

Então vamos lá, o rails criou um cadastro completo, que se você digitar o seguinte comando para inicializar o servidor, já pode acessar:

1
ruby script/server

Agora abra o seu browser preferido e acesse o endereço: http://localhost:3000/url_infos

Você vai ver uma listagem de informações de URLs, e quantos clicks cada URL já recebeu, você já pode até cadastrar algumas URLs ai, não vamos mexer muito neste controlador que foi criado, vamos alterar só um pouquinho, não faz sentido na hora do cadastro de uma URL ser necessário informar o número de clicks, então vamos abrir o arquivo app/views/url_infos/edit.html.erb e deixe ele como o que esta abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<h1>Editing url_info</h1>
<% form_for(@url_info) do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label :href %><br />
    <%= f.text_field :href %>
  </p>
  <p>
    <%= f.submit 'Update' %>
  </p>
<% end %>
 
<%= link_to 'Show', @url_info %> |
<%= link_to 'Back', url_infos_path %>

(Dica, eu removi o parágrafo que continha o campo “clicks”)

Nesta página podemos ver alguns dos helpers do rails para a geração de formulários HTML, e para tratamento de mensagens, a idéia do helper “form_for” é que a variável passada como argumento para o bloco representa um formulário para “aquele elemento”, isto torna possível utilizar os outros helpers “formulário.text_field”.
O Rails tem diversos helpers, tanto para formulários, para options, para AJAX e diversos outros.

Agora vamos duplicar a mudança no outro formulário no arquivo app/views/url_infos/new.html.erb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<h1>New url_info</h1>
 
<% form_for(@url_info) do |f| %>
  <%= f.error_messages %>
 
  <p>
    <%= f.label :href %><br />
    <%= f.text_field :href %>
  </p>
  <p>
    <%= f.submit 'Create' %>
  </p>
<% end %>
 
<%= link_to 'Back', url_infos_path %>

Claro que estes dois arquivos são bem parecidos, e que poderíamos juntar todo o código repetido dos dois, mas vamos deixar isto para depois, por enquanto isto não nos interessa muito.

Agora vamos editar o controlador no arquivo app/controllers/url_infos_controller.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
class UrlInfosController < ApplicationController
  # GET /url_infos
  # GET /url_infos.xml
  def index
    @url_infos = UrlInfo.all
 
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @url_infos }
    end
  end
 
  # GET /url_infos/1
  # GET /url_infos/1.xml
  def show
    @url_info = UrlInfo.find(params[:id])
 
    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @url_info }
    end
  end
 
  # GET /url_infos/new
  # GET /url_infos/new.xml
  def new
    @url_info = UrlInfo.new
 
    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @url_info }
    end
  end
 
  # GET /url_infos/1/edit
  def edit
    @url_info = UrlInfo.find(params[:id])
  end
 
  # POST /url_infos
  # POST /url_infos.xml
  def create
    @url_info = UrlInfo.new(params[:url_info])
    @url_info.clicks = 0
    respond_to do |format|
      if @url_info.save
        flash[:notice] = 'UrlInfo was successfully created.'
        format.html { redirect_to(@url_info) }
        format.xml  { render :xml => @url_info, :status => :created, :location => @url_info }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @url_info.errors, :status => :unprocessable_entity }
      end
    end
  end
 
  # PUT /url_infos/1
  # PUT /url_infos/1.xml
  def update
    @url_info = UrlInfo.find(params[:id])
 
    respond_to do |format|
      if @url_info.update_attributes(params[:url_info])
        flash[:notice] = 'UrlInfo was successfully updated.'
        format.html { redirect_to(@url_info) }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @url_info.errors, :status => :unprocessable_entity }
      end
    end
  end
 
  # DELETE /url_infos/1
  # DELETE /url_infos/1.xml
  def destroy
    @url_info = UrlInfo.find(params[:id])
    @url_info.destroy
 
    respond_to do |format|
      format.html { redirect_to(url_infos_url) }
      format.xml  { head :ok }
    end
  end
end

A única alteração do código gerado foi a adição da linha “@url_info.clicks = 0″ no método create, que informa aquele parâmetro que removemos do formulário.

Neste arquivo podemos também ver alguns métodos interessantes do rails, veja na lista abaixo a explicação de alguns deles:

  • UrlInfo.all – A classe ActiveRecord::Base do rails tem diversos métodos para pesquisa, o all é um alias para find(:all) e aceita os mesmos parâmetros
  • respond_to – O bloco respond_to informa ao rails como responder para diferentes tipos mime
  • render – Este é o método que realmente envia a resposta para o cliente, ele possui diversos parâmetros que permitem o envio de respostas AJAX, XML, HTML e diversas outras de forma normalmente transparante, a documentação pode ser encontrada aqui.
  • @url_inf.save – save é o método da classe ActiveRecord::Base que insere ou altera o conteúdo de um registro de uma tabela.
  • @url_info.update_attributes – este é o método da classe ActiveRecord::Base que atualiza os atributos de um registro alterados e salva as alterações no banco de dados
  • params[...] – params é um hash que permite acesso aos parâmetros enviados pelo usuário
  • @url_info.destroy – este é o método utilizado para apagar um registro do banco de dados
  • format. – format. dentro de um bloco “respond_to” informa ao rails quais tipos mime este método sabe retornar, assim o rails decide qual o mais adequado a solicitação do usuário
  • UrlInfo.find – A classe ActiveRecord::Base do rails tem diversos métodos para pesquisa, o método find é a base para a maioria deles
  • redirect_to – este método permite enviar ao browser um código de redirecionamento HTTP

Acho que por enquanto já esta bom de alterações no cadastro, vamos fazer o encurtador de URLs funcionar.

Para isto vamos criar mais um controlador, execute no console o seguinte comando:

1
ruby script/generate controller redirector index

Como o nome diz, este é o controlador que vai fazer os redirecionamentos, depois deste comando executado, o arquivo app/controllers/redirector_controller.rb foi criado, vamos editar este arquivo para que ele fique mais ou menos assim:

1
2
3
4
5
6
7
class RedirectorController < ApplicationController
  def index
	  ui = UrlInfo.find params[:id]
	  redirect_to ui.href if ui
  end
 
end

Isto já faz o redirecionador funcionar, mas não exatamente da maneira que gostaríamos :D

Por enquanto para ele funcionar precisamos acessar http://localhost:3000/redirector?id=… a idéia é que funcione acessando http://localhost:3000/[id]

Quando o id for passado o redirecionamento deve ocorrer automagicamente, quando não for passado devemos ver a lista de links conhecidos com quantos clicks cada um já teve.

Para que isto funcione vamos editar o arquivo config/routes.rb como no exemplo abaixo (vou apagar todos os comentários para facilitar a leitura do arquivo, comentários em Ruby são as linhas começadas por “#”).

1
2
3
4
5
ActionController::Routing::Routes.draw do |map|
  map.resources :url_infos
 
  map.connect ':id', :controller => 'redirector', :action => 'index'
end

A linha map.resources :url_infos foi gerada automaticamente com o scaffold, ela configura todas as rotas para o cadastro de URLs.
Esta linha configura as seguitnes rotas na aplicação:

Nome Método HTTP Caminho Mapeamento
url_infos GET /url_infos(.:format) {:action=>”index”, :controller=>”url_infos”}
  POST /url_infos(.:format) {:action=>”create”, :controller=>”url_infos”}
new_url_info GET /url_infos/new(.:format) {:action=>”new”, :controller=>”url_infos”}
edit_url_info GET /url_infos/:id/edit(.:format) {:action=>”edit”, :controller=>”url_infos”}
url_info GET /url_infos/:id(.:format) {:action=>”show”, :controller=>”url_infos”}
  PUT /url_infos/:id(.:format) {:action=>”update”, :controller=>”url_infos”}
  DELETE /url_infos/:id(.:format) {:action=>”destroy”, :controller=>”url_infos”}

A segunda linha configura a aplicação para quando receber apenas um parâmetro passar isto para o controlador de nome “redirector” para a action “index”, agora se acessarmos o endereço http://localhost:3000/1 a aplicação vai nos redirecionar para a primeira URL cadastrada, mas ainda não esta legal, precisamos contar os clicks tabém.

Para contar o clicks vamos alterar o redirector controller que editamos antes, o código dele vai ficar assim:

1
2
3
4
5
6
7
8
9
10
class RedirectorController < ApplicationController
  def index
        ui = UrlInfo.find params[:id]
        if ui
                ui.clicks += 1
                ui.save
                redirect_to ui.href
        end
  end
end

Agora antes de redirecionar a quantidade de clicks é incrementada e a informação é salva no banco de dados.

Agora vamos mudar a página inicial para a listagem de URLs, temos duas formas de fazer isto, forma chinelona:
Editar o arquivo public/index.html e configurar um meta refresh, o conteúdo fica como abaixo (HTML padrão).

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
  <head>
    <meta http-equiv="refresh" content="0;url=url_infos" />
    <title>Mega URL Shortener Sample</title>
  </head>
  <body>
  <a href="url_infos">Link List</a>
  </body>
</html>

Ou apagar o arquivo public/index.html, e editar novamente o arquivo config/routes.rb, verifique abaixo o conteúdo alterado.

1
2
3
4
5
ActionController::Routing::Routes.draw do |map|
  map.resources :url_infos
  map.root :controller => 'url_infos'
  map.connect ':id', :controller => 'redirector', :action => 'index'
end

Foi adicionada a linha “map.root :controller => ‘url_infos’” que informa qual a ação padrão da aplicação.

E com isto já temos o encurtador de URLs quase pronto, faltam alguns detalhes, primeiro no arquivo app/views/redirector/index.html.erb vamos adicionar uma mensagem dizendo que a URL não esta cadastrada.

1
<b>A URL informada não esta cadastrada no sistema</b>

Isto vai funcionar por que o controlador “redirector” só chama o redirect se a “UrlInfo” for encontrada, caso contrário ele executa a ação default, que é renderizar a “view” correspondente ao método.

Agora vamos apagar alguns arquivos no diretório app/views/url_infos/, siga a lista:

  • edit.html.erb
  • new.html.erb
  • show.html.erb

Vamos editar o arquivo index.html.erb no mesmo diretório:

1
2
3
4
5
6
7
<h1>Shortened URLs</h1>
<div id="form">
<%= render :partial => 'editor_form' %>
</div>
<div id="table">
<%= render :partial => 'urls_table' %>
</div>

Todo o conteúdo deste arquivo foi movido para dois partials, partials são uma forma de reutilizar código de views no rails, mas neste caso estaremos utilizando partials para implementar um pouco de AJAX.

A idéia é que o formulário do topo da página seja submetido via ajax e que atualize apenas o pedaço da página que for necessário, veja abaixo como ficaram os dois partials:
_editor_form.html.erb

1
2
3
4
5
6
7
8
<% form_remote_for(@url_info) do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label :href, 'URL:' %>
    <%= f.text_field :href %>
    <%= f.submit 'Create' %>
  </p>
<% end %>

Neste formulário estamos utilizando o helper “form_for_remote” que cria um formulário que sera submetido via AJAX, não fazendo refresh da página toda de uma só vez.
_urls_table.html.erb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<table width="100%">
  <tr>
    <th>Original URL</th>
    <th>Shortened URL</th>
    <th>Clicks</th>
  </tr>
<% @url_infos.each do |url_info| %>
  <tr>
    <td><%=h url_info.href %></td>
    <td><%=h url_for(:controller => 'redirector', :id => url_info.id, :only_path => false) %></td>
    <td><%=h url_info.clicks %></td>
    <td><%= link_to 'Go To', :controller => 'redirector', :id => url_info.id %></td>
    <td><%= link_to 'Destroy', url_info, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
<% end %>
</table>

A tabela não sofreu alterações grandes, apenas foi colocado mais um campo para mostrar qual a URL no sistema correspondente a uma URL cadastrada, e para que isto ficasse dinâmico, o helper “url_for” for utilizado, com o parâmetro “:only_path” setado para false, desta forma a URL completa seria impressa.

Para que este formulário via AJAX[bb] funcione, algumas alterações precisaram ser feitas no controlados “url_infos”, como pode ser visto abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class UrlInfosController < ApplicationController
  # GET /url_infos
  # GET /url_infos.xml
  def index
    @url_infos = UrlInfo.all
    @url_info = UrlInfo.new
 
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @url_infos }
    end
  end
 
  # POST /url_infos
  # POST /url_infos.xml
  def create
    @url_info = UrlInfo.new(params[:url_info])
    @url_info.href = params[:href] if params[:href]
    @url_info.clicks = 0
    respond_to do |format|
      if @url_info.save
        format.html  do
          flash[:notice] = 'UrlInfo was successfully created.'
          redirect_to root_url
        end
        format.xml  { render :xml => @url_info, :status => :created, :location => @url_info }
	format.js  do
          @url_infos = UrlInfo.all
          @url_info = UrlInfo.new
          render :update do |page|
            page.replace_html 'form', :partial => 'editor_form'
            page.replace_html 'table', :partial => 'urls_table'
            page.alert 'UrlInfo was successfully created.'
          end 
        end
      else
        format.html { render :action => "index" }
        format.xml  { render :xml => @url_info.errors, :status => :unprocessable_entity }
        format.js do
          render :update do |page|
            page.alert @url_info.errors.full_messages.join '\n'
          end
        end
      end
    end
  end
 
  # DELETE /url_infos/1
  # DELETE /url_infos/1.xml
  def destroy
    @url_info = UrlInfo.find(params[:id])
    @url_info.destroy
 
    respond_to do |format|
      format.html { redirect_to(url_infos_url) }
      format.xml  { head :ok }
    end
  end
end

Alguns métodos foram removidos, e o metodo create sofreu algumas alterações dentro do bloco “respond_to” adicionando suporte a respostas tipo “javascript”. E tem mais um detalhe no mesmo método, a segunda linha foi adicionada para quebrar todo o suporte “REST” do rails, como este é um encurtador de URLs eu quero que seja possível adicionar uma URL via uma chamada a uma URL do sistema, neste caso vai ser “/add/” e para isto na segunda linha do método “create” se o parâmetro “href” existir este é utilizado como valor da URL sendo criada no sistema, mas para isto funcionar a alteração abaixo é necessária no arquivo config/routes.rb:

1
2
3
4
5
6
7
ActionController::Routing::Routes.draw do |map|
  map.resources :url_infos
 
  map.root :controller => 'url_infos'
  map.connect '/add/:href', :controller => 'url_infos', :action => 'create', :href => /http[s]{0,1}:\/\/.*/
  map.connect ':id', :controller => 'redirector', :action => 'index'
end

A alteração feita foi a adição da linha “map.connect ‘/add/…”, preste atenção na utilização de uma expressão regular na especificação do parâmetro “href” no final da linha, isto permite que a URL completa seja utilizada como parâmetro, se isto não for utilizado o parâmetro vai terminar na primeira “/” da URL e o roteamento não vai funcionar corretamente.

Agora para que o AJAX funcione vamos alterar o layout gerado quando executamos o primeiro comando “script/generate scaffold …”, naquele momento foi gerado também o arquivo app/views/layouts/url_infos.html.erb.

Como este arquivo de layout[bb] tem o nome de um controlador, ele é utilizado apenas por este controlador, se o nome do arquivo fosse “application.html.erb” ele seria utilizado por todos os controladores da aplicação que não tivesse um layout próprio.

O conteúdo do arquivo é semelhante a qualquer outra view, uma coisa interessante de se reparar no nome do arquivo é que ele contem o “mime type” no nome, então se quisermos criar um “layout” para respostas XML vale a mesma lógica (application.xml.erb).

Vamos ver o conteúdo deste layout:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
  <title>UrlInfos: <%= controller.action_name %></title>
  <%= stylesheet_link_tag 'scaffold' %>
  <%= javascript_include_tag :defaults %>
</head>
<body>
 
<p style="color: green"><%= flash[:notice] %></p>
 
<%= yield %>
 
</body>
</html>

Foi adicionada a linha “<%= javascript_include_tag :defaults %>” que adiciona na página gerada a chamada para o javascript padrão do rails (que utiliza por padrão a biblioteca prototype).

O conteúdo das views é renderizado no lugar em que se encontra “<%= yield %>” neste arquivo de layout.

A última alteração que falta é impedir que sejam cadastradas URLs duplicadas ou em branco, para isto vamos voltar ao arquivo do model app/models/utl_info.rb

1
2
3
4
class UrlInfo < ActiveRecord::Base
	validates_uniqueness_of :href
	validates_presence_of :href
end

O suporte a validações do rails é bem flexível, e possui helpers para diversas validações, neste caso estamos garantindo que a URL seja única e esteja preenchida.

Bom, acho que era isto, temos um encurtador de URLs bem simples pronto. Esepro que o exemplo tenha sido útil para mostrar alguns dos recursos do Rails fugindo um pouco do exemplo padrão do blog.

Siga os links para a documentação do Rails, e lembre-se de programar sempre com o site da API do rails aberto em um browser.

PS.: o código completo para este exemplo esta disponível no github.

Tags: , , , ,

09 Nov 09 Titanium Developer – Reutilizando toda a expertise de desenvolver aplicações Web no Desktop

Titanium Desktop Titanium Mobile

Este fim de semana resolvi testar o Titanium Developer, da appcelerant. Achei a idéia espetacular …
O Titanium permite que você desenvolva aplicações desktop e mobile nativas utilizando HTML e scripting como se faz para WEB.
A base da aplicação é uma janela com um WebKit embedded, por tanto, todo o poder do HTML5 esta a sua disposição, alem de uma biblioteca de javascript criada pelo pessoal da appcelerant que disponibiliza via javascript acesso a diversos recursos do SO Host da aplicação.
É possível criar aplicações para Mac, Linux e windows utilizando exatamente o mesmo código, com acesso a alguns recursos do SO presentes nos três ambientes, a se você for pensar, a maior parte das aplicações não tem muita integração do o SO mesmo …
O Titanium suporta Python, Ruby e PHP para scripting na aplicação, e o código fica bem estranho a principio, pois é possível chamar funções ruby/python/php de dentro das funções javascript, o código destas linguagens pode ser incluído no HTML com tags script exatamente como é feito com o javascript …
Não brinquei muito com ele ainda, mas a idéia me agradou bastante, só fiz até agora uma calculadora bem feinha :D
O código basico é um new application no titanium desktop, lembrando de marcar o checkbox de suporte a ruby, ai só alterei o index.html de dentro do diretório resources para o código abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<html>
	<head>
		<script type="text/javascript" src="jquery-1.3.2.js"></script>
		<script type="text/ruby">
			def calc(x,y,op)
				x.to_f.send(op.to_sym,y.to_f)
			end
		</script>
	</head>
	<body style="background-color:#1c1c1c;margin:0">
		<div style="border-top:1px solid #404040">
			<div style="color:#fff;;padding:10px">
			<form>
				<fieldset>
					<legend>Calculator</legend>
					Resultado:<input type="text" readonly="true" id="result"/><br/>
					Valor 1:<input type="text" id="valor1"/><br/>
					Valor 2:<input type="text" id="valor2"/><br/>
					<input type="button" value="+" onclick="result.value=calc(valor1.value,valor2.value,this.value)"/>
					<input type="button" value="-" onclick="result.value=calc(valor1.value,valor2.value,this.value)"/>
					<input type="button" value="*" onclick="result.value=calc(valor1.value,valor2.value,this.value)"/>
					<input type="button" value="/" onclick="result.value=calc(valor1.value,valor2.value,this.value)"/>
				</fieldset>
			</form>
			</div>
		</div>
	</body>
</html>

E a calculadora funciona direitinho!
É criado um executável windows (a maquina que utilizei para testar é um windows) e se eu clicar em package, é possível ainda criar o executável para linux e mac utilizando os servidores da appcelerant ..
A aplicação pode ficar disponível na cloud deles, e ainda é possível criar aplicações para iPhone e Android com a mesma estrutura, ou seja, a idéia de como programar é a mesma …
E alem disto, ainda existe o Bowline, um framework Ruby escrito para facilitar mais ainda o uso do Titanium Desktop. Este ainda não tive tempo de brincar, mas pelo que li no site parece muito bom também :D

Finalmente temos uma forma fácil de programar aplicações desktop multi plataforma sem ter que penar na mão do SWING :D
Vou tentar fazer uns exemplos mais complexos para ver se o brinquedo vale mesmo a pena, mas eu já adianto que adorei a idéia :D
Mas nem tudo é perfeito, eu ainda estou procurando no site informações sobre licenciamento, não sei se é necessário pagar para distribuir as aplicações, não encontrei nada a respeito …
Mas a idéia continua sendo excelente :D
Recomendo uma olhada …

Tags: , , ,

22 Oct 09 Ruby On Rails: Produtividade? Agilidade? Apenas uma Ferramenta?

Este foi o título da Palestra, se é que se pode chamar assim, que eu apresentei ontem na semana acadêmica da faculdade de tecnologia do SENAC/RS.
Como prometi, os slides estão no SlideShare, e embedded abaixo:

Não vou colocar o código aqui por que praticamente não teve código, foi mais uma conversa com algumas demonstrações de código, não sei se era bem isto que eles estavam esperando, mas espero que tenha sido útil.
Se alguem que estava la vir este post, deixe um comentário aqui dizendo o que achou da conversa, e podem fazer perguntas também.

Tem mais uma palestra no JugDay no sábado, espero ver vocês por lá, vou sortear mais uma copia do meu livro por lá :D

Tags: , , , ,

21 Oct 09 Promoção Natal com Tecnologia – Mais uma chance de ganhar uma copia do meu livro, alem de alguns outros premios.

Logo Promoção Natal com Tecnologia

É com muito prazer que anuncio a Promoção de Natal com Tecnologia do blog FernandoQuadro.com.br, em parceria com a Editora MundoGEO e Editora Novatec, que irá presentear 6 leitores. Confira abaixo a premiação:

Quer participar? Basta deixar um comentário no post da promoção, ler as instruções e torcer para ser sorteado. Boa sorte!

20 Oct 09 Palestras da Semana

JugDay Faculdade de Tecnologia SENAC/RS


Pelo titulo parece que tenho feito palestras todas as semanas :D
Bom, mas esta semana tem duas:
Na quarta feira vou falar de Ruby on Rails, de produtividade, de metodologias ágeis e conversar um pouco com os alunos da Faculdade de Tecnologia do Senac/RS
e no sábado tem o RSJUG Jug Day onde eu vou apresentar um tutorial sobre desenvolvimento de aplicações Ruby on Rails, para mudar um pouquinho vou fazer um sistema de gerenciamento de projetos bem simples, mas já foge bastante do blog e da loja virtual que todo mundo faz o tempo todo, e pra deixar mais divertido vou usar um pouco de jQuery para tornar a aplicação mais interativa.

Bom era isto, ando escrevendo pouco por aqui por que a correria anda grande, tento responder todos os emails :D

Estou com um projeto que pode se tornar mais um livro, mas com certeza não vai ser publicado este ano, o trabalho ta corrido, o filho ta grande e cada dia mais parecido comigo, mas o importante é que tem saúde :D Daqui a 3 dias faz 5 meses.

Nos vemos dia 21 a noite no Senac e dia 24 na faculdade Dom Bosco no Jug Day!

Tags: , , , , , ,

24 Sep 09 Software Auto Identificável – Self Identifying Software

2identify_yourself_logo3333
esta imagem foi encontrada na web usando o images.google.com e eu achei que tinha a ver com o post

Eu não conhecia o conceito até ver este twit do CV falando deste post sobre Self Identifying Software. E lendo o post percebi que já passei e algumas vezes ainda passo pelo mesmo problema: Identificar qual versão do software esta instalada em um servidor, ou em que versão do sofware algum bug apareceu ou aconteceu ou acontece …

Bom, eu curti a idéia e fiquei pensando em como implementar isto, pelo menos em projetos Java, disto sairam estes “code snippets” abaixo …
Bom, normalmente trabalho com o ANT para fazer o build de projetos Java, e tenho utilizado o Subversion (sim, eu conheço o GIT e gosto dele, mas no momento não vai rolar no trampo, mas uso para projetos pessoais :D )
Então, fui a página do subversion e baixei o SVNANT, desenvolvido pelo pessoal do subclipse, e integrei ele no meu build assim:

1
2
3
4
5
6
7
8
9
10
        <path id="svn_tasks">
		<fileset dir="${directory_you_unzipped_the_svnant_package}" includes="svn*.jar">
		</fileset>
	</path>
	<taskdef classpathref="svn_tasks" resource="org/tigris/subversion/svnant/svnantlib.xml" />
	<target name="_setup_svn_info">
		<svn failonerror="false" javahl="true" svnkit="false">
			<info target="${basedir}" verbose="true"/>
		</svn>
	</target>

Depois disto, em qualquer parte do build em que você for criar um .jar, .war ou qualquer tipo de pacote java, basta fazer algo parecido com isto:

1
2
3
4
5
6
7
8
9
        <target name="build_jar" depends="_setup_svn_info,compile">
		<jar destfile="${dist.dir}/${jar.name}">
			<fileset dir="${basedir}/bin" includes="*.*" />
			<manifest>
				<attribute name="SVN-URL" value="${svn.info.url}" />
				<attribute name="SVN-REV" value="${svn.info.rev}" />
			</manifest>
		</jar>
        </target>

Claro que o importante é o depends e o manifest, o resto vai depender do seu build, isto não é nem um exemplo real, escrevi direto aqui no blog para dar a idéia, então se tiver algum problema com o código me avisem nos comentários :D

Mas isto não é útil se você não conseguir ler o MANIFEST.MF do .jar onde a sua classe se encontra, então estou colocando aqui também um exemplo de código para isto, mas lembre-se de alterar o nome da classe para cada pacote, caso contrário você nunca saberá de qual pacote a classe esta sendo carregada :D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package blog.urubatan;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.jar.Manifest;
 
public class ExemploDoUrubatan {
	private Manifest manifest;
 
	private void initManifest() throws URISyntaxException,
			FileNotFoundException, IOException {
		Class<?> clazz = getClass();
		URL classContainer = clazz.getProtectionDomain().getCodeSource()
				.getLocation();
		File manifestContainer = new File(classContainer.toURI());
		File metaInf = new File(manifestContainer, "META-INF");
		File manifestFile = new File(metaInf, "MANIFEST.MF");
		manifest = new Manifest(new FileInputStream(manifestFile));
 
	}
 
	public ExemploDoUrubatan() throws URISyntaxException,
			FileNotFoundException, IOException {
		initManifest();
	}
 
	public String getSvnUrl() {
		return manifest.getMainAttributes().getValue("SVN-URL");
	}
 
	public String getSvnRevision() {
		return manifest.getMainAttributes().getValue("SVN-REV");
	}
 
	public static void main(String[] args) throws FileNotFoundException,
			URISyntaxException, IOException {
		ExemploDoUrubatan ex = new ExemploDoUrubatan();
		System.out.println(ex.getSvnUrl());
		System.out.println(ex.getSvnRevision());
	}
}

Se for a versão de um arquivo .war o código pode ser colocado em um servlet com uma URL conhecida, ou em um listener que vai guardar esta informação no servlet context para ser impresso depois por uma URL conhecida …
Se o servlet for a opção selecionada, o código ficaria parecido com isto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package blog.urubatan;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
public class ServletExample extends HttpServlet {
	private static final long serialVersionUID = 1L;
 
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		super.doPost(req, resp);
	}
 
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		String warRoot = getServletContext().getRealPath(".");
		File manifestContainer = new File(warRoot);
		File metaInf = new File(manifestContainer, "META-INF");
		File manifestFile = new File(metaInf, "MANIFEST.MF");
		Manifest manifest = new Manifest(new FileInputStream(manifestFile));
		PrintWriter writer = resp.getWriter();
		Attributes mainAttributes = manifest.getMainAttributes();
		String svnUrl = mainAttributes.getValue("SVN-URL");
		String svnRev = mainAttributes.getValue("SVN-REV");
		writer.format("URL: %s\nRev:%s\n", svnUrl, svnRev);
	}
 
}

Com isto, pelo menos para projetos java, já cobrimos duas das situações mais comuns, que são saber a versão de uma API e saber a versão de uma aplicação WEB.
Com isto já é possível verificar o deploy de aplicações durante o build se o script for um pouco mais inteligente, o pessoal de testes tem condições de dizer exatamente qual foi a build que gerou o problema, é possível construir um “dashboard” com a versão de tudo que é utilizado no sistema, facilitando bastante a identificação de onde o problema ocorre, e principalmente, no caso de clusters, permitindo que seja verificada a versão em cada um dos nós de uma forma fácil …

Agora no caso do Rails, eu ainda não consegui decidir qual a melhor abordagem para isto …
criar um arquivo com estes meta dados dentro do diretório config, atualizar este arquivo por uma task rake toda vez que for executar um deploy via capistrano e criar um controller para informar a versão?
As gems já tem um mecanismo de versionamento, seria só atualizar a versão da gem a cada build, coisa que pode ser feita até com keywork expansion, ou utilizando o mesmo esquema do rake mencionado antes.
Bom, vou pensar mais nisto, derepente rola até criar um plugin para aplicações rails pra facilitar a vida :D
O que vocês acham?

Tags: , , , ,

13 Sep 09 “Canelada!” – Terceira errata do melhor livro de RoR que eu escrevi no mundo inteiro!!

Canelada! é uma referencia a leitura de emails do Nerdcast, o poscast mais engraçado que eu ouço :D
Mas voltando ao assunto, preciso agradecer ao Mauro Oliveira por me avisar de alguns pequenos erros que ele encontrou enquanto lia meu livro sobre Ruby On Rails, o melhor livro sobre Ruby on Rails que eu já escrevi no mundo inteiro :D

Na página 113, no comando SQL do relatório de horas trabalhadas, no livro aparece:

1
INNER JOIN users usr ON tl.user_id = tl.id #{user_filter}

o correto seria:

1
INNER JOIN users usr ON tl.user_id = usr.id #{user_filter}

Na página 96, com o código mostrado no livro, o link para registrar horas em um projeto especifico não funciona em algumas situações, no livro o código esta assim:

1
<%= link_to “Registrar Horas”, user_time_logs_path(@current_user, :project_id => project.id) %>

Com a alteração sugerida, ele funciona sempre>

1
<%= link_to “Registrar Horas”, new_user_time_log_path(@current_user, :project_id => project.id) %>

E na edição de tipos de tarefa, na página 101, no método edit, que no livro esta assim:

1
2
3
4
5
6
7
 # GET /task_types/1/edit
 
  def edit
 
     @task_type = TaskType.find(params[:id])
 
  end

Foi preciso adicionar uma linha:

1
2
3
4
5
6
7
8
9
# GET /task_types/1/edit
 
  def edit
 
     @task_type = TaskType.find(params[:id])
 
     @project = @task_type.project
 
  end

E alterar a view de edição de task type, a linha:

1
<% form_for([:project, @task_type]) do |f| %>

Para:

1
<% form_for([@project, @task_type]) do |f| %>

Isto faz a edição de tipos de tarefa funcionar corretamente :D

Desculpem pelo transtorno, se encontrarem mais alguma coisa errada por favor me avisem.

Tags: , , ,

31 Aug 09 Rs On Rails 2009 – Ótimo evento, espero que aconteça novamente em 2010

RS On Rails

O Rs On Rails que aconteceu no fim de semana passado estava excelente, palestras muito boas, a organização estava show debola também.
Tinha mais gente no evento do que eu achei que teria …
Acho que só temos a agradecer ao pessoal da Softa pela realização deste evento …
Infelizmente eu pude ficar pouco tempo por lá, meu filho esta com 3 meses agora e eu só tenho os fins de semana pra babar um pouco com ele acordado, e pra poder ajudar a Aline a cuidar dele (Não basta ser pai, tem que participar) :D
Mas este post tem 5 objetivos:

  1. Agradecer ao pessoal da organização pelo ótimo evento, que ja fiz
  2. Avisar que os slides da minha palestra já estão no SlideShare (embedded abaixo), e que o código esta no GitHub (clique aqui)
  3. Perguntar aos leitores do blog o que acharam da minha palestra, o que preciso melhorar para a próxima :D
  4. Tirar um pouco da poeira do blog, já que não tenho tido muito tempo de escrever por aqui
  5. Lembrar vocês que quem quiser comprar o meu livro com 30% desconto, pode utilizar o código URUBATAN no site da Novatec

Bom, acho que era isto, seguem os slides …

Quaisquer dúvidas é só deixar um comentário por aqui …

PS.: fiz o upload errado como um usuário guest no slideshare também, mas o oficial é o acima que esta na minha conta do slideshare :D

Tags: , , , , , , ,