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

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: , , , ,

08 Jan 10 Utilizando Rake para o Build de projetos Java!

A alguns dias atrás eu li este twitt do Martin Fowler: “you don’t want a build tool which automatically downloads unresolved dependencies before cleaning out yr build output: http://bit.ly/59Rl85“, li todo o post e ele fala de forma bastante prolixa de alguns dos motivos que me fazem não gostar do Maven.

Não me levem a mal, eu já tentei utilizar ele algumas vezes, mas eu não consigo gostar de uma ferramenta que acha que sabe mais do meu projeto do que eu mesmo (ou o cliente, ou os desenvolvedores, …).
Ou pior que isto, uma ferramenta que tem a infeliz mania de tentar fazer um backup da internet antes de cada build só para verificar se tem a última versão das dependências disponível …

Como é citado no post, não acho que alguma ferramenta vá saber exatamente o que é necessário para qualquer projeto, até por que cada projeto é um projeto, e cada projeto tem suas peculiaridades, e eu simplesmente desisti todas as vezes que precisei configurar alguma destas peculiaridades no maven e voltei para o ANT.

O ANT é uma ferramenta bastante flexível, e pelo que eu tenho visto no mercado, fora alguns teimosos que preferem usar o maven mesmo passando muito mais trabalho do que o necessário, o ANT é o “defacto standard” para builds em Java, mas algumas vezes a “linguagem de script” do ANT dificulta as coisas quando se precisa realmente de um script para fazer alguma coisa durante o build, então resolvi usar Ruby para escrever os builds, ou seja, utilizar uma linguagem de scripts de verdade.

Ai pensei, como é que vou fazer para compilar meu projeto java utilizando o Rake? A linguagem de script é muito fodastica, é Ruby, eu me sinto bem programando em Ruby, mas e como compilar?

Fui perguntar ao oraculo e descobri o BuildR e o Raven que fora o fato de não utilizarem XML e sim Ruby, conseguem repetir todos os erros do Maven, eles parecem “ports do Maven para o Rake” e eu não sei por que alguem iria fazer isto, se você gosta tanto assim do Maven, use ele mesmo …

Mas do Rake eu gosto, me acostumei com ele trabalhando com o Rails, é muito fácil de automatizar tarefas relacionadas a um projeto utilizando o Rake, e não apenas o “build”, mas algumas tarefas que as vezes precisam ser automatizadas, como um merge freqüente com algum sub projeto desenvolvido em outra parte do mundo …

Isto me criou apenas um problema, como compilar, empacotar, …

Ou seja, me faziam falta as tasks básicas do ANT que eu utilizo sempre. As outras tarefas são melhor executadas na minha opinião pelo próprio Rake ou até mesmo por um script em Ruby, mas estas tarefas básicas iriam fazer falta, e para resolver isto eu criei uma classe wrapper para os comandos do JDK, que pode ser estendida depois, não é algo 100% rake, mas eu achei que ficou legal assim, se alguem não concordar e tiver idéias para melhorar estou aceitando sugestões :D

O wraper para os comandos do JDK ficou assim:

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
class JavaUtil
  RAW_COMMANDS = %w{appletviewer apt extcheck idlj jar jarsigner java javac javadoc javah javap javaw javaws jconsole jdb jhat jinfo jmap jps jrunscript jstack jstat jstatd jvisualvm keytool kinit klist ktab native2ascii orbd pack200 packager policytool rmic rmid rmiregistry schemagen serialver servertool tnameserv unpack200 wsgen wsimport xjc}
 
  def initialize(jdk_home=nil)
    @commands = {}
    @jdk_home = jdk_home || ENV['java_home']
    @default_for_command = {}
    @global_default = {}
    init_commands
  end
 
  def method_missing(met,*args)
    if RAW_COMMANDS.include? met.to_s
      execute_command met, *args
    else
      super.method_missing met, *args
    end
  end
 
  def respond_to?(met)
    RAW_COMMANDS.include?(met.to_s) || super.respond_to?(met)
  end
 
  def default_parameter(param,value)
    @global_default[param] = value
  end
 
  def default_parameter_for(met,param,value)
    params = @default_for_command[met] || {}
    params[param] = value
    @default_for_command[met] = params
  end
 
  private
    def init_commands
      @jdk_bin = File.join @jdk_home , "bin"
      RAW_COMMANDS.each do |cmd|
        @commands[cmd.to_sym] = File.join @jdk_bin, cmd
      end
    end
 
    def update_or_concat_with_defaults(opts,defaults)
      defaults.each do |key,value|
        param = opts[key]
        if !param
          param = value
        else
          if param.is_a? Array
            param << value
            param.flatten!
          end
        end
        opts[key] = param
      end
    end
 
    def execute_command(cmd, *args)
      actual_command = @commands[cmd.to_sym]
      if args
        opts = {}
        opts.update args.pop if args.last.is_a? Hash
        update_or_concat_with_defaults opts, @global_default
        update_or_concat_with_defaults opts, @default_for_command[cmd.to_sym] if @default_for_command[cmd.to_sym]        
        opts.each do |key, value|
          param = value
          param = param.join File::PATH_SEPARATOR if param.is_a? Array
          actual_command << " -" << key.to_s << " "  << param
        end
        actual_command = "#{actual_command} #{args.join ' '} "
      end
      puts actual_command
      res = %x{#{actual_command}}
      puts res
      [$?,res]
    end
end

A minha idéia dos parâmetros default globais tem um pequeno problema, alguns comandos não recebem os mesmos parâmetros, mas é possível setar parâmetros padrão por comando, o que ficou legal, e deixou a compilação mais limpa …

A classe pode ser utilizada com qualquer JDK, inclusive instâncias diferentes podem utilizar JDKs diferentes para o mesmo build, basta passar o “JAVA_HOME” no construtor, por padrão a variável de ambiente é utilizada …

Mas beleza, como é que eu utilizo esta tranqueira em um Rakefile agora? bom, o meu Rakefile para o projeto de exemplo ficou assim:

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
require 'lib/java_util'
@java_util = JavaUtil.new
 
task :default => :test
 
SRC_FILES = FileList.new 'src/**/*.java'
TST_FILES = FileList.new 'test/**/*.java'
CLASSPATH = FileList.new "#{File.join(ENV['TOMCAT_DIR'], 'lib').gsub /\\/,'/'}/*.jar"
 
@java_util.default_parameter_for :java, :classpath, CLASSPATH.to_a
@java_util.default_parameter_for :javac, :classpath, CLASSPATH.to_a
 
directory 'output/classes'
directory 'output/tests'
 
desc "Compile all the java files"
task :compile => ['output/classes','output/tests']  do
  @java_util.javac SRC_FILES, :d => 'output/classes'
  @java_util.javac TST_FILES, :d => 'output/tests', :classpath => ['output/classes',"#{ENV['JUNIT_DIR']}\\junit-4.4.jar"]
end
 
desc "Creates the package after compilation"
task :package => :compile do
  @java_util.jar '-cf output/target.jar -C output/classes .'
  cp 'output/target.jar', 'WebContent/WEB-INF/lib'
  @java_util.jar '-cf output/target.war -C WebContent .'
end
 
desc "Runs the tests after packaging"
task :test => :package do
  test_classes = FileList.new 'output/tests/**/*.class'
  test_classes.gsub! /output\/tests\/(.*)\.class/,'\1'
  test_classes.gsub! /\//, '.'
  @java_util.java "org.junit.runner.JUnitCore #{test_classes.join ' '}", :classpath => ['output/tests','output/target.jar',"#{ENV['JUNIT_DIR']}\\junit-4.4.jar"]
end
 
desc "Clean up all the mess we created"
task :clean do
  rm_f 'output'
  rm_t 'WebContent/WEB-INF/lib/target.jar'
end

O código dos testes não precisava ser tão complexo, eu poderia ter criado um wrapper para ele, a mesma coisa para a criação do jar, poderia até mesmo ter utilizado o “rubyzip” para deixar mais bonitinho, mas a idéia por enquanto é ser bem simples.

Estou utilizando este build em um projeto, se engrenar provavelmente a biblioteca vá crescendo, mas acho que por agora já serve para começar a brincar e ver o que vocês acham da idéia.
A classe “JavaUtil” precisa ser mais testável, mas isto tornou ela complexa demais para o exemplo deste post, se eu convencer o resto da equipe a continuar usando esta solução vou melhorando ela aos poucos :D

Acho que vou separar a montagem do comando e a execução do mesmo, ou transformar cada comando em uma classe para facilitar a expansão da biblioteca e tornar mais testável, ou até mesmo as duas coisas.
No momento a classe não é nada testável, mas já esta divertida e o meu bluid diminuiu muitas linhas depois que eu converti ele de ANT para Rake utilizando esta lib :D

PS.: quem quiser pegar o projeto de testes para brincar, só para olhar ou até mesmo para implementar algumas melhorias, ele esta publicado no github. Se implementarem alguma melhoria, não esqueçam de enviar um pull request para que eu possa fazer o merge das alterações :D

Tags: , , , ,

06 Jan 10 Cucumber e BDD – Vantagens para a empresa (Argumentos para o gerente, para o arquiteto, para o presidente da empresa, …)

Desenvolvimento Guiado pelo Comportamento (BDD – Behaviour Driven Development)

Desenvolvimento guiado pelo comportamento da aplicação é o que todos deveriam fazer sempre, de forma bastante resumida é definir com o cliente como a aplicação deve se comportar, escrever um teste automatizado para verificar este comportamento e depois escrever código suficiente para fazer o comportamento da aplicação ficar de acordo com o que o cliente deseja.

A diferença básica do Behaviour Driven Development (de agora em diante simplesmente BDD) para o desenvolvimento orientado por testes, é que o BDD coloca em foco o valor para o negócio que o software vai adicionar. Parece ser uma diferença puramente conceitual mas não é, por exemplo:
A tela inicial deve listar todos os clientes
Não é a mesma coisa que
O método HomeController.index precisa popular a variável @clientes

  1. O primeiro é inteligivel para um leigo, o segundo é especifico para um sistema e apenas um programador entende
  2. O primeiro é um exemplo de uma definição de comportamento de uma tela, uma coisa que um cliente poderia dizer.
  3. O segundo é um exemplo de como um programador poderia ler uma linha de código fonte.

Claro que não são só estas diferenças, trabalhando mais com BDD percebe-se que ele poupa muito mais trabalho do que o TDD simples (Não que TDD seja simples de se adotar). BDD tem todas as vantagens de TDD e mais algumas, veja as duas listas abaixo:

Test Driven Development:

  1. O código gerado tem menor acomplamento e maior coesão.
  2. O código gerado tem uma maior qualidade por ser quase 100% testado.
  3. Refatoramentos podem ser feitos sem medo pois qualquer problema sera detectado pelos testes.
  4. É possível saber claramente quando uma tarefa foi concluida, pois o teste correspondente esta passando.
  5. Testes de regressão automatizados existem sem nenhum esforço adicional.
  6. A maior parte dos bugs é encontrada mais cedo o que torna mais barato corrigi-los.

Behavior Driven Development:

  1. Todos os anteriores
  2. Aumenta a integração entre o cliente, os testadores e os desenvolvedores pois todos falam a mesma lingua.
  3. Mesmo quando testadores e desenvolvedores são equipes diferentes eles podem trabalhar juntos para definir o design do que vai ser feito, escrever User Stories é uma ótima forma de fazer isto, pode ser feito com a ajuda do usuário ou pelo menos validada com o usuário que vai entender o que esta escrito.
  4. User Stories servem como Test Case, Código do teste automatizado, e Design tudo junto.
  5. As User Stories se tornam testes executáveis, o que quer dizer que o usuário pode escrever o código dos testes de aceitação (OK, isto é bem pouco provável, mas ele pode pelo menos ler)

Ou seja, alem de gerar um código com mais qualidade, o BDD poupa trabalho de toda a equipe e o principal, melhora a comunicação, que tanto para quem trabalha com metodologias ágeis quanto para quem não trabalha é extremamente importante e a falta dela é um problema gravíssimo que leva diversos projetos ao fracasso. Na minha opinião, só o fato de melhorar a comunicação já é motivo o suficiente para testar BDD.

Claro que alguns dos benefícios que eu citei dependem do suporte de ferramentas, para ser mais preciso, as User Stories serem o código executável dos testes de aceitação depende de uma ferramenta para ser verdade, e a ferramenta que eu escolhi para isto é o Cucumber, do qual eu vou começar a falar com mais freqüência aqui no blog.

Eu pessoalmente encaro o BDD como uma evolução do TDD, eu sempre tive dificuldades em escrever testes unitários antes do código da aplicação, claro que para bibliotecas é fácil, mas para a interface com o usuário que consome boa parte do código da aplicação é bem complicado escrever testes estilo XUnit, mas quando as user stories se tornaram o código executável dos testes de aceitação da UI (pelo menos os básicos desconsiderando o layout) um novo mundo se abriu para mim e tudo passou a fazer sentido.

Cucumber – Quem disse que pepinos seriam um problema?

para quem não percebeu este sub-titulo é uma brincadeira com a tradução da palavra cucumber que quer dizer pepino

Cucumber é uma ferramenta que torna possível executar histórias em texto puro, ele é uma ferramenta escrita em Ruby que veio para substituir o RSpec Story Runner, e tem diversas vantagens sobre este.

O Cucumber, utiliza Ruby e expressões regulares para definir o que qualquer expressão quer dizer, mas antes de entrar nestes pormenores vamos entender um pouquinho da estrutura básica que o Cucumber define para as User Stories.

Para que seja possível executar uma User Storie utilizando o Cucumber, ela precisa ter uma estrutura básica.

  1. Para o cucumber, todas as User Stories referentes a uma funcionalidade do sistema estarão agrupadas em um arquivo com a extensão .feature
  2. No início de cada arquivo existe um resumo da funcionalidade com um formato bem simples: um título, qual o problema a ser resolvido, qual ator trabalha nesta história e qual o resultado desejado.
  3. Logo depois são definidos os cenários, que são as histórias em si, cada arquivo tem pelo menos um cenário.
  4. Cada história, ou cenário é composto por uma descrição ou título, uma ou mais pré condições, uma ou mais ações e uma ou mais verificações.

Esta é uma forma de definir a estrutura básica de um arquivo .feature do Cucumber, claro que explicando desta forma estas definições se encaixam em muita coisa, então vamos ser um pouco mais especificos.

O Cucumber define algumas palavras chave para cada uma destas sessões, estas palavras chave podem ser traduzidas para diversas linguas, o primeiro exemplo eu vou colocar em ingês, depois vou mostrar o equivalente em portugues, mais adiante quando entrarmos na parte de configurações do cucumber vamos ver melhor como selecionar a lingua utilizada, e como customizar isto, mas por enquanto fiquemos com as configurações padrão.

	Feature: Simple math
		In order to avoid silly mistakes
		As a math idiot
		I want to be told the result of simple math operations

		Scenario: adition
			Given I have entered 50 into the calculator
			And I have entered 70 into the calculator
			When I press add
			Then I should see 120 on the screen

		Scenario: subtraction
			Given I have entered 60 into the calculator
			And I have entered 30 into the calculator
			When I press sub
			Then I should see 30 on the screen

Este é o exemplo de uma história bem simples que o cucumber pode interpretar, as palavras chave apresentadas são:

  • Feature
  • Scenario
  • Given
  • And
  • When
  • Then

Estas mesmas palavras podem ser traduzidas para o portugues como:

  • Funcionalidade
  • Cenário
  • Dado
  • E
  • Quando
  • Então

Utilizando estas palavras chave, seria possível escrever esta história assim em portugues:

	Funcionalidade: Matemática Simples
		Para evitar erros idiotas
		Como um completo ignorante em matemática
		Eu quero que operações simples de matemática sejam resolvidas para mim

		Cenário: adição
			Dado que eu digite 50 na calculadora
			E que eu digite 70 na calculadora
			Quando eu precionar "Adicione"
			Então eu devo ver 120 na tela

		Cenário: subtração
			Dado que eu digite 60 na calculadora
			E que eu digite 30 na calculadora
			Quando eu precionar "Subtraia"
			Então eu devo ver 30 na tela

O legal é que esta histórinha poderia ser escrita por um usuário, as únicas regras reais são:

  1. Começar as frases com as palavras chave definidas
  2. Tentar utilizar as mesmas frases sempre que possível, isto vai facilitar na tradução do dialeto utilizado para Ruby

A estrutura que utilizei na descrição da funcionalidade não esta descrita em palavras chave por que ela não é interpretada pelo cucumber, é apenas uma descrição e o formato pode variar um pouco.

Mas por que utilizar esta ferramenta para testes em vez de qualquer outra?
Por que utilizar o cucumber como ferramenta de testes vai viabilizar uma abordagem BDD no desenvolvimento do seu sistema, e que isto vai te poupar muito dinheiro.

Seguem agora alguns argumentos (fora os que você pode retirar do texto)

  • Presidente da empresa
    • Esta metodologia de desenvolvimento em conjunto com as ferramentas corretas vão poupar bastante dinheiro no desenvolvimento de sistemas
  • Gerente
    • A integração entre as equipes de desenvolvimento e os clientes vai melhorar muito, isto vai fazer com que os clientes fiquem mais felizes com as entregas, possam acompanhar o progresso do desenvolvimento e entendam se o que esta sendo testado é o que realmente importa para eles melhorando a qualidade do que é entregue
  • Arquiteto
    • Esta metodologia vai melhorar o entendimento da equipe sobre o que deve ser desenvolvido
  • Desenvolvedores e Testadores
    • É legal trabalhar desta forma, e você vai trabalhar menos no final das contas o que é bom e ermite que você exercite a sua preguiça :D

Acho que era isto, falei um pouco de BDD, um monte de Cucumber e acho que consegui mostrar a idéia geral, mas isto vai ficar um pouco mais claro nos próximos posts sobre o Cucumber.
PS.: parabéns pela paciência se você leu até aqui :D

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: , , , ,

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: , , , , , , ,

04 Jul 09 RS On Rails – Muita produtividade para um evento só :D

RS On Rails

É isso ai pessoal :D (Como dizia o perna longa no final dos desenhos).
Sábado, dia 29 de agosto de 2009 das 8h as 20h vai acontecer o 1o RS On Rails, no Centro de Eventos da PUCRS – Sala 601
A grade de palestras esta bem interessante, incluindo uma palestra deste que vos escreve :D
Deem uma olhadinha rápida na grade atual …

  • CouchDB vs Postgres em Rails – Diogo Biazus e Johalf Farina
  • TDD no Rails: Ferramentas, técnicas e experiências – Jony dos Santos Kostetzer
  • Introdução a Ruby on Rails – Juan Maiz Lulkin Flores da Cunha
  • Olá mundo OpenSocial! – O Tutorial bem rápido usando Sinatra – José Peleteiro
  • Segurança em Ruby on Rails – Marcelo Castellani
  • Câmara Municipal nos Trilhos – Márcia Almeida e Henrique Testa
  • Reutilização de código em aplicações Rails: Plugins, Gem e Engines – Rodrigo Urubatan Ferreira Jardim

Quem usa o twitter pode “seguir” o evento, e ficar sabendo das novidades (quando forem postadas :D )
E se ja estão no twitter mesmo, não custa nada me seguir também :D
Devemos agradecer ao pessoal do grupo Rails-RS e a Softa pela organização do evento, na minha temporada na organização do RSJUG tive certeza de que organizar um evento da muito trabalho mesmo.
Mas acho que era isto, propaganda do evento feita, poeira do blog sacudida.
Espero ver vocês no evento, vai estar muito legal.

PS.: Gostaria de agradecer ao Carlos Brando pelo meu novo apelido: Rodrigo Urubatan “Nome Comprido” Ferreira Jardim.
É por estas e outras que eu prefiro que me chamem só de “Urubatan”, até por que não tem tantas pessoas assim com Urubatan no nome por ai :D
hehehe

Tags: , , ,

25 Jun 09 Um especialista precisa saber um pouco de cada coisa

Para quem acha que o título deste post esta contraditório, lamento informar, mas você esta completamente equivocado.
Você conhece algum especialista? De preferência algum que esteja ai pertinho de você.
Se conhece por exemplo um especialista em Java ou .NET, chega pra ele e pergunta se ele conhece algum dos seguintes assuntos:

  • XML
  • Expressões Regulares
  • HTML
  • XHTML
  • Javascript
  • Modelos de Threading
  • Como funciona uma CPU
  • Para que serve um sistema operacional
  • O que é e para que serve uma “Maquina Virtual”
  • Flash
  • XSD
  • XPath
  • SQL
  • Estrutura de bancos de dados
  • TCP/IP
  • Sockets

Acredito que a resposta vai ser sim para todos, ou pelo menos a grande maior parte destes itens. E isto são só coisas genéricas, imagina se começarmos a detalhar a sopa de letrinhas existente no mundo Java EE ou no .NET.
Pois é mais ou menos isto que estou querendo dizer, um especialista precisa saber um monte de coisas para se tornar um especialista em uma delas.
A forma mais fácil que eu conheço para melhorar muito e muito rápido a qualidade do código que você escreve em uma linguagem é aprendendo outra linguagem de programação.
Tem gente que diz que o ideal é aprender uma linguagem nova por ano, e com certeza, o período da minha vida profissional que eu mais melhorei foi quando aprendi várias linguagens em um período curto de tempo.
Quando eu era mais novo (coisa de velho escrever isto :D ) o meu chefe na época disse que um especialista é alguem que sabe cada vez mais sobre cada vez menos, e que um super especialista é alguem que sabe absolutamente tudo sobre absolutamente nada …
Ach oque este conceito esta um pouco desatualizado, até por que por este conceito, um super especialista é o cara que sabe absolutamente tudo sobre absolutamente nada.

Pelo menos na minha opinião, eu espero que um especialista em Java por exemplo, consiga criar um pacote EAR padrão Java EE para uma aplicação composta por dois módulos web e três módulos EJB além de algumas bibliotecas utilizadas por todos os módulos.
Para fazer isto, o cara vai ter que conhecer no mínimo muito XML, vai ter que saber o que são meta dados, vai ter que saber quais meta dados foram definidos via anotações no código e quais ele vai querer sobre escrever com XML. Vai ter que conhecer a estrutura de um arquivo EAR, a estrutura de um arquivo WAR e qual a diferença entre um arquivo jar de uma biblioteca e de um módulo EJB.
Para entender direito o que ele ta fazendo, ele vai ter que conhecer o protocolo HTTP, por conseqüência o protocolo TCP e o IP. Além de precisar entender de RMI que é utilizado para chamada dos EJBs, RMI também funciona sobre TCP.
Se o servidor for rodar em cluster, é necessário saber como este cluster esta configurado, a maior parte dos servidores Java EE utiliza o protocolo IIOP/IP, o mesmo do corba, já que pela especificação Java EE todo EJB pode ser chamado utilizando CORBA também, e que o IIOP/IP permite roteamento muito mais fácil do que o RMI direto.
E isto tudo só para começar.
Se o especialista em java precisar também configurar o servidor de aplicações também ai aumenta bastante a quantidade de coisas que ele vai ter que saber só para poder ser chamado de especialista em Java e nem chegamos na parte de desenvolvimento ainda …
Claro que isto ainda é só a minha opinião, mas para ser um especialista em java, o cara tem que saber muito bem Orientação a Objetos, Reflexão, Refactoring e mais Refactoring, AOP, a diferença entre excessões checadas e não checadas, para que serve cada tipo de collection, todas as classes no mínimo dos pacotes java.lang e java.util e mais um monte de outras coisas.

Só para finalizar.
Vocês não vão conseguir se tornar especialistas em nada da noite para o dia. Isto vai demorar bastante, e mesmo que você queira ser especialista em .NET por exemplo, você vai ter que estudar muitas outras coisas.
A pior coisa que tem é programador bitolado que acha que a única linguagem/ferramenta/time/religião que presta é a que ele conhece agora …
(isto foi um misto de dicas com desabafo :D )

Tags: , ,

11 May 09 Mensagens de erro são feias mas não mentem (nem mordem)

Já sou desenvolvedor a algum tempo (comecei em 1997, façam as contas se quiserem :D ), e uma das coisas mais importantes que aprendi até hoje é com certeza que todas as mensagens de erro geradas por linguagens de programação, frameworks, e assemelhados, são realmente feias.
Os Stack Traces do Java são realmente muito feios, chegam a assustar quem esta começando, os do Ruby não são muito melhores.
Em C++ não tem stack traces, mas os memory dumps fazem um papel parecido, e memory dumps podem ser conseguidos a partir de qualquer linguagem compilada.
O C# tem stack traces também, bem próximos do Java, acredito que isto seja parte do .NET e não uma particularidade do C#, mas eu conheço muito pouco de .Net, então agradeço se alguem puder confirmar isto.

Outra coisa bastante importante, e uma verdade absoluta, regra inquebrável, e como tal, tem pouquíssimas exceções, é que o código que você vai escrever não vai funcionar de primeira, você não é perfeito, e você vai cometer erros.
Pode acontecer de uma ou duas vezes durante a sua vida, você conseguir testar alguma coisa e esta coisa funcionar de primeira, mas eu não faria com que a minha felicidade dependesse disto, por que esta é uma situação bastante incomum.

Beleza, e o que uma coisa tem a ver com a outra?
Se você vai cometer erros, você vai precisar descobrir o que você fez de errado, e muitas das vezes, isto não vai ser fácil, e o seu melhor amigo para esta situação, a melhor ajuda que você vai conseguir, não vai ser do seu colega do lado, por mais Nerd que ele seja, vai ser a mensagem de erro/stack trace/memory dump que vai salvar a sua pele nesta situação.
Se o seu colega Nerd for te ajudar, provavelmente, ele vai perguntar: “Qual foi o erro?”
E se preste atenção nesta listinha de respostas:

  • Não sei.
  • Ah, apareceu um stack trace aqui, mas eu já apaguei.
  • Foi um erro estranho, acontece de vez em quando, mas nunca prestei atenção na mensagem.
  • Não lembro se foi null pointer ou out of memory, mas tu pode me ajudar aqui?
  • Ahh, vem aqui ver, eu não sei ler esta mensagem.

Estas são resposta inválidas, e provavelmente vão fazer o seu colega, que poderia te ajudar, ficar bastante chateado, e te ajudar com má vontade.
Para resolver este problema, você precisa aprender a ler estas mensagens de erro, isto vai te poupar muito tempo, e tudo o que poupa tempo, acaba te tornando mais produtivo, se tu for mais produtivo, o teu chefe vai gostar mais de ti, e tu vai ganhar mais, se tu for mais produtivo, tu vai terminar o que tem que fazer mais rápido, e por conseqüência, vai pra casa mais cedo :D

A leitura de mensagens de erro, seja qual for a encarnação, requer quatro coisas:

  1. Conhecimento básico do código da aplicação, ou conhecimento profundo da linguagem/framework utilizado (o primeiro é mais fácil de conseguir, mas não tenho como ajudar muito)
  2. Um pouquinho de técnica – nisto eu posso dar uma ajudinha, inclusive é esta a idéia deste post
  3. Conhecimento no mínimo básico de inglês – posso fornecer alguns links para ajudar, mas se você realmente quer continuar trabalhando com desenvolvimento de sistemas, você precisa aprender, no mínimo a lêr em inglês.
  4. Que o stack seja impresso na tela, em arquivo, mostrado em um dialog, ou qualquer coisa que facilite a visualização, sugiro logar todos os stack traces

Técnica básica para leitura de stack traces no Java

Algumas vezes, apenas ler a mensagem ja resolve o problema, como neste exemplo que peguei por ai na web:

INFO 13:37:20 [org.hibernate.connection.ConnectionProviderFactory] - Initializing connection provider: org.springframework.orm.hibernate3.LocalDataSourceConnectionProvider
WARN 13:37:41 [org.hibernate.util.JDBCExceptionReporter] - SQL Error: 17002, SQLState: null
ERROR 13:37:41 [org.hibernate.util.JDBCExceptionReporter] - Io exception: The Network Adapter could not establish the connection
WARN 13:37:41 [org.hibernate.cfg.SettingsFactory] - Could not obtain connection metadata
java.sql.SQLException: Io exception: The Network Adapter could not establish the connection
 at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:125)
 at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:162)
 at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:274)
 at oracle.jdbc.driver.T4CConnection.logon(T4CConnection.java:328)
 at oracle.jdbc.driver.PhysicalConnection.(PhysicalConnection.java:361)
 at oracle.jdbc.driver.T4CConnection.(T4CConnection.java:151)
 at oracle.jdbc.driver.T4CDriverExtension.getConnection(T4CDriverExtension.java:32)
 at oracle.jdbc.driver.OracleDriver.connect(OracleDriver.java:595)
 at java.sql.DriverManager.getConnection(DriverManager.java:525)
 at java.sql.DriverManager.getConnection(DriverManager.java:140)
 at org.springframework.jdbc.datasource.DriverManagerDataSource.getConnectionFromDriverManager(DriverManagerDataSource.java:291)
 at org.springframework.jdbc.datasource.DriverManagerDataSource.getConnectionFromDriverManager(DriverManagerDataSource.java:277)
 at org.springframework.jdbc.datasource.DriverManagerDataSource.getConnectionFromDriverManager(DriverManagerDataSource.java:259)
 at org.springframework.jdbc.datasource.DriverManagerDataSource.getConnection(DriverManagerDataSource.java:241)
 at org.springframework.orm.hibernate3.LocalDataSourceConnectionProvider.getConnection(LocalDataSourceConnectionProvider.java:80)
 at org.hibernate.cfg.SettingsFactory.buildSettings(SettingsFactory.java:72)
 at org.hibernate.cfg.Configuration.buildSettings(Configuration.java:1859)
 at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1152)
 at org.springframework.orm.hibernate3.LocalSessionFactoryBean.newSessionFactory(LocalSessionFactoryBean.java:800)
 at org.springframework.orm.hibernate3.LocalSessionFactoryBean.afterPropertiesSet(LocalSessionFactoryBean.java:726)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1059)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:363)
 at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:226)
 at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:147)
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:269)
 at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:320)
 at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:87)
 at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:72)
 at org.springframework.test.AbstractSpringContextTests.loadContextLocations(AbstractSpringContextTests.java:121)
 at org.springframework.test.AbstractDependencyInjectionSpringContextTests.loadContextLocations(AbstractDependencyInjectionSpringContextTests.java:210)
 at org.springframework.test.AbstractSpringContextTests.getContext(AbstractSpringContextTests.java:101)
 at org.springframework.test.AbstractDependencyInjectionSpringContextTests.setUp(AbstractDependencyInjectionSpringContextTests.java:178)
 at junit.framework.TestCase.runBare(TestCase.java:125)
 at junit.framework.TestResult$1.protect(TestResult.java:106)
 at junit.framework.TestResult.runProtected(TestResult.java:124)
 at junit.framework.TestResult.run(TestResult.java:109)
 at junit.framework.TestCase.run(TestCase.java:118)
 at junit.framework.TestSuite.runTest(TestSuite.java:208)
 at junit.framework.TestSuite.run(TestSuite.java:203)
 at org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run(JUnit3TestReference.java:128)
 at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)

Como pode ser visto na linha 5, os stack traces no Java, começam sempre pelo nome completo da classe da última exceção gerada, seguida imediatamente pela mensagem de erro, separadas por “:”.
No caso deste exemplo, precisamos apenas acreditar que não foi possível conectar no banco de dados, ocorreu algum problema de rede.
Isto nos leva a um ponto que você vai descobrir sozinho quando trabalhar com oracle por um tempo, eles não ajudam muito a descobrir qual o problema :D
Acredito que este erro tenha ocorrido por problemas de configuração da conexão com o banco de dados ou então problemas com o banco de dados real …
Mas este stack esta aqui só pra eu poder reclamar um pouquinho da Oracle :D
Não serve como um exemplo do que eu quero mostrar para vocês (que tiveram paciência de ler até aqui);
Vejam este outro stack que eu gerei de propósito como exemplo:

Exception in thread "main" java.lang.NullPointerException
	at java.io.File.(File.java:222)
	at utils.urubatan.StackTraceReadingExample.readConfigurationFromFileName(StackTraceReadingExample.java:29)
	at utils.urubatan.StackTraceReadingExample.readConfiguration(StackTraceReadingExample.java:25)
	at utils.urubatan.StackTraceReadingExample.verifyConfiguration(StackTraceReadingExample.java:21)
	at utils.urubatan.StackTraceReadingExample.connectAndExecuteQuery(StackTraceReadingExample.java:17)
	at utils.urubatan.StackTraceReadingExample.main(StackTraceReadingExample.java:11)

Na linha 1, já temos um erro bastante comum, e se você ler isto, olhar para o seu colega do lado, e reclamar que o seu codigo gera um NullPointerException sem dizer o que esta acontecendo, por favor, desista de programar agora, antes que você fique realmente frustrado, ou se for muito insistente, coloque o seu amigo na cadeira de um psicólogo achando que trabalha com retardados :D
Este stack é até bem fácil, e serve para demonstrar o que eu quero …
Para ler um Stack trace, comece a ler de traz para frente, ou seja, leia normalmente de cima para baixo, e pare de ler na primeira linha em que o nome da classe pertencer ao seu projeto.
Neste caso, isto ocorre na linha 3 do stack trace: utils.urubatan.StackTraceReadingExample.readConfigurationFromFileName(StackTraceReadingExample.java:29)
Onde podemos ver que o erro esta sendo gerado no método “readConfigurationFromFileName”, da classe “StackTraceReadingExample”, na linha 29 do arquivo “StackTraceReadingExample.java”, ou seja, para corrigir o problema vamos para esta linha ver o que acontece lá, segue o código do exemplo:

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
package utils.urubatan;
 
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
 
public class StackTraceReadingExample {
	public static void main(String[] args) throws IOException {
		StackTraceReadingExample ex = new StackTraceReadingExample();
		ex.connectAndExecuteQuery();
	}
	private String fileName;
	private Properties configuration;
 
	private void connectAndExecuteQuery() throws IOException {
		verifyConfiguration();
	}
 
	private void verifyConfiguration() throws IOException {
		readConfiguration();
	}
 
	private void readConfiguration() throws IOException {
		readConfigurationFromFileName(fileName);
	}
 
	private void readConfigurationFromFileName(String theFileName) throws IOException {
		FileReader fr = new FileReader(new File(theFileName));
		createEmptyConfigurationIfNeeded();
		configuration.load(fr);
		fr.close();
	}
 
	private void createEmptyConfigurationIfNeeded() {
		if (configuration == null) {
			configuration = new Properties();
		}
	}
}

Na linha informada, a única variável que esta sendo utilizada é o nome do arquivo, que se formos ler o código, realmente nunca foi inicializado, para resolver este problema, basta que alteremos a linha 13 para inicializar a variável para algum nome de arquivo, vou adicionar: = “teste.config” e vamos ver o que acontece.

Depois desta alteração continuamos com um erro, e este stack continua bastante simples, mas é um pouco mais complicado que o anterior:

Exception in thread "main" java.io.FileNotFoundException: teste.config (The system cannot find the file specified)
	at java.io.FileInputStream.open(Native Method)
	at java.io.FileInputStream.(FileInputStream.java:106)
	at java.io.FileReader.(FileReader.java:55)
	at utils.urubatan.StackTraceReadingExample.readConfigurationFromFileName(StackTraceReadingExample.java:29)
	at utils.urubatan.StackTraceReadingExample.readConfiguration(StackTraceReadingExample.java:25)
	at utils.urubatan.StackTraceReadingExample.verifyConfiguration(StackTraceReadingExample.java:21)
	at utils.urubatan.StackTraceReadingExample.connectAndExecuteQuery(StackTraceReadingExample.java:17)
	at utils.urubatan.StackTraceReadingExample.main(StackTraceReadingExample.java:11)

Agora o erro esta na 5a linha do stack, que é a primeira linha com código fonte da aplicação …
Esta é a técnica básica para ler stack traces:

  1. Leia a mensagem de erro na primeira linha, as vezes ela pode te ajudar, se a mensagem não ajudar, você pode anotar o nome da classe do erro, que algumas vezes também já é informação o suficiente
  2. Logo em sequida, leia o stack de traz para frente (que é a mesma coisa que de cima para baixo no Java) e procure a primeira linha de código da tua aplicação, ou do framework que você esta utilizando, dependendo do caso e do erro que esta acontecendo, assim você acabou de localizar a linha de código que esta gerando o erro
  3. Se isto ainda não for o suficiente (muitas vezes não é), seguindo pelo Stack, mais para baixo, você pode literalmente voltar no tempo e saber quem chamou aquele método até o início dos tempos
  4. Se apenas ler o código não resolver, utilize a informação obtida no passo anterior, para saber onde colocar o “break point” e utilize o debugger da sua IDE para encontrar o problema, ou pelo menos a raiz do problema

Com estes passos, os stack traces vão te ajudar bastante, algumas IDEs como o Eclipse por exemplo, imprimem os stack clicaveis no console, ou seja, você clica em uma linha do stack trace, e o eclipse abre o arquivo, na linha em que o erro ocorreu.

Estes passos servem também para builds ANT, para programas escritos em action script (Flash ou Flex), para programas escritos em Ruby, incluindo o Rails.
E com pequenas adaptações, funciona também para C++, C, qualquer outra linguagem que gere algo parecido com um stack trace.

Agora uma perguntinha, só pra não perder o costume, você que leu até aqui, acha que valeu a pena a leitura? tem algum colega que você gostaria de poder obrigar a ler isto? ou tem algum exemplo que não se enquadra no que eu escrevi?
Eu tenho alguns amigos que eu gostaria de obrigar a ler isto, ou então abrir a cabeça e jogar isto para dentro, mas infelizmente eu não posso fazer isto.
Se você acha que o texto ficou bom, indique a leitura, se acha que precisa melhorar alguma coisa, deixe nos comentários que eu incorporo a melhoria no texto do post :D

PS.: este versionamento de posts do WP até que é legal, apaguei tudo sem querer, e acho que consegui recuperar legal com ele :D

Tags:

11 May 09 Até propaganda internacional tem meu livro agora :D

É issai :D

rubylearning

Em parceria com o pessoal do RubyLearning.org, e com a Novatec, estamos fazendo mais um sorteio do melhor livro sobre Ruby on Rails em português que eu já escrevi até hoje :D
O RubyLearning é um dos maiores e melhores sites disponíveis hoje para quem quer aprender Ruby, e o meu livro é uma ótima forma de se aprender Rails (pelo menos eu acho que é, e pelos reviews publicados, tem mais gente que concorda :D )

Quem quiser participar do sorteio, é só se cadastrar neste link, e informar o código BPCE101.

E tem também desconto para quem não ganhar o livro sorteado. Informações sobre o desconto de 30% no mesmo link, ou então na home do meu blog, logo abaixo da capa do livro :D

Bom, espero que gostem de mais esta promoção, que gostem do desconto, e que comprem muitas copias do meu livro, pra quem estiver com preguiça de acessar o blog para pegar o código de desconto, segue o banner :D




PS.: eu sei que só tenho falado do livro aqui, mas prometo que esta semana vou publicar uns posts muito legais, e que não vão ter nada a ver com o livro …

Tags: , , ,

08 May 09 Fotos do Porto Alegre Agile Weekend 2009

Foram publicadas as fotos do Porto Alegre Agile Weekend 2009.
Tem até algumas fotos do gordo que vos escreve palestrando :D
Aqui, aqui e aqui.

Eu só não sei quem foi o fotografo, que quase não tirou fotos das moças da recepção :D
Bom, era isto, falta do que escrever é algo complicado :D

Tags: ,

25 Apr 09 Porto Alegre Agileweekend 2009 – Slides e código fonte

Hoje a tarde eu estive no Porto Alegre Agile Weekend 2009 apresentando a palestra “Implementando Com Rails As Histórias Dos Usuários”.
Tinha pouca gente assistindo a minha palestra, mas a palestra estava bem legal, o pessoal que estava por ali fez diversas perguntas e acho que aproveitaram bastante da palestra.
Eu falei um pouco de Rails, um pouco de TDD, um pouco de BDD e bastante do Cucumber e do Webrat.
Se não estou enganado, o pessoal da TV Software livre gravou as palestras, se eles gravaram mesmo, então as palestras devem ser liberadas pela web em algum momento :D
Mas enquanto elas não são liberadas, eu coloquei os slides da palestra no Slide Share, e o código fonte no GitHub.
Os slides estão abaixo.

O projeto no github ta aqui.
Se tiverem dúvidas é só deixar um comentário aqui :D

Eu fiz algumas referências ao meu livro durante a palestra, quem quiser comprar ele, tem links para as lojas virtuais que estão vendendo o livro na página do livro.

Tags: , , ,