Bom, este post é mais minha opinião do que qualquer outra coisa, mas o que motivou a escrita dele foi este post no dzone: Will the real programmers please stand up? automated edition, onde o autor diz que apenas 1 em 20 entrevistados por ele conseguiam resolver de forma satisfatória testes simples como:
Escreva em C um método que inverta uma lista encadeada.
Claro que algumas restrições eram aplicadas, a assinatura do método não deveria ser alterada e a estrutura que armazenaria a lista encadeada também não deveria ser alterada. Elas eram apresentadas como o código abaixo:
1 2 3 4 5 6 | typedef struct nodetype { int value; struct nodetype *next; } node; node* reverselist(node*) |
Tentando automatizar um pouco o processo, ele tentou apresentar o código pronto com um bug, para que o bug fosse encontrado, mas o candidato sempre poderia escolher reescrever o código do zero se seguisse as regras acima.
Bom, olhei o código e achei que a recursividade utilizada estava só piorando o algoritmo, ele precisa de duas iterações para inverter a lista, então eu pensei, por que não escrever uma versão sem recursividade e que faça o processo todo em apenas uma iteração?
Bom, a minha resposta para o problema foi esta que esta abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | static class LinkedItem { int value; LinkedItem next; public LinkedItem(int value) { super(); this.value = value; } } private static LinkedItem reverseLinkedList(LinkedItem currentHead) { LinkedItem nextHead = null, previousHead = null; while (currentHead != null) { nextHead = currentHead; currentHead = nextHead.next; nextHead.next = previousHead; previousHead = nextHead; } return nextHead; } |
Eu escrevi em java, mas não faz diferença pois para o algoritmo que escrevi a unica diferença seria a sintaxe.
Para testar isto escrevi um programa simples que inicializa uma lista encadeada com números de 1 a 10 e imprime a lista antes e depois desta ser invertida. O código esta abaixo, se alguem quiser fazer uma versão melhor deste algoritmo por favor poste a sua resposta nos comentários.
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 | package test; public class Main { static class LinkedItem { int value; LinkedItem next; public LinkedItem(int value) { super(); this.value = value; } } private static LinkedItem reverseLinkedList(LinkedItem currentHead) { LinkedItem nextHead = null, previousHead = null; while (currentHead != null) { nextHead = currentHead; currentHead = nextHead.next; nextHead.next = previousHead; previousHead = nextHead; } return nextHead; } public static void main(String[] args) { LinkedItem head = initializeSampleList(); printLinkedList(head); head = reverseLinkedList(head); printLinkedList(head); } private static LinkedItem initializeSampleList() { LinkedItem it = new LinkedItem(1), head = it; for (int i = 2; i < 10; i++) { LinkedItem it2 = new LinkedItem(i); it.next = it2; it = it2; } return head; } private static void printLinkedList(LinkedItem it) { while (it != null) { System.out.printf("%d ", it.value); it = it.next; } System.out.println(); } } |
Eu achei espetacular a idéia deles de criar um site para automatizar este tipo de smoke test para entrevista de emprego de programadores, se o cara não consegue resolver isto deveria estudar um pouco mais antes de querer trabalhar com programação mesmo.
Mas se a idéia for testar a habilidade com algoritmos acredito que o candidato deveria poder escolher em que linguagem implementar, caso contrario, em alguns casos a habilidade com a linguagem estaria sendo testada, e acho que estas duas coisas deveriam ser testadas em separado.
E vocês o que acham sobre isto? Sobre este tipo de testes?
Já fizeram algum teste parecido com este em alguma entrevista de emprego?
E se você é o entrevistador, gostou da idéia deste tipo de teste? Acha util ou desnecessário?
Utiliza alguma técnica alternativa?
Olha o código produzido pelo candidato em projetos open source?
Bom, acho que por enquanto é isto
PS.: me decepcionei com a minha demora para implementar este algoritmo simples, levei uns 10 minutos, quando comecei a escrever achei que seria algo natural e funcionaria de primeira. Isto deve ter sido causado pela facilidade que temos hoje, como um Collections.reverse do java ou um [].reverse do ruby por exemplo. Raramente temos que lidar diretamente com este tipo de algoritmo básico, mas saber implementa-los é extremamente importante, pelo menos na minha opinião
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.
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: agendamento, dicas, rails, Ruby, scheduler

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 ![]()
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
Tags: customize, generator, personalização, produtividade, rails, Ruby, templat
Estou de férias, e eu não ia escrever nada aqui no blog antes de voltar ao trabalho, mas isto merece o post ![]()
A chamada de trabalhos para o Agile Brazil 2010 esta aberta.
Se você tem algo interessante sobre Agile para falar, envie sua proposta de palestra, tutorial ou workshop por este link.
A Agile Brazil 2010 é um evento nacional organizado por representantes das principais comunidades ágeis brasileiras. Junte-se a nós submetendo trabalhos, participando do concurso do logo e divulgando o evento.
Acompanhe as novidades do @agilebrazil pelo Twitter.
O evento vai acontecer em Porto Alegre de 22 a 25 de Junho 2010.
Espero ver alguns de vocês por lá!
Tags: agile, bdd, cucumber, eventos, Java, palestras, rails, Ruby, tdd
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
Primeiro, você vai precisar do Rails 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:
Não vou explicar para que serve cada um deles, mas os mais importantes para este mini tutorial são:
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 configurados, o que pode facilitar bastante a vida, ou complicar as vezes
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:
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
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:
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 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/
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 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: 101, rails, Ruby, tutorial, url shortener
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
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
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
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

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
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:
Behavior Driven Development:
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.
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.
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:
Estas mesmas palavras podem ser traduzidas para o portugues como:
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:
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.
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

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
Finalmente temos uma forma fácil de programar aplicações desktop multi plataforma sem ter que penar na mão do SWING ![]()
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 ![]()
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 ![]()
Recomendo uma olhada …
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á
Tags: palestra, rails, Ruby, semana academica, senac

Bom era isto, ando escrevendo pouco por aqui por que a correria anda grande, tento responder todos os emails
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
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: 2009, jugday, palestra, produtividade, rsjug, Ruby, senac

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
Tags: engine, palestra, plugin, produtividade, rails, reutilização, Ruby, ruby on rails

Quem usa o twitter pode “seguir” o evento, e ficar sabendo das novidades (quando forem postadas
)
E se ja estão no twitter mesmo, não custa nada me seguir também ![]()
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 ![]()
hehehe
É issai ![]()

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
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 ![]()

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 ![]()
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
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: agileweekend, palestra, rails, Ruby
O pessoal do Ruby Brasil fez uma promoção em parceria com a Novatec, quem seguisse o Ruby Brasil no twitter, e mandasse a melhor frase dizendo por que deveria ganhar o meu livro de Ruby On rails, ganharia uma copia do mesmo.
Eu acabei de responder um email dizendo qual era a melhor frase de uma lista (pelo menos na minha opinião). Acho que a que eu apontei vai ganhar o livro, mas eu achei que os autores destas tres mereciam um prêmio, e como a grana anda curta, só vou listar as 3 aqui no blog mesmo e parabenizar os autores (Não tenho os nomes dos autores das frases, então se quiserem se acusar aqui, sem problemas
).
As tres frases estão listadas na ordem em que apareceram na lista, não na minha ordem de preferência por que eu achei as três muito boas, e principalmente criativas.
Novamente, meus parabéns aos autores destas 3 frases.