Criando sua própria RubyGem
Leia em 4 minutos
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.
$ 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 test/hello_world_test.rb
. Nosso módulo terá um único método chamado say
.
require "minitest/autorun"
class HelloWorldTest < Minitest::Test
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.
Run options: --seed 51020
# Running:
E
Finished in 0.001068s, 936.5488 runs/s, 0.0000 assertions/s.
1) Error:
HelloWorldTest#test_say_hello_to_the_world:
NameError: uninitialized constant HelloWorldTest::HelloWorld
test/hello_world_test.rb:5:in `test_say_hello_to_the_world'
1 runs, 0 assertions, 0 failures, 1 errors, 0 skips
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 "minitest/autorun"
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
Run options: --seed 63119
# Running:
.
Finished in 0.001127s, 887.1926 runs/s, 887.1926 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
Ó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.rb
/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
sugerido pelo Semantic Versioning, como em 1.2.3
.
MAJOR:
inclui alterações de API e pode quebrar compatibilidade com versões anterioresMINOR:
inclui novas funcionalidades, sem quebrar APIs existentesPATCH:
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
No seu arquivo hello_world.gemspec
, carregue o arquivo de versão e referencie-o em vez de deixar o valor fixo neste arquivo.
require './lib/hello_world/version'
Gem::Specification.new do |s|
s.name = "hello_world"
s.version = HelloWorld::Version::STRING
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
Usando o Bundler
O Bundler possui um gerador de gems que facilita toda esta configuração. Para gerar a estrutura de sua biblioteca, execute o comando bundle gem
.
$ bundle gem hello_world -t minitest --coc
Creating gem 'hello_world'...
Code of conduct enabled in config
create hello_world/Gemfile
create hello_world/.gitignore
create hello_world/lib/hello_world.rb
create hello_world/lib/hello_world/version.rb
create hello_world/hello_world.gemspec
create hello_world/Rakefile
create hello_world/README.md
create hello_world/bin/console
create hello_world/bin/setup
create hello_world/CODE_OF_CONDUCT.md
create hello_world/.travis.yml
create hello_world/test/test_helper.rb
create hello_world/test/hello_world_test.rb
Initializing git repo in /Users/fvieira/Projects/samples/hello_world
A ideia é basicamente a mesma; crie seu código no diretório lib
e os testes no diretório test
. A maior diferença do Bundler é que ele configura a geração e publicação da gem através de Rake tasks.
$ rake -T
rake build # Build hello_world-0.1.0.gem into the pkg directory
rake install # Build and install hello_world-0.1.0.gem into system gems
rake install:local # Build and install hello_world-0.1.0.gem into system gems without network access
rake release # Create tag v0.1.0 and build and push hello_world-0.1.0.gem to Rubygems
rake test # Run tests
Finalizando
Entender o processo de criação e publicação de uma gem é fundamental para qualquer desenvolvedor Ruby. Além de permitir que você distribua seu código, ajuda na hora de fazer o debugging de uma biblioteca que você esteja usando.