Importando e exportando suas gems com Rubygems Snapshot 2

Posted by Roger Leite on dezembro 22, 2009

Final de ano está rendendo.
O rubygems_snapshot nasceu da necessidade de “migrar” as gems instaladas de uma máquina para outra, aliado ao rvm (veja este post-guia-rápido), permite mudar e/ou criar diferentes ambientes em minutos. Assim você pode fugir do famoso “gem hell”.

Veja como é difícil usar:

Instalação:

sudo gem install rubygems_snapshot

Para exportar as gems instaladas:

gem snapshot export projeto-exemplo.yml

Supondo que esteja em outra máquina, para importar as gems, use:

[sudo] gem snapshot import projeto-exemplo.yml

Afinal, o que tem de legal nisso?

Vamos supor que você acaba de entrar numa nova equipe e tem que montar o ambiente de desenvolvimento (por sinal, um ambiente complicado de configurar). O gem snapshot aliada ao rvm, foi feito para facilitar isto, vamos a um exemplo rápido:

Com o rvm, você pode criar um “novo ambiente”:

rvm use 1.8.7%projeto_exemplo
gem list

Deve retornar vazio.

gem install rubygems_snapshot
gem snapshot import projeto-exemplo.yml

Instalará as gems necessárias para o projeto e pronto!

ToDo:

Esta é uma versão bem básica, onde o “import” somente lê as gem e version e manda instalar sem requerir dependências. Está previsto de colocar um aviso no final das gems que deram erro, geralmente devido a dependências de “build nativos”, mas por enquanto estamos usando aqui na equipe com sucesso.

Como faço para criar um rubygems plugin também?

Bom, logo de cara posso te garantir que não é difícil (apesar da pouca documentação na internet), mas deixarei os detalhes para um outro post. Por enquanto, a minha recomendação é: clone o projeto, analise os dois rb do projeto :D e crie o seu!

Caso algum corajoso for usar, estou a disposição para ajudar, é só deixar um comentário aê!
Valeu e sucesso!

Ruby, Rubygems e $LOAD_PATH ou Como funciona o require de gems

Posted by Roger Leite on dezembro 17, 2009

Na madrugada passada, andei “brincando” com o fonte do Rubygems. Logo de cara posso te dizer que não consegui fazer o que queria, e pra amenizar o sentimento de “perda de tempo”, resolvi postar alguns truques aprendidos.

Baixei o fonte do rubygems, como faço pra rodá-lo sem alterar o meu sistema?

Foi a primeira pergunta que fiz. Percebi que com o google não iria encontrar a resposta, mas consegui uma dica importante: $LOAD_PATH.

$ irb
irb(main):001:0> $LOAD_PATH

No meu Ubuntu, obtive:

["/usr/local/lib/site_ruby/1.8", "/usr/local/lib/site_ruby/1.8/i486-linux", "/usr/local/lib/site_ruby/1.8/i386-linux", "/usr/local/lib/site_ruby", "/usr/lib/ruby/vendor_ruby/1.8", "/usr/lib/ruby/vendor_ruby/1.8/i486-linux", "/usr/lib/ruby/vendor_ruby", "/usr/lib/ruby/1.8", "/usr/lib/ruby/1.8/i486-linux", "/usr/lib/ruby/1.8/i386-linux", "."]
$ ls /usr/local/lib/site_ruby/1.8/

Exatamente nesta pasta que se encontra o rubygems.rb. Bingo!
Para rodar o fonte do rubygems, só é necessário adicionar ao $LOAD_PATH a pasta lib do projeto. Dado que estou na raiz do projeto rubygems baixado, execute:

~/rubygems$ ruby -I $PWD/lib ./bin/gem -v

O paramêtro -I permite adicionar diretório ao $LOAD_PATH. Simples e prático. Primeiro problema resolvido, comecei a programar.

Afinal, como funciona o “require de gems”?

Bom, já sabemos que o require “rubygems” fuciona pois encontra-se no $LOAD_PATH do ruby, no caso do meu Ubuntu em “/usr/local/lib/site_ruby/1.8″.

Basicamente (e muito), o Rubygems faz duas coisas no Kernel do Ruby.

  • Adiciona o metodo Kernel#gem.
  • Faz um Monkey Patch no Kernel#require

Kernel#gem

Permite “acionar” uma versão específica de gem. Note que este acionar, traduz-se para, adicionar a lib da gem no $LOAD_PATH. Segue um trecho do comentário do Kernel#gem:

##

# Use Kernel#gem to activate a specific version of +gem_name+.

#

# +version_requirements+ is a list of version requirements that the

# specified gem must match, most commonly “= example.version.number”.  See

# Gem::Requirement for how to specify a version requirement.

#

# If you will be activating the latest version of a gem, there is no need to

# call Kernel#gem, Kernel#require will do the right thing for you.

#

# Kernel#gem returns true if the gem was activated, otherwise false.  If the

# gem could not be found, didn’t match the version requirements, or a

# different version was already activated, an exception will be raised.
[...]

Kernel#require

No final do rubygems.rb encontramos:

if RUBY_VERSION < ‘1.9′ then

require ‘rubygems/custom_require’

end

Não consegui descobrir o que acontece com o ruby 1.9, mas no 1.8, o monkey patch executa os seguintes passos:

  • Chama o “original” require;
  • Em caso de LoadError;
    • Executa o “Gem.searcher.find(path)”;
    • Se true
      • Chama o activate (novamente traduz-se para adiciona a gem no $LOAD_PATH)
      • Executa o “original” require novamente;

Exemplos com o IRB

Para finalizar legal e comprovar tudo isso, fiz alguns testes:

$ gem list json

*** LOCAL GEMS ***

json (1.2.0, 1.1.9)

json_pure (1.2.0)

$ irb
irb(main):001:0> $LOAD_PATH

=> ["/usr/local/lib/site_ruby/1.8", "/usr/local/lib/site_ruby/1.8/i486-linux", "/usr/local/lib/site_ruby/1.8/i386-linux", "/usr/local/lib/site_ruby", "/usr/lib/ruby/vendor_ruby/1.8", "/usr/lib/ruby/vendor_ruby/1.8/i486-linux", "/usr/lib/ruby/vendor_ruby", "/usr/lib/ruby/1.8", "/usr/lib/ruby/1.8/i486-linux", "/usr/lib/ruby/1.8/i386-linux", "."]

irb(main):004:0> require "json"

LoadError: no such file to load — json

from (irb):4:in `require’

from (irb):4

from :0

irb(main):005:0> gem "json", "= 1.2.0"

NoMethodError: undefined method `gem’ for main:Object

from (irb):5
from :0

O require “json” por si só, carrega a versão mais atual da gem.

irb(main):006:0> require "rubygems"

=> true

irb(main):007:0> require "json"

=> true

irb(main):008:0> JSON::VERSION

=> “1.2.0″

irb(main):009:0> $LOAD_PATH

=> ["/usr/lib/ruby/gems/1.8/gems/gemcutter-0.1.8/lib", "/usr/lib/ruby/gems/1.8/gems/json-1.2.0/bin", "/usr/lib/ruby/gems/1.8/gems/json-1.2.0/ext/json/ext", "/usr/lib/ruby/gems/1.8/gems/json-1.2.0/ext", "/usr/lib/ruby/gems/1.8/gems/json-1.2.0/lib", "/usr/local/lib/site_ruby/1.8", "/usr/local/lib/site_ruby/1.8/i486-linux", "/usr/local/lib/site_ruby/1.8/i386-linux", "/usr/local/lib/site_ruby", "/usr/lib/ruby/vendor_ruby/1.8", "/usr/lib/ruby/vendor_ruby/1.8/i486-linux", "/usr/lib/ruby/vendor_ruby", "/usr/lib/ruby/1.8", "/usr/lib/ruby/1.8/i486-linux", "/usr/lib/ruby/1.8/i386-linux", "."]

irb(main):010:0> quit

Após o require “json”, as pastas foram adicionadas no $LOAD_PATH.

"/usr/lib/ruby/gems/1.8/gems/json-1.2.0/bin", "/usr/lib/ruby/gems/1.8/gems/json-1.2.0/ext/json/ext", "/usr/lib/ruby/gems/1.8/gems/json-1.2.0/ext", "/usr/lib/ruby/gems/1.8/gems/json-1.2.0/lib"

Agora olhe que interessante este último teste:

$ irb

irb(main):001:0> require "rubygems"

=> true

irb(main):002:0> $LOAD_PATH

=> ["/usr/lib/ruby/gems/1.8/gems/gemcutter-0.1.8/lib", "/usr/local/lib/site_ruby/1.8", "/usr/local/lib/site_ruby/1.8/i486-linux", "/usr/local/lib/site_ruby/1.8/i386-linux", "/usr/local/lib/site_ruby", "/usr/lib/ruby/vendor_ruby/1.8", "/usr/lib/ruby/vendor_ruby/1.8/i486-linux", "/usr/lib/ruby/vendor_ruby", "/usr/lib/ruby/1.8", "/usr/lib/ruby/1.8/i486-linux", "/usr/lib/ruby/1.8/i386-linux", "."]

irb(main):003:0> gem "json", "= 1.1.9"

=> true

irb(main):004:0> $LOAD_PATH

=> ["/usr/lib/ruby/gems/1.8/gems/gemcutter-0.1.8/lib", "/usr/lib/ruby/gems/1.8/gems/json-1.1.9/bin", "/usr/lib/ruby/gems/1.8/gems/json-1.1.9/ext/json/ext", "/usr/lib/ruby/gems/1.8/gems/json-1.1.9/ext", "/usr/lib/ruby/gems/1.8/gems/json-1.1.9/lib", "/usr/local/lib/site_ruby/1.8", "/usr/local/lib/site_ruby/1.8/i486-linux", "/usr/local/lib/site_ruby/1.8/i386-linux", "/usr/local/lib/site_ruby", "/usr/lib/ruby/vendor_ruby/1.8", "/usr/lib/ruby/vendor_ruby/1.8/i486-linux", "/usr/lib/ruby/vendor_ruby", "/usr/lib/ruby/1.8", "/usr/lib/ruby/1.8/i486-linux", "/usr/lib/ruby/1.8/i386-linux", "."]

irb(main):005:0> JSON

NameError: uninitialized constant JSON
from (irb):5

irb(main):006:0> require "json"

=> true

irb(main):007:0> JSON::VERSION

=> “1.1.9″

irb(main):008:0> quit

Note que após o gem “json”, “= 1.1.9″ … a versao 1.1.9 foi adicionada no $LOAD_PATH mas não foi carregada. Ao executar o require “json”, como este já estava no $LOAD_PATH, a versão 1.1.9 é usada.

Espero que com estas explicações, você use com mais segurança o rubygems.

Paginação no Rails com will_paginate e Ajax de modo fácil 2

Posted by Rodrigo Panachi on julho 13, 2009

Paginação é um recurso simples e indispensável em qualquer aplicação séria. Em se tratando de Rails, a solução mais popular é a gem WillPaginate que basicamente adiciona o método “paginate_” aos models do ActiveRecord e fornece um helper para renderização dos links da paginação nas views.

Instalando a gem:

sudo gem install will_paginate

Para utilizar na aplicação, adicione no final do config/environment.rb:

require 'will_paginate'

Altere o controller para utilizar paginação:

def index
  @posts = Post.paginate :all, :page => params[:page], :per_page => 10
end

E adicione os links da paginação na view:

<%= will_paginate @posts %>

Pronto! Ao clicar nos links da paginação o parâmetro “page” será incluído automaticamente na requisição.

Legal, mas cadê o “ajax”?

Por padrão o WillPaginate não se preocupa com isso. O próprio desenvolvedor recomenda usar javascript para interceptar o “click” dos links e renderizar o resultado na mesma página.

Outra alternativa seria estender a classe responsável por renderizar os links da paginação para utilizar requisições com ajax.

Inclua em app/helpers o arquivo ajax_will_paginate.rb com o código:

class AjaxWillPaginate < WillPaginate::LinkRenderer
  def prepare(collection, options, template)
    @update = options[:update]
    super
  end
  protected
  def page_link(page, text, attributes = {})
    @template.link_to_remote(text, {
      :url     => url_for(page),
      :method  => :get,
      :update => @update
    })
  end
end

Então adicione no final do arquivo config/environment.rb:

  WillPaginate::ViewHelpers.pagination_options[:renderer] = 'AjaxWillPaginate'

E altere a chamada do helper na view para:

<%= will_paginate @posts, :update => 'div_principal' %>

Informe na opção :update o Id de um objeto html que contenha todo o conteúdo da paginação que será substituído nas requisições seguintes.

É importante lembrar que esta solução altera o comportamento de todos os helpers de paginação da aplicação, por isso deve ser utilizada com cautela. Outras soluções parecidas podem ser encontradas aqui.

Agendando tarefas em aplicações Rails com rufus-scheduler 1

Posted by Rodrigo Panachi on maio 31, 2009

Rufus é um conjunto de gems utilizado para Workflow e BPM. O rufus-scheduler é a gem responsável pelo agendamento e execução de tarefas (jobs). Se você programa em Java e conhece o Quartz não vai ter dificuldade em utilizá-la.

Instalação:

sudo gem install rufus-scheduler

Utilização:

require 'rubygems'
require 'rufus/scheduler'

scheduler = Rufus::Scheduler.start_new

scheduler.every '5m' do
  puts 'Executando a cada 5 minutos'
end

scheduler.schedule '0 18 * * *' do
  puts 'Executando todos os dias as 18h'
end

Simples assim! Consulte a documentação oficial ou contribua com o código.

Gerando cheat sheet para os snippets do gedit 1

Posted by Roger Leite on maio 05, 2009

Acredito que a maioria de vocês conhecem e/ou já usaram as famosas cheat sheets (tradução para “cola”?) para algo. Costumo usá-las quando quero fixar algum conceito novo ou simplesmente para consultas rápidas. Com o pai Google, é possível encontrar os mais diversos tipos: HTML, Ruby, Ruby on Rails, Shell Script… etc.

Atualmente venho praticando Ruby, sendo o gedit – “tunado” com vários plugins – o meu maior aliado. Um dos plugins que mais me ajuda é o Snippets e neste post do Cássio Marques, você encontra uma breve descrição do que é, e um link para snippets de exemplo.

Tudo isso foi só pra contar o que me levou a criar a gem gedit-snippets-tool. Com ela é possível criar cheat sheets dos seus snippets. Supondo que você tenha ruby e rubygem instalados, seu modo de uso é muito simples. Para instalar a gem execute:

sudo gem install rogerleite-gedit-snippets-tool -s http://gems.github.com

Após a instalação, para gerar um cheat sheet com todos os seus snippets, execute:

gedit-snippets-tool -cs > ~/mycheatsheet.xhtml

Caso tenha muitos snippets, e deseja criar um cheat sheet somente com Ruby e Ruby on Rails por exemplo, execute:

gedit-snippets-tool -cs ruby* > ~/mycheatsheet.xhtml
Meu cheat sheet de exemplo

Meu cheat sheet de exemplo

Levando em conta que a gem foi feita em três dias e focamos somente o necessário para lançarmos uma versão 0.x “em produção”, vamos as limitações:

  • O gedit-snippets-tool lê os snippets (arquivos XML) que estão na pasta “home”. Constatei que o gedit “limpo” logo após habilitar o plugin Snippets, guarda os snippets na pasta “/usr/share/gedit-2/plugins/snippets/”. Bom, se for este o seu caso, peço a gentileza de copiá-los para a “home” em “{home-folder}/.gnome2/gedit/snippets/”.
  • O template usado para gerar a página xhtml está bem “rústico”. O lado bom disso é que está bem fácil de alterá-lo. Vejam o código do template (via gist, leitores de RSS, sorry):

Quem tiver sugestões (inclusive de template), podem forkar o projeto ou se quiser, deixe um comentário que eu entro em contato.

Sobre o Desenvolvimento

Assim que tive a idéia, análisei o que seria necessário, e resumindo:

  • Ler os XMLs dos snippets;
  • Uma engine para gerar páginas através de “templates”;
  • Fazer uma gem executável… pois assim é mais fácil e rápido para quem quiser usá-la;

A parte do XML é fácil, pois até já fiz coisa parecida antes. A parte da engine para templates, o pai Google me guiou a uma ótima, chamada Erubis, que é uma gem e torna as coisas muito mais fáceis.

Para criar o esqueleto da gem, usei o gemhub do Diego Carrion, que facilitou muito o meu trabalho, sem contar a ajuda que me deu na hora de publicar o gemspec no github… valeu truta! Bom, o resto foram três noites programando e aprendendo a fazer a minha primeira gem. Quem quiser participar estão todos convidados a “forka-lo” no github.