Criando sua própria RubyGem
24/07/10
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.

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 anterioresMINOR:inclui novas funcionalidades ou pouca alteração de APIPATCH: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!
- Permalink
- Trackback
- Comentários (10)
- Ao som de: Phone Trio – Don't Sign The Divorce Papers, Babe
Textos escritos por
Comentários #
Ótimo post.
Também sempre uso a Jeweler. :)
Muito bacana o post Nando.
ps: acho que na linha do mkdir, seria "test" no lugar de "tests", não?
Boa! Já alterei o post! ;)
Post bacana!
Só dispenso o jeweler - vou mais pela linha desse post:
http://yehudakatz.com/2010/04/02/using-gemspecs-as-intended/
Uso o Jeweler principalmente por causa da integração com Github + Gemcutter. A gemspec é só um detalhe. ;)
Muito bom o post Nando, valeu!
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.
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/
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.
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 !
Deixe um comentário