Criando sua própria Ruby gem
(com CLI)
por meleu
Motivação
Criar uma Ruby Gem é uma excelente forma de compartilhar código.
(não apenas no Open-Source, mas também internamente)
O que veremos?
- Processo de criação/publicação de gems:
- "do zero"
- com o bundler
- configurar o arquivo
*.gemspec
- o que o RubyGems.org nos oferece
- criar um CLI para usar a gem direto do terminal
quem sou eu?
- Nerdão do Linux since late 90s
- Projetos Open Source relacionados a retrogaming
- Desenvolvedor Web
- DevOps Engineer
- Hoje Software Quality Engineer na CloudWalk
- https://meleu.dev
Calibrando as expectativas
- Foco no processo de criação de uma gem
- (e não na funcionalidade da gem)
Calibrando o conteúdo
- Test first
- CI/CD pipelines
WTF is a gem?!
- o comando
gem
- o que é uma gem
- RubyGems.org
O comando gem
Um gerenciador de pacotes da linguagem Ruby que provê um formato padronizado de distribuir programas e bibliotecas.
O que é uma gem
É um pacote auto-contido que contêm código ruby que pode ser reutilizado.
Exemplos:
- rails
- nokogiri
- faker
RubyGems.org
É um serviço web utilizado para disponibilizar gems.
Espera! Mas e o bundler?!
O bundler é uma ferramenta criada para garantir que os desenvolvedores de um projeto utilizem a mesma versão das gems.
Gemfile
e Gemfile.lock
são arquivos usados pelo bundler (e não pelo comando gem
).
Colocando a mão na massa! 💪
doc: https://guides.rubygems.org/what-is-a-gem/
O mínimo do mínimo para criarmos uma gem:
$ tree hello_meleu
hello_meleu/
├── hello_meleu.gemspec
└── lib
└── hello_meleu.rb
obs.: usar um nome diferente!
Test First!!!
(???)
test/test_hello_meleu.rb
lib/hello_meleu.rb
class HelloMeleu
def self.hello
'Hello meleu!'
end
end
hello_meleu.gemspec
Gem::Specification.new do |s|
s.name = 'hello_meleu'
s.authors = ['meleu']
s.files = ['lib/hello_meleu.rb'] # <-- IMPORTANTE!
s.summary = 'Greeting meleu'
s.version = '0.0.1'
end
doc:
https://guides.rubygems.org/specification-reference/
Publicando sua gem
# buildando a gem
gem build hello_meleu.gemspec
# instalando localmente
gem install hello_meleu-0.0.1.gem
# experimentar no irb
# publicando no RubyGems.org
gem push hello_meleu-0.0.1.gem
# na primeira vez, vai pedir pra se cadastrar
🎉 Parabéns! 🎉
Sua gem foi publicada!
https://rubygems.org/gems/hello_meleu
enriquecendo a gemspec
criar um repositório no github
enriquecendo a gemspec
Gem::Specification.new do |s|
# ...
s.homepage = "https://github.com/meleu/hello_meleu"
end
enriquecendo a gemspec
Gem::Specification.new do |s|
# ...
s.metadata = {
"bug_tracker_uri": "#{s.homepage}/issues",
"changelog_uri": "#{s.homepage}/releases",
"wiki_uri": "#{s.homepage}/wiki",
"source_code_uri": s.homepage,
"documentation_uri": "https://www.rubydoc.info/gems/hello_meleu",
}
end
enriquecendo a gemspec
documentação
TODO: link para YARD
# Class used to greet meleu with "hello".
class HelloMeleu
# Greets meleu with "hello".
#
# @return [String] "Hello meleu!"
def self.hello
"Hello meleu!"
end
end
Publicando a nova versão
# buildando a gem
gem build hello_meleu.gemspec
# publicando no RubyGems.org
gem push hello_meleu-0.0.2.gem
🎉 Parabéns novamente! 🎉
Sua gem foi atualizada!
https://rubygems.org/gems/hello_meleu
(lembrete: mostrar rubydoc.info)
Sua gem está disponível para o mundo!
# instalar
gem install hello_meleu
# testar no irb
Recapitulando
- colocamos nosso código em
lib/
- especificamos configurações da nossa gem no arquivo
*.gemspec
- vimos alguns campos interessantes do gemspec
- publicamos nossa gem para o mundo
- vimos alguns recursos úteis do RubyGems.org
Break time!
https://icanhazdadjoke.com
(aka "piadas de tiozão")
Fornece uma API REST que não requer autenticação!
gem HTTParty
https://github.com/jnunemaker/httparty
experimentar no irb
:
- random joke
- search
Criar uma gem pra buscar dad jokes!
Motivação:
- Criar uma gem que depende de outra gem
- Criar um CLI pra usar a gem a partir do terminal
Criando uma gem usando o bundler
(scaffold feelings 😇)
obs.: usar um nome diferente!
bundle gem dadjoke \
--exe \
--coc \
--mit \
--test=minitest \
--ci=github \
--linter=rubocop
cd dadjoke
git stuff
- obs.: não precisa criar repo no github ainda
- criar um
.gitignore
git add && git commit
bundle install # vai falhar
Gemfile
# ...
# Specify your gem's dependencies in dadjoke.gemspec
gemspec
# ...
TODO: editar os TODOs! Principalmente os do gemspec.
Falar do arquivo version.rb
Listando dependências no gemspec
Gem::Specification.new do |s|
# ...
# ignorar código confuso e cheio de TODOs...
# ...
spec.add_runtime_dependency "httparty", "~> 0.21"
end
# instalar dependências
bundle install
Test First!!!
(??? link ???)
test/test_dadjoke.rb
Obtendo uma dadjoke aleatória
class Joke
def self.random
HTTParty.get(@url, headers: @headers)["joke"]
end
end
Buscando dadjokes
class Joke
def self.search(term, limit = 3)
query = { term:, limit: }
response = HTTParty.get(
"#{@url}/search",
headers: @headers,
query:
)
response["results"].map { |result| result["joke"] }
end
end
Testar no irb
bin/console
Tudo ok?
Boa hora para mais um git commit
e subir pra um repo no github.
GitHub Actions
(opcional)
Mostrar action no repo.
Ajeitando o gemspec
- preencher os
TODO
s spec.metadata
- formatar em um hash
"documentation_uri" => "https://rubydoc.info/gems/dadjoke"
spec.files
spec.executables
lib/dadjoke/version.rb
Publicando a gem!
# buildando a gem
gem build dadjoke.gemspec
# publicando no RubyGems.org
gem push dadjoke-0.0.1.gem
# instalar do rubygems.org
gem install dadjoke
Criando um CLI
Primeiro um deleteme.rb
.
Queremos um comando UNIX decente!
- com uma mensagem de help
- com argumentos auto-explicativos
Conhecendo o Thor
(criar um mycli
com hello world e opção --up
)
Adicionando dependência no gemspec
Gem::Specification.new do |s|
# ...
spec.add_runtime_dependency "thor", "~> 1.3"
end
bundle install
Obtendo uma dad joke aleatória
class DadjokeCLI < Thor
desc "random", "Get a random dad joke"
def random
puts Dadjoke::Joke.random
end
end
# comando:
dadjoke random
Comando default
Não quero ter que digitar dadjoke random
quando apenas dadjoke
já seria suficiente.
class DadjokeCLI < Thor
# ...
default_command :random
end
# comando:
dadjoke
Buscando uma dad joke
class DadjokeCLI < Thor
desc "search TERM", "Get jokes based on a search TERM"
def search(term)
jokes = Dadjoke::Joke.search(term)
puts jokes.join("\n\n---\n\n")
end
end
Buscando uma dad joke
class DadjokeCLI < Thor
desc "search TERM", "Get jokes based on a search TERM"
+ option :num, type: :numeric, default: 1, desc: "Amount of jokes to retrieve"
def search(term)
- jokes = Dadjoke::Joke.search(term)
+ jokes = Dadjoke::Joke.search(term, options[:num])
puts jokes.join("\n\n---\n\n")
end
end
Recapitulando
- Criamos uma gem um pouco mais realista
- interagindo com API REST
- com dependências
- com repo no github (aberto para contribuições)
- com linting (rubocop)
- Criamos um CLI pra usar nossa gem no terminal
- Publicamos nossa gem para o mundo usar!