RSS Feed

Criando suas próprias Gems

26/01/2012 by Uriel Juliatti Valle

Mesmo com pouco tempo de desenvolvimento pude concluir que continuamente escrevia aplicações amarradas e extremamente acopladas. À medida que fui adquirindo experiência e a oportunidade de desenvolver aplicações mais robustas aqui na Giran (@giran_br), notei a urgência de  aprender a desenvolver aplicações mais flexíveis e modulares, isto é, “plugáveis”. Essa visão torna o sistema mais coeso e com o risco de acoplamento bem menor – como aconselham as boas práticas sobre a arquitetura de um projeto de sistema e a “real” Orientação a Objetos (polêmicos)..

Contudo, com esse objetivo em vista, resolvi utilizar os recursos do ruby para ir de encontro às melhores soluções. E embarcando nessa jornada comecei a dar uma pincelada sobre as solução já existentes no ruby, quando então me deparei com o mais óbvio: as gems - Você já fez um gem install -algumagembonitaecharmosaquevaifazertudopragente-, não é? É por aí..

Mas antes da prática, um pouco de teoria para fixar:

RubyGems

A descrição do site oficial diz:

RubyGems is a package manager for the Ruby programming language that provides a standard format for distributing Ruby programs and libraries (in a self-contained format called a “gem”), a tool designed to easily manage the installation of gems, and a server for distributing them.

Utilizando a referência, o RubyGems é um gerenciador de pacotes para ruby que facilita a instalação de gems. Alguns se confundem com a definição e até mesmo com a aplicação dos termos, confundindo um com o outro, mas o RubyGem é, como havia dito, o gerenciador, ou seja, a ferramenta necessária para versionar, instalar, desinstalar, listar , procurar, construir as gems, etc.

E o que são gems?

Bibliotecas, pacotes e até mesmo aplicações em ruby, se assim posso dizer – uma gem é basicamente isso. Porém, a maneira que você vai utilizar essa gem é uma questão de decisão de cada programador de acordo com a decisão de cada projeto.

Teoria, teoria, bla bla bla.

O principal objetivo desse post é aprender como funciona a construção de uma gem simples e como ela pode ser construída. Portanto, não terá nenhum tipo de implementação por agora. Vamos começar com o já subestimado  HelloWorld para o mundo da criação de gems. Depois dessa chuva teórica – e radioativa – abra o seu terminal crie o ambiente ruby para essa gem:

1
rvm gemset create hello_word
rvm gemset create hello_word
1
rvm gemset use hello_word
rvm gemset use hello_word

Se você não sabe ou nunca utilizou o RVM, pode começar lendo aqui e aqui: O RVM irá ajudar bastante na organização dos rubies dos seus projetos.

Criando a estrutura.

A partir daí , passamos para a construção do esqueleto das nossas gems: Alguns preferem via shellzinho [trollface]

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

, outros preferem usar o Jeweler – tanto para estrutura como para o manipulamento – mas eu escolhi o Bundler - a mesma biblioteca que utilizamos no rails para instalar as dependências do Gemfile ;)

Portanto, caso não tiver instalado – verifique com um rvm gem list - utilize o comando gem install bundler  e com a gem já instalada, vamos criar a nossa estrutura assim: $ bundle gem hello_world:

1
2
3
4
5
6
7
create hello_world/Gemfile
create hello_world/Rakefile
create hello_world /.gitignore
create hello_world/hello_world.gemspec
create hello_world/lib/hello_world.rb
create hello_world/lib/hello_world/version.rb
Initializating git repo in /Users/urieljuliatti/git/hello_world
create hello_world/Gemfile
create hello_world/Rakefile
create hello_world /.gitignore
create hello_world/hello_world.gemspec
create hello_world/lib/hello_world.rb
create hello_world/lib/hello_world/version.rb
Initializating git repo in /Users/urieljuliatti/git/hello_world

Modificando o gemspec.

O bundler criou um arquivo .gemspec que possui diversos dados sobre a sua gem recém criada. Algumas partes você precisa modificar, mas falarei em alguns instantes. Obtivemos o seguinte dentro do .gemspec:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
require "hello_world/version"
 
Gem::Specification.new do |s|
s.name = "hello_world"
s.version = HelloWorld::VERSION
s.platform = Gem::Platform::RUBY
s.authors = ["Seu nome"]
s.email = ["Seu email"]
s.homepage = "Seu Website"
s.summary = %q{Um resumo.}
s.description = %q{A descrição da sua gem}
 
s.rubyforge_project = "hello_world"
 
s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
end
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
require "hello_world/version"

Gem::Specification.new do |s|
s.name = "hello_world"
s.version = HelloWorld::VERSION
s.platform = Gem::Platform::RUBY
s.authors = ["Seu nome"]
s.email = ["Seu email"]
s.homepage = "Seu Website"
s.summary = %q{Um resumo.}
s.description = %q{A descrição da sua gem}

s.rubyforge_project = "hello_world"

s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
end

* Utilizaremos as configurações logo adiante na hora que formos publicar nossas gem.

Versionamento.

Observe que o Bundler criou um /version além de uma constante chamada HelloWorld::VERSION – gerada pelo version.rb dentro de lib/hello_world – para a nossa gem, isto significa que você pode versionar sua gem através dele. Inclusive, esse é um dos bons recursos adquiridos ao tomar uma decisão de criar uma gem . O ideal é você seguir algumas recomendações Semantic Versioning, mas caso deseje uma mais simples, seguiremos com algumas considerações que fiz:

1
2
3
4
5
6
7
# dir: /lib/hello_world/version.rb
 
module HelloWorld
 
VERSION = "0.0.1"
 
end
# dir: /lib/hello_world/version.rb

module HelloWorld

VERSION = "0.0.1"

end

Quando sua gem tiver outros ou novos recursos, é só modificar o número da versão e publicar novamente com o gem build.

Agora vejamos o que foi gerado no diretório lib/, temos um arquivo chamado hello_world  que é o cara que chamamos quando alguém requerer nossa gem. Você pode criar outros arquivos no diretório e chamá-los nessa classe, mas no nosso caso vai ser só o método hello, para demonstração ;)

1
2
3
4
5
6
7
8
9
10
11
# dir: lib/hello_world.rb
 
module HelloWorl
 
def self.hello
 
"Olá mundo!"
 
end
 
end
# dir: lib/hello_world.rb

module HelloWorl

def self.hello

"Olá mundo!"

end

end

Publicando nossa gem.

Bem, agora que você já gerou a estrutura, criou seu arquivo ruby e definiu algum comportamento ( método), vamos então vamos editar nosso gemspec para publicar nossa gem:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
require "hello_world/version"
 
Gem::Specification.new do |s|
s.name = "hello_world"
s.version = HelloWorld::VERSION
s.platform = Gem::Platform::RUBY
s.authors = ["Uriel Juliatti"]
s.email = ["uriel.juliatti@giran.com.br"]
s.homepage = ""
s.summary = %q{Primeira gemzinha para aprender como criar a sua própria gem.}
s.description = %q{Essa gem é um demonstrativo do blog do Uriel Juliatti, desenvolvedor web na Giran Soluções e E-commerce.}
 
s.rubyforge_project = "hello_world"
 
s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
end
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
require "hello_world/version"

Gem::Specification.new do |s|
s.name = "hello_world"
s.version = HelloWorld::VERSION
s.platform = Gem::Platform::RUBY
s.authors = ["Uriel Juliatti"]
s.email = ["uriel.juliatti@giran.com.br"]
s.homepage = ""
s.summary = %q{Primeira gemzinha para aprender como criar a sua própria gem.}
s.description = %q{Essa gem é um demonstrativo do blog do Uriel Juliatti, desenvolvedor web na Giran Soluções e E-commerce.}

s.rubyforge_project = "hello_world"

s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
end

Agora buildamos a nossa gem:

1
2
3
4
5
6
$ gem build hello_world.gemspec
WARNING: no homepage specified
Successfully built RubyGem
Name: hello_world
Version: 0.0.1
File: hello_world-0.0.1.gem
$ gem build hello_world.gemspec
WARNING: no homepage specified
Successfully built RubyGem
Name: hello_world
Version: 0.0.1
File: hello_world-0.0.1.gem

Esse comando é responsável por gerar um .gem. Inclusive, otimi a homepage, caso contrário ele iria subir o arquivo para o repositório Rubygems.org e deixaria disponível ao público essa gem teste.

Gemfile

Um detalhe para  o arquivo de Gemspec – como já mostrado anteriormente –  é que podemos carregar dependências através do bundler – sem se preocupar em gerenciar esse arquivo diretamente. É a melhor forma para gerenciar dependências de uma gem. Por exemplo, digamos que escolhêssemos usar o RSpec para testar a nossa gem: Ao invés de referenciar o RSpec diretamente no Gemfile vamos adicioná-lo como uma dependência em hello_word.gemspec.

Obs: Se você não estiver familiarizado com o gerenciamento de um arquivo Gemspec dê uma conferida na documentação.

1
2
3
4
5
6
7
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib"__FILE__)
require "hello_world/version"
 
Gem::Specification.new do |s|
s.add_development_dependency "rspec"
end
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
require "hello_world/version"

Gem::Specification.new do |s|
s.add_development_dependency "rspec"
end

Só para garantir, já que o arquivo é referenciado através do Gemfile , vamos executar o bundle e averiguar se temos todas as dependências instaladas:

1
2
3
4
5
6
7
8
9
10
 $ bundle
Fetching source index for http://rubygems.org/
Using diff-lcs (1.1.2)
Using hello_world (0.0.1) from source at /Users/urieljuliatti/Projects/ruby/hello_world
Installing rspec-core (2.3.1)
Installing rspec-expectations (2.3.0)
Installing rspec-mocks (2.3.0)
Installing rspec (2.3.0)
Using bundler (1.0.7)
Your bundle is complete! It was installed into /Users/urieljuliatti/.rvm/gems/ruby-1.9.2-p290
 $ bundle
Fetching source index for http://rubygems.org/
Using diff-lcs (1.1.2)
Using hello_world (0.0.1) from source at /Users/urieljuliatti/Projects/ruby/hello_world
Installing rspec-core (2.3.1)
Installing rspec-expectations (2.3.0)
Installing rspec-mocks (2.3.0)
Installing rspec (2.3.0)
Using bundler (1.0.7)
Your bundle is complete! It was installed into /Users/urieljuliatti/.rvm/gems/ruby-1.9.2-p290

Dessa forma, ao publicarmos uma gem que tenha vários contribuidores envolvidos, você pode instruí-los a usar o bundle, ajudando-os a configurar o ambiente e instalando todas as suas dependências de uma só vez. Como boa prática, adicione essas informações sempre no README da sua gem ;)

Gerenciando tarefas.

Um outro bom detalhe é se atentar ao Rakefile gerado pelo Bundler e as suas tarefas:

1
2
3
4
5
6
7
8
9
10
11
12
13
#dir /Rakefile
 
require 'bundler'
 
$ rake -T
 
(in /Users/urieljuliatti/ruby/hello_world)
 
rake build    # Constrói a gem  dentro de um diretório , empacotando-a.
 
rake install  # Constrói e instala a gem no sistema de gems
 
rake release  # Cria uma tag para a versão, constrói a gem e publica lá em Rubygems
#dir /Rakefile

require 'bundler'

$ rake -T

(in /Users/urieljuliatti/ruby/hello_world)

rake build    # Constrói a gem  dentro de um diretório , empacotando-a.

rake install  # Constrói e instala a gem no sistema de gems

rake release  # Cria uma tag para a versão, constrói a gem e publica lá em Rubygems

Esse código é responsável por instalar uma gem e configurada do nosso jeito, portanto podemos instalá-la e testá-la por completo localmente, aí então chamar o rake release para tagear aquela versão e publicá-la no RubyGems. Praticamente, é uma forma de “automatizar” certos processos referentes a produção das nossas gems.


1 Comment »

  1. Kholied says:

    Nice tutorial, been lokiong for something like this for a while now. Just two possible correction points: 1. In the “awesome_gem.gemspec” file you need to add the authors entry, e.g. s.authors = ["Your Name"] 2. The command to install the gem after successfully creating it is: gem install awesome_gem-0.0.0.gem it should refer to the .gem file not the .gemspec Hope this helps.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code lang=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" extra="">