Estava precisando de gerar uma estrutura de dados dinamicamente sem preocupar se o nome do atributo
existe, se ele está vazio, qual o tipo do atributo, etc. Antes de apresentar uma solução vamos lembrar do Hash
Hash
defini key e value em um dicionário.
h={}
h[:chave]="valor"
em um Hash nativo do ruby a priori não temos a possibilidade de acessar os valores através dos métodos
h.chave
um erro do tipo NoMethodError: undefined method `chave’ for {}:Hash
é lançado informando que não há método que responda a chave .
Com o intuito de facilitar a definição e leitura dos valores do Hash criei um módulo chamado HashUtil.
Baixe aqui o hash_util.rb (gzipped)
Forma clássica de atribuição
h={}
h[:usuario]="nome do usuario"
h.store(:usuario, "nome do usuario")
h['usuario']="nome do usuario"
Usando HashUtil
Pode fazer um extend do módulo na instância
h={}
h.extend(HashUtil)
ou adicionar o módulo diretamente no Hash, Criando uma classe Hash e inclindo HashUtil, assim
class Hash
include HashUtil
end
Para os exemplos subjacentes utilizaremos a classe Hash especializada com include HashUtil.
Formas de atribuição
h={}
h.usuario="nome do usuario"
h.tipo="pessoa física"
h.sexo="masculino"
verifique com inspect os valores
{'usuario'=>"nome do usuario", 'sexo'=>"masculino", 'tipo'=>"pessoa fisica"}
Os valores podem ser lidos chamando apenas pela nome da chave
h.usuario
h['usuario']
ou re-atribuir outro valor utilizando o =
Forma dinâmica e aninhada
O HashUtil utiliza internamente regras simples.
1 - se for solicitado uma chave que não existe, aponte para uma nova instância de Hash e retorne nil.
2 - se for atribuido um valor para uma chave que não existe, crie uma nova chave apontando para o valor.
3 - tenta detectar se a chave é String ou Symbol automaticamente
Criaremos uma estrutura semelhante a de diretórios com arquivos(apenas para exemplificar).
Veja como é fácil.
raiz={}
raiz.usr.include.sys="mount.h"
raiz.etc.apache.conf="httpd.conf"
raiz.etc.cron.daily="logrotate"
raiz.inspect
{"etc"=>{"cron"=>{"daily"=>"logrotate"}, "apache"=>{"conf"=>"httpd.conf"}},
"usr"=>{"include"=>{"sys"=>"mount.h"}}}
Observe que o caminho informado pelo programador será criado automaticamente
Mas gostaríamos agora de adicionar vários diretórios no hash raiz.usr.include, vamos a prática:
raiz={}
raiz.usr.include
raiz.usr.include.sys
raiz.usr.include.linux
raiz.usr.include.linuxthreads
raiz.usr.include.sys.video="vga"
raiz.usr.include.sys.sound="alsa"
raiz.usr.include.linux.kernel="mini_kernel.h"
Estamos colocando os valores como String mas aceita quaisquer outros objetos, é um hash normal(hackeado).
Mas esses diretórios aí em cima só podem ter uma arquivo?
Daquela forma sim.
Mas podemos trocar
raiz.usr.include.sys="mount.h"
Por algo mais inteligente. No HashUtil temos um método append que:
- se não tiver a chave cria e atribui o valor
- se tiver a chave e não for array reempacota tudo dentro de um array
O append
Indique o hash e adicione a chave e o valor
raiz={}
raiz.usr.include
raiz.usr.include.append(:sys, 'mount.h')
raiz.usr.include.append(:sys, 'pci.h')
raiz.usr.include.append(:sys, 'perm.h')
raiz.usr.include.append(:linux, 'kernel.h')
Com isso os valores do hash raiz.usr.include.sys podem ser lidos
raiz.usr.include.sys
ou atribuidos
raiz.usr.include.sys << "mais um valor"
Gerando um YAML
Para gerar um yaml é só invocar o to_yaml
Exemplo utilizando a estrutura acima.
raiz.to_yaml
gera a saída
---
usr:
include:
linux: kernel.h
sys:
- mount.h
- pci.h
- perm.h
o método to_yaml e todos os outro métodos nativos da classe Hash são preservados pelo HashUtil.
Gosto de utilizar o HashUtil para armazenar e recuperar informações de configuração é fácil e intuitivo.