Todo mundo já deve ter usado pelo menos uma vez na vida as famosas gems. Elas ajudam muito na hora distribuir e usar bibliotecas Ruby, sem dúvida nenhuma. Mas o que nem todo mundo sabe é que criar uma gem é muito mais fácil do que parece. Neste artigo você verá como criar e distribuir sua própria gem.

Entendo a gem

Uma gem nada mais é que um arquivo .tar.gz.

$ tar -ztvf bundler-0.9.26.gem
-rw-r--r--  0 wheel  wheel   47579 Dec 31  1969 data.tar.gz
-rw-r--r--  0 wheel  wheel     993 Dec 31  1969 metadata.gz

Neste arquivo, você encontra o código Ruby propriamente dito (data.tar.gz) e os metadados (metadata.gz), que é a gem specification (ou apenas gemspec) em formato YAML. Esta gemspec informa, dentre outras coisas, qual é o nome da gem, a versão e dependências do pacote, além dos arquivos que compoem a biblioteca.

Uma gem é composta por uma estrutura mais ou menos definida de arquivos e diretórios, como você pode ver abaixo.

Estrutura de arquivos e diretórios de uma gem

O diretório lib é de longe o mais importante. É nele que todo código Ruby fica armazenado. E é ele que é adicionado ao GEM_PATH, utilizado pelo RubyGems (gerenciador de pacotes) para saber quais bibliotecas estão disponíveis para uso.

O RubyGems substitui o método Kernel#require com sua própria implementação, que faz uma busca pela gem que você precisa utilizando os diretórios adicionados ao GEM_PATH. Então, sempre que você utilizar o método require "some_library", o RubyGems irá procurar por um arquivo lib/some_library.rb, independente de qual gem tenha este arquivo.

Criando a biblioteca HelloWorld

Vamos criar nossa biblioteca HelloWorld, que será empacotada posteriormente como uma gem. O primeiro passo é gerar nossa estrutura de diretórios.

$ mkdir -p hello_world/{lib/hello_world,test}

Crie um arquivo de testes em tests/hello_world_test.rb. Nosso módulo terá um único método chamado say.

require "test/unit"
 
class HelloWorldTest < Test::Unit::TestCase
  def test_say_hello_to_the_world
    assert_equal "Hello World!", HelloWorld.say
  end
end
 

Você pode executar este teste com o comando ruby test/hello_world_test.rb. Ele irá falhar, como era de se esperar.

$ ruby test/hello_world_test.rb
Loaded suite test/hello_world_test
Started
E
Finished in 0.000388 seconds.
 
  1) Error:
test_say_hello_to_the_world(HelloWorldTest):
NameError: uninitialized constant HelloWorldTest::HelloWorld
    test/hello_world_test.rb:5:in `test_say_hello_to_the_world'
 
1 tests, 0 assertions, 0 failures, 1 errors
 

Agora, crie o arquivo lib/hello_world.rb. Nossa implementação é bastante simples.

module HelloWorld
  def self.say
    "Hello World!"
  end
end

Execute o comando ruby test/hello_world_test.rb mais uma vez. Como era de se esperar, o teste irá… falhar novamente. Isso acontece porque não estamos carregando o módulo HelloWorld. Embora você possa resolver isso de várias maneiras diferentes, vamos fazer do modo mais organizado: iremos criar uma rake task para executar nossos testes, adicionando o diretório lib ao $LOAD_PATH.

Crie um arquivo Rakefile com o seguinte código:

require "rake/testtask"
 
Rake::TestTask.new do |t|
  t.libs << "lib"
  t.test_files = Dir["test/**/*_test.rb"]
end

Altere o arquivo test/hello_world_test.rb, adicionando a linha que irá carregar o arquivo hello_world.rb.

require "test/unit"
require "hello_world"  # Added
 
class HelloWorldTest < Test::Unit::TestCase
  def test_say_hello_to_the_world
    assert_equal "Hello World!", HelloWorld.say
  end
end

Rode o teste mais uma vez, só que agora usando o comando rake test.

$ rake test
(in /Users/fnando/Sites/github/hello_world)
Loaded suite /Users/fnando/.gem/spaces/active/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
.
Finished in 0.00031 seconds.
 
1 tests, 1 assertions, 0 failures, 0 errors

Ótimo! Nossos testes estão passando e como terminamos toda nossa implementação, já podemos criar e distribuir nossa gem.

Gerando nosso pacote

Para gerar um arquivo .gem, precisamos escrever nossa gemspec. Crie um arquivo hello_world.gemspec e adicione o código abaixo.

Gem::Specification.new do |s|
  s.name        = "hello_world"
  s.version     = "0.1.0"
  s.description = "A simple gem that says hello to the world!"
  s.summary     = "Say hello!"
  s.author      = "Nando Vieira"
  s.files       = Dir["{lib/**/*.rb,README.rdoc,test/**/*.rb,Rakefile,*.gemspec}"]
end

As opções da classe Gem::Specification são auto-descritivas; para saber mais sobre estas e outras opções, consulte a documentação.

Agora vem a parte mais fácil. Execute o comando gem build hello_world.gemspec e um arquivo hello_world-0.1.0.gem será gerado.

Você pode instalar este arquivo localmente utilizando o comando gem install hello_world-0.1.0.gem --local. Para listar o que foi instalado, execute gem contents hello_world.

$ gem contents hello_world
/Users/fnando/.gem/spaces/active/gems/hello_world-1.0/lib/hello_world/version.rb
/Users/fnando/.gem/spaces/active/gems/hello_world-1.0/lib/hello_world.rb
/Users/fnando/.gem/spaces/active/gems/hello_world-1.0/README.rdoc
/Users/fnando/.gem/spaces/active/gems/hello_world-1.0/test/hello_world_test.rb
/Users/fnando/.gem/spaces/active/gems/hello_world-1.0/Rakefile
/Users/fnando/.gem/spaces/active/gems/hello_world-1.0/hello_world.gemspec

E, claro, nossa gem também será listada com o comando gem list.

$ gem list hello_world -d
 
*** LOCAL GEMS ***
 
hello_world (1.0)
    Author: Nando Vieira
    Installed at: /Users/fnando/.gem/spaces/active
 
    Say hello!

Distribuindo nossa gem

Para distribuir sua gem, é preciso criar uma conta no site RubyGems.org.

Depois, basta executar gem push hello_world-0.1.0.gem; informe seu e-mail e senha cadastrados no RubyGems.org quando solicitado. Se tudo der certo, você verá uma mensagem como esta:

$ gem push hello_world-0.1.0.gem
Pushing gem to RubyGems.org...
Successfully registered gem: hello_world (0.1.0)

Você pode acessar sua gem em http://rubygems.org/gems/hello_world.

DICA: Antes de escrever sua gem, verifique se o nome está disponível. Um simples gem list nome_da_gem -rd já te dará a resposta. A gem hello_world já existia, por isso foi publicada como http://rubygems.org/gems/fnando-hello_world.

Versionamento dos pacotes

É sua responsabilidade definir como será o versionamento de sua gem. Eu utilizo o formato MAJOR.MINOR.PATCH, como em 1.2.3.

  • MAJOR: inclui alterações de API e pode quebrar compatibilidade com versões anteriores
  • MINOR: inclui novas funcionalidades ou pouca alteração de API
  • PATCH: corrige bugs ou traz melhorias em implementações já existentes

No arquivo lib/hello_world/version.rb você pode ter algo como:

module HelloWorld
  module Version
    MAJOR = 0
    MINOR = 1
    PATCH = 0
    STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
  end
end

E para finalizar…

Gems como Jeweler (que sempre uso) também podem ajudar na hora de criar, distribuir e manter suas bibliotecas. Mas antes de utilizar uma dessas bibliotecas, entender o processo de criação de gems é fundamental. Afinal, é fazendo que se aprende!

Comentários #

#1 Leandro Silva disse:
24 Jul 10, 04:50PM

Ótimo post.

Também sempre uso a Jeweler. :)

#2 Carlos Antonio disse:
24 Jul 10, 05:33PM

Muito bacana o post Nando.
ps: acho que na linha do mkdir, seria "test" no lugar de "tests", não?

#3 Douglas Campos (qmx) disse:
24 Jul 10, 06:30PM

Post bacana!

Só dispenso o jeweler - vou mais pela linha desse post:

http://yehudakatz.com/2010/04/02/using-gemspecs-as-intended/

#4 Hugo Baraúna disse:
24 Jul 10, 07:41PM

Muito bom o post Nando, valeu!

#5 Nando Vieira disse:
24 Jul 10, 10:59PM

Uso o Jeweler principalmente por causa da integração com Github + Gemcutter. A gemspec é só um detalhe. ;)

#6 Nando Vieira disse:
24 Jul 10, 11:01PM

Boa! Já alterei o post! ;)

#7 Daniel disse:
25 Jul 10, 04:08PM

Só um detalhe no começo do post (sem querer ser o cara chato, mas já sendo…): um arquivo .gem em si é só um arquivo .tar, sem compressão. Até não faria muito sentido usar gzip de novo se os dois arquivos internos já passaram por ele.

#8 Roger Leite disse:
26 Jul 10, 10:52AM

Legal o post!
No quesito "helper", prefiro usar o gemhub do Diego Carrion.
http://www.mouseoverstudio.com/blog/2008/10/27/criando-gems-com-gemhub-nunca-foi-tao-simples/

#9 Alexandre de Oliveira disse:
31 Jul 10, 09:47PM

Obrigado pelo post. Como estou começando, não tinha resolvido criar uma gem ainda, mas quando vi seu post me senti compelido e consegui fazer tudo certinho.

Obrigado.

#10 Luiz Eduardo disse:
17 Ago 10, 05:19PM

Olá Nando ... muito bom o Post!!! Mas tenho uma dúvida:

Como eu faço pra minha gem... ao rodar um comando : " script/generate gemdoluiz " copiar alguns arquivos para a pasta public/javascripts, e um arquivo no config/initializers ? pois estou montando um formbuilder para ser usado com jQuery ... e gostaria de copiar meu plugin jquery pra pasta javascript e um arquivo pro config/initializers pro rails usar meu form builder como padrão...

Aguma idéia ??? Valew !

#11 Bruno A. Santos disse:
06 Set 10, 02:33PM

Sei que esse cometario nao é ligado ao post, mas é uma duvida que eu tenho
e creio que você possa me ajudar.
Como eu instalo um bundle que esta no github no textmate?

Desde já, muito obrigado.

#12 Uma introdução a scripts de build | blog.caelum.com.br disse:
10 Nov 10, 10:32AM

[...] em Ruby, o artefato comum é uma gem, que podemos criar utilizando a task [...]

#13 Marco Antonio disse:
01 Dez 10, 11:30PM

Muito bom, me ajudou bastante

#14 Luiz Fernando disse:
01 Abr 11, 08:07PM

E ai Nando, gostei mesmo do post, mas fiz um teste com os códigos aqui e deu erro, ai alterei a lib no arquivo rake para lib/hello_world, e funcionou, mas não sei se a forma como fiz é o correto, estou começando com ruby, gostaria de saber se a forma como ocorreu para mim é o correto? eu uso o ruby 1.8.7.

Deixe um comentário





Não é aceito código HTML: adicione-o no pastie.org ou paste.milk-it.net e poste apenas o link.

Se este é seu primeiro comentário, ele terá que ser aprovado antes de ser exibido.

jQuery: Dominando o framework

Você quer aprender a usar jQuery de verdade? Então chegou a hora! Neste workshop você verá como funciona este framework de JavaScript, entendendo todos os aspectos que fazem do jQuery uma das melhores ferramentas para desenvolvimento de interfaces.

Saiba mais Fechar

Conheça também o HOWTO