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.
