GeistHaus
log in · sign up

Blog do Tino Gomes

Part of feedburner.com

stories
Metaprogramação em Ruby - Criando métodos de Classe
Show full content

Nem falo nada sobre quanto tempo sem escrever nada, mas vou contar uma novidade rapidinha: Agora Gaveteiro virou NEI. Mas vamos ao que interessa nesse post.

Métodos de Classe? Bom, sabemos que isso não existe em Ruby. Na verdade são métodos singleton da classe, mas enfim, vamos abistrair essa parte.

Bom, então, digamos que você vai mapear um recurso de uma API e ela tem um campo/atributo type, e essa pode retornar os valores ClientA.administrador, ClientA.manager e ClientA.buyer como valores para este atributo. Mas você quer ter um código mais limpo, algo que você possa perguntar ao seu objeto: resource.admin? em vez de ficar espalhado pelo seu código algo como resource.type == "ClientA.administrador" e etc. E o mesmo para criar uma instância desse objeto, teria que fazer algo como Resource.new(type: 'ClientA.administrador'). Não seria melhor ter algo como Resource.new_admin(...)?. Nesse caso, vou apresentar dois métodos para criar dinamicamente métodos. o define_method serve para criar os métodos de instância e o define_singleton_method para criar os “métodos de classe”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Resource < OpenStruct
  include ActiveModel::Validations

  TYPES = {
    "admin"   => "ClientA.administrator",
    "manager" => "ClientA.manager",
    "buyer"   => "ClientA.buyer"
  }.freeze

  validates :api_id, presence: true
  validates :type, inclusion: TYPES.values

  TYPES.each do |key, value|
    # define "question" methods for each type (e.g.: def admin?; type == "ClientA.administrador"; end)
    define_method "#{key}?" do
      type == value
    end

    # define constructors for each type (e.g.: Resource.new_admin({...}))
    self.define_singleton_method("new_#{key}".to_sym) do |**args|
      new(args.merge(type: value))
    end
  end
end

Então agora, é só usar

resource = Resource.new_admin
resource.type   # => it returns "ClientA.administrator"
resource.admin? # => it returns true
resource.buyer? # => it returns false

resource = Resource.new_buyer(name: "Tino")
resource.type   # => it returns "ClientA.buyer"
resource.admin? # => it returns false
resource.buyer? # => it returns true
resource.name   # => it returns "Tino" - YOU DON'T SAY?

O que achou?

PS1: Claro que ainda estamos limitados a conhecer todos os valores que virão no atributo type. Se quisermos que seja 100% dinâmico… (Será que rola outro post?)

PS2: Deu para perceber que desde o último post, estou fazendo integrações com APIs.

Referências:

https://blog.tinogomes.com/2020/02/15/metaprogramacao-em-ruby-criando-metodos-de-classe
Converter JSON para um Objeto Ruby
Show full content

Quem nunca quis validar um JSON usando as validações do ActiveModel do Rails? Mas transformar um JSON multinível em um objeto Ruby parece ser um passo chato de realizar, né? Bom, hoje eu descobri que não.

Exemplo de um JSON bem simples

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "id": "740b3e24-8686-48b6-9eb0-1b09d04bf18e",
  "name": "Carl Johnson",
  "document": "12345",
  "addresses": [
    {
      "street": "414, East 137th Street",
      "neiborhood": "Compton",
      "city": "Los Angeles",
      "state": "CA",
      "zipcode": "90061"
    }
  ]
}

Importando o JSON para uma classe

1
2
3
4
5
6
7
8
9
10
11
12
13
class AccountFromJson < OpenStruct
  include ActiveModel::Validations

  validates :name, :document, presence: true
end


json = File.read('./any/path/file.json')
account = JSON.parse json, object_class: AccountFromJson

account.valid? # => true
account.name # => "Carl Johnson"
account.addresses[0].street # => "414, East 137th Street"

Ah, e Também funciona se no JSON for um array.

Pois é, quem diz que macaco velho não aprende “novos” truques?

Referências:

https://blog.tinogomes.com/2019/10/19/converter-json-para-um-objeto-ruby
Mais DRY no seu YAML
Show full content

Olha quem está vivo? Sim, sou eu. Sem mais enrolação.

Como já publicado no post do DRY config/database.yml, é possível usar aliases para evitar duplicidade de valores, mas como fazer isso para chaves simples? E porque eu tive essa necessidade?

Em uma view onde eu preciso exibir uma mensagem de disponibilidade de expedição de um produto, tenho algo como:

app/views/products/show.html

1
2
3
4
5
6
...
<dl>
  <dt>Prazo para expição</dt>
  <dd><%= I18n.t('.delivery_days', count: product.delivery_days) %></dd>
</dl>
...

config/locales/pt-BR.yml com repetição

1
2
3
4
5
6
7
pt-BR:
  products:
    show:
      delivery_days:
        one: Pronta Entrega
        other: Disponível em %{count} dias úteis
        zero: Pronta Entrega

Considerando que eu tenha que mostrar a mensagem quando da disponibilidade de entrega é de zero ou um dia, mas não quero duplicar a mensagem. O que podemos fazer?

Antes da chave que nós queremos “copiar”, criamos o alias &alias_name, e no lugar do valor que vamos repetir, usamos o *alias_name

config/locales/pt-BR.yml sem repetição

1
2
3
4
5
6
7
pt-BR:
  products:
    show:
      delivery_days:
        &oneDeliveryDay one: Pronta Entrega
        other: Disponível em %{count} dias úteis
        zero: *oneDeliveryDay

No exemplo acima, nós identificamos a chave :one com o alias onDeliveryDay e aplicanos na chave :zero

https://blog.tinogomes.com/2019/03/01/mais-dry-no-seu-yaml
Usando Git Hook para fazer análise do código
Show full content

Senta que lá vem história.

Atualmente temos algumas ferramentas online, como Code Climate e Codacy, para fazer análise do código do nosso projeto, em busca de falhas de segurança, duplicação de código e etc. Também estamos diante da possibilidade de levantar um servidor de CI, como o jenkins, e configurar para fazer o mesmo processo. Usando essas ferramentas, nossos códigos são avaliados somente após feito o commit e enviado (git push) para o repositório “central”. No caso, havendo algum problema no resultado dos testes, podemos ser somos notificados por e-mail e, sendo isso parte do fluxo do CI, teoricamente, nossas últimas atualizações serão um bloqueio para continuar nosso trabalho em outra coisa.

Eu tenho preferido passar essas ferramentas antes de fazer o commit, aproveitando que estou com a mente fresca com as minhas últimas atualizações, ficando mais fácil resolver o problema detectado por alguma dessas ferramentas.

Com isso, resolver colocar o git hook pre_commit. No caso em questão, estou em um projeto Ruby on Rails e nele estão configurados algumas ferramentas de análise de código, como brakeman, rucobop, rails_best_practices e rubycritic.

Segue abaixo o hook pre-commit:

Outras referências:

https://blog.tinogomes.com/2016/12/29/usando-git-hook-para-fazer-analise-do-codigo
[Dicas] Bash - Aprendendo a usar XARGS de uma vez por todas
Show full content

Depois da milésia vez buscando por como usar o xargs resolvi escrever esse post para não precisar mais.

O comando xargs é utilizado para, dado uma lista de argumentos, executar um comando passando essa lista como argumento(s) desse comando. Então, você pode juntar todos os itens da lista para serem passados como argumentos de um comando, ou para que se execute um comando por item da lista. Por padrão, cada termo separado por espaço é considerado um item de lista e toda a lista é passada como argumentos para o comando.

Sintaxe com os parâmetros que mais eu uso:

command-list | xargs [-0 | -n NUM] [-I NAME] [-p] [command-exec [args]]
Argumentos Descrição command-list Qualquer comando que gere uma lista. Pode ser ls, cat, echo, find e etc… -0 Muda o separador padrão para o caracter nulo. Muito útil quando os itens da lista possuem espaço. -n NUM Pega NUM itens da lista para executar o comando. -I NAME Dá um nome para o(s) item(ns) a ser passado para o comando. -p Exibe o comando com os argumentos que serão executados e pede confirmação para executar o comando. command-exec [args] Comando a ser executado com os argumentos lidos pelo xargs. Por padrão, é usado um echo

Exemplos:

    $ seq -s ' item\n' 3 | xargs
    1 item 2 item 3 item

    $ seq -s ' item\n' 3 | xargs -0
    item 1
    item 2
    item 3

    $ seq -s ' item\n' 3 | xargs -n 1
    1
    item
    2
    item
    3
    item

    $ seq -s ' item\n' 3 | xargs -n 2
    item 1
    item 2
    item 3

    $ seq -s ' item\n' 3 | xargs -n 3
    1 item 2
    item 3 item

    $ seq -s ' item\n' 3 | xargs -p
    /bin/echo 1 item 2 item 3 item?...

    $ seq -s ' item\n' 3 | xargs -INUM echo NUM arg
    1 item arg
    2 item arg
    3 item arg

Agora espero não mais buscar por isso.

Segue algumas referências que usei:

https://blog.tinogomes.com/2016/10/20/dicas-bash-aprendendo-a-usar-xargs-de-uma-vez-por-todas
Novos desafios: Gaveteiro. Obrigado Hotel Urbano
Show full content

Obrigado HU

Hoje (30/abril/2015) foi meu último dia no Hotel Urbano (HU). Muita coisa mudou em menos de um ano, onde era uma empresa que era 100% PHP e hoje trabalha com várias tecnologias, como PHP, Ruby, Python, NodeJS, Scala e Java. Tive o prazer de trabalhar em diversas equipes, com diversas pessoas, tudo gente boa, tanto no pessoal como no profissional. Um obrigado especial ao Bruno que me convidou para me juntar ao HU a um ano atrás. Desejo todo sucesso a todos e ao HU (uhull - \o/)

Um novo ciclo começa e com isso, um novo desafio. Estou me juntando ao time do Gaveteiro, a convite do Luiz Rocha.

https://blog.tinogomes.com/2015/04/30/novos-desafios-gaveteiro-obrigado-hotel-urbano
Criando gemset e arquivos de carregamento do RVM
Show full content

Essa é mais para ficar para lembrança do que uma dica, mas enfim, sempre que vou começar um novo projeto em Ruby, eu costumo a usar o RVM (sim, ainda o RVM) e gosto de separar os projetos por gemsets (sim, isso também ocupa mais espaço).

Para criar um novo gemset, na versão do Ruby correta e os arquivos de carregamento automático, executo o comando abaixo:

$ rvm 2.2.1@project_name --create --ruby-version

Pronto!

https://blog.tinogomes.com/2015/03/23/criando-gemset-e-arquivos-de-carregamento-do-rvm
[Dicas] Bash - Usando Array e criando arquivos temporários
Show full content

As dicas abaixo foram extraídas do post 10 tips for writing efficient Bash scripts.

Usar Array em lugar de múltiplas variáveis

Ao invés de criar muitas variáveis para valores em um mesmo contexto.

1
2
3
4
5
6
color1='Red'
color2='Green'
color3='Blue'

echo $color1
echo $color2

Crie um array.

1
2
3
4
$colors=('Red' 'Green' 'Blue')

echo ${colors[0]}
echo ${colors[1]}
Criar arquivos/diretórios temporários

Precisa de um arquivo temporário? Use mktemp para criar arquivos ou diretórios temporários.

1
2
3
4
5
tempfile=$(mktemp)
tempdir=$(mktemp -d)

echo $tempfile
echo $tempdir

PS: No FreeBSD (OSX), tem que passar um template para o nome do arquivo/diretório

https://blog.tinogomes.com/2015/02/04/dicas-bash-array-e-arquivos-temporarios
Hotel Urbano aí vou eu
Show full content

Nesta minha última saga em busca de uma nova oportunidade, entre conversas em várias empresas, startups e amigos, minha decisão foi ingressar no Hotel Urbano, a convite do Bruno, onde pessoalmente o grande desafio será a tecnologia, pois a linguagem base usada no HU é PHP e nos últimos 7 anos venho trabalhando com Ruby (ou rãby?). Também serão outros desafios que no momento não estou habilitado a compartilhar, mas esperamos a curto prazo compartilhamos o sucesso desses desafios.

Então, este post é de agradecimento a todos que me indicaram, outros que se despuseram em me ouvir oferecendo uma oportunidade de me juntar a seus negócios e, principalmente, ao Bruno por efetivamente me aceitar a unir ao time do HU.

PS: Espero não fazer PHuby on PHails :P

https://blog.tinogomes.com/2014/05/10/hotel-urbano-ai-vou-eu
Criando branches no git muito mais rápido
Show full content

Cansei de criar git branches assim:

$ git checkout -b fix/some_bugfix_with_long_name

Agora é só:

$ fix-branch some bugfix with long name

Segue abaixo minhas novas funções bash:

function fix-branch() {
    local new_branch_name=$(echo "$*" | tr " " _)
    git checkout -b fix/$new_branch_name
}

function feature-branch() {
    local new_branch_name=$(echo "$*" | tr " " _)
    git checkout -b feature/$new_branch_name
}
https://blog.tinogomes.com/2014/03/21/dica-criando-branches-no-git
Luto por Jim Weirich
Show full content

Mudei o tema do blog apenas por luto pelo falecimento de Jim Weirich, criador do Rake e outras ferramentas incriveis para Ruby. Também rolou uma homenagem da comunidade no último commit do Jim no github.

Tema de luto

Descanse em paz e força aos familiares!

https://blog.tinogomes.com/2014/02/20/luto-por-jim-weirich
Busca em campos de texto passando valores numéricos no MySQL
Show full content

Como em toda casa de ferreiro, nem sempre o espero é de ferro.

Em nosso dia a dia, as vezes precisamos fazer umas intervenções via console do Rails e, com isso, temos alguns métodos auxiliares para fazer tarefas simples, porém rotineiras.

Entre essas rotinas, fazemos busca de uma conta através do e-mail de um usuário e então atualizamos a informação necessária. Como as vezes temos somente o e-mail do usuário, precisamos fazer uma busca do usuário pelo e-mail e, dado o usuário, obtemos a lista de contas e então, temos a conta para fazer o ajuste. Outras vezes já temos o ID da conta a ser ajustada, o que agiliza o processo. Para auxiliar essa busca, estava criando mais um método para encontrar a conta, dado o e-mail do usuário, algo como:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# ./lib/console_helpers.rb
module ConsoleHelper
  def update_info(id_or_email, new_data = {})
    account = Account.find_by_id(id_or_email)
    unless account
      user = User.find_by_email!(id_or_email)
      raise "User have more than 1 account: #{user.account_ids}" if user.accounts.count > 1
      account = user.accounts.first
    end
    account.update_info(new_date)
    puts "New data for account##{account.id}: #{new_date.inspect}"
  end
end

# ./config/application.rb
console do
  Rails::ConsoleMethods.send :include, ::ConsoleHelpers
end

Neste caso, quando entro no console, posso facilmente fazer:

=> update_info 'johndoe@domain.com', {attribute: 'new_value'}
New data for account #123: {:attribute => 'new_value'}

Porém, fazendo os testes no console (após os testes automatizados, claro!), ao passar um número de ID que não existia (número zero), fui surpreendido com a atualização de uma conta que eu não esperava.

=> update_info 0, {attribute: 'new_value'}
New data for account #456: {:attribute => 'new_value'}

O que notei, foi que a busca do MySQL retornou um usuário, cujo o e-mail dele era 00022e34f23a72@domain.com, pois a query SQL criada foi:

 SELECT `users`.* FROM `users` WHERE `users`.`email` = 0 ORDER BY email ASC LIMIT 1;

Ou seja, o MySQL tem o mesmo comportamento do Ruby, ao tentar converter uma string para número.

=> '102-string'.to_i # results: 102
102

Logo, fiz o ajuste na função para converter para string na busca por e-mail.

1
2
3
4
5
6
7
8
9
10
11
12
13
# ./lib/console_helpers.rb
module ConsoleHelper
  def update_info(id_or_email, new_data = {})
    account = Account.find_by_id(id_or_email)
    unless account
      user = User.find_by_email!(id_or_email.to_s)
      raise "User have more than 1 account: #{user.account_ids}" if user.accounts.count > 1
      account = user.accounts.first
    end
    account.update_info(new_date)
    puts "New data for account##{account.id}: #{new_date.inspect}"
  end
end

Não caia da mesma armadilha.

https://blog.tinogomes.com/2013/12/23/busca-em-campos-de-texto-passando-valores-numericos-no-mysql
Últimas nem tão novidades assim e agradecimentos
Show full content

Em agosto, minha família e eu, voltamos a morar na Cidade Maravilhosa e desde então, estou trabalhando na Myfreecomm, no produto MyFinance.

Com isso, deixamos para trás a Terra da Garoa e muito amigos que vão deixar saudades do convívio diário, principalmente os com espírito Webco, que já espalharam mundo afora.

Quero agradecer a toda família Balbino todos que conviveram comigo durante esses 5 anos paulistamos, em especial: Rocha, Nando, Zagari, Cipriani, Pluguinho, MV, Fais, GB, Hungaro, Shiota, Bibinha, Fabinho.

Esse mundo é pequeno e em breve estaremos juntos novamente…

https://blog.tinogomes.com/2013/11/22/ultimas-nem-tao-novidades-assim-e-agradecimentos
Testando seu código em várias versões de dependência
Show full content

ATUALIZAÇÃO 1: Se você, como nós, tem uma gem pública, existe a possibilidade de usar o Travis CI para verificação em várias versões de Rails e Rubies.

ATUALIZAÇÃO 2: deve-se usar o comando DO do RVM https://rvm.io/set/do/

Preciso verificar se o Brazilian Rails funciona em várias versões do Rails, 2.3.x, 3.0.x, 3.1.x e 3.2.x e Ruby 1.8.7 e 1.9.x. Como faz?

Com RVM, seria:

$ rvm 1.8.7@rails23x,1.8.7@rails30x,1.8.7@rails31x,1.8.7@rails32x,[até],1.9.3@rails32x do rake

Claro, existe um script automatizando isto, mas ainda sim é feio d+.

Uma solução, seria usar o infinity_test, mas o Brazilian Rails tem testes em TestUnit e também em RSpec e como o infity_test é para execução em apenas um “ambiente”, não sei se irá funcionar (não testei)

Outra é usar o Appraisal, que se integra com o Bundler e Rake, o que fica bastante flexível.

Seguindo os passos conforme indicado no README do projeto, no meu ambiente fico com apenas um gemset por versão de Ruby.

$ rvm 1.8.7@brazilianrails,1.9.1@brazilianrails,1.9.2@brazilianrails,1.9.3@brazilianrails do rake test_spec

Para quem não separa as instalações por gemset, usando apenas o default, fica ainda mais limpo.

$ rvm 1.8.7,1.9.1,1.9.2,1.9.3 do rake test_spec

E se seguir a sugestão de David Czarneck, basta criar uma tarefa Rake,

1
2
3
4
5
6
7
8
9
10
11
# Rakefile
desc "Ruby test units and RSpec"
task :test_spec do
  Rake::Task.execute["test"]
  Rake::Task.execute["spec"]
end

desc "Runs tests on Ruby 1.8.7 and 1.9.2"
task :test_all do
  system "rvm 1.8.7@brazilianrails,1.9.1@brazilianrails,1.9.2@brazilianrails,1.9.3@brazilianrails do rake test_spec"
end

e executar:

$ rake test_all

Referências:

https://blog.tinogomes.com/2012/06/04/testando-seu-cdigo-em-vrias-verses-de-dependncia
Como configurar o timezone no Heroku
Show full content

Basta apenas adicionar uma configuração, em uma variável de ambiente

$ heroku config:add TZ=America/Sao_Paulo
Adding config vars and restarting app... done, v24
  TZ => America/Sao_Paulo
$ heroku run console
Running console attached to terminal... Time.oup, run.1
Time.now
irb(main):001:0> Time.now
=> 2012-05-14 09:51:51 -0300

A lista de timezones disponíveis está na Wikipedia.

Referências:

https://blog.tinogomes.com/2012/05/14/como-configurar-o-timezone-no-heroku
Git na porta SSH diferente da padrão
Show full content

Digamos que por motivo de segurança, resolveram mudar o número da porta SSH e você não consegue mais fazer um simples git pull então, basta adicionar o prefixo do protocolo ssh:// e então adicionar o número da porta. Considerando que a porta agora é 2222.

$ git clone ssh://user@git.repo:2222/path/repo.git

Referências:

https://blog.tinogomes.com/2012/04/03/git-na-porta-ssh-diferente-da-padrao