usados estão exemplifica-
dos em seguida:
frequency = { "hello" => 1, "world" => 2 }
frequency.keys
# ["hello", "world"]
frequency.values
# [1, 2]
frenquency.has_key?("hello")
# true
frequency.has_value?(3)
# false
Menção honrosa
É comum lidarmos com integrações com outros sistemas e APIs que retornam
construções complexas em hashes e quase sempre não são consistentes. Vejamos um
exemplo fictício abaixo:
20
Casa do Código
Capítulo 2. Conhecendo Ruby
user_data = {
'email' => 'cicrano@example.com',
'full_name' => 'Cicrano'
}
Imaginemos agora que queiramos acessar os atributos email, full_name e
address. address, porém, não está presente no exemplo de hash acima. Dessa
forma, ao fazermos o código abaixo, temos um problema:
address = user_data['address']
address.strip
# NoMethodError: undefined method `strip' for nil:NilClass
Por esse motivo, é bastante comum fazermos proteção contra nil usando uma
expressão idiomática. Dessa forma, nosso código fica mais limpo pois não temos
que ficar checando a existência de dados. Aplicando a expressão idiomática, temos
o resultado abaixo (para mais detalhes sobre essa expressão, veja na seção 2.4):
address = user_data['address'] || 'vazio'
address.strip
# "vazio"
Este caso é muito comum. Através do pouco usado método #fetch, temos uma
outra maneira de retornar um valor default, muito mais legível e sem problemas com
confusão de precedência de parâmetros:
address = user_data.fetch('address', 'vazio')
address.strip
# vazio
Notação para métodos
A comunidade Ruby adotou duas notações importantes quando se tra-
tam de documentação de métodos. Métodos começando com # indicam
que são aplicados à instâncias daquela classe, por exemplo Hash#fetch.
Métodos começando com : : ou . indicam métodos de classe, por exem-
plo Time::now.
O Ruby tem um comportamento às vezes indesejável de retornar sempre nil
quando a chave não existe:
21
2.3. Tipos e estrutura de dados
Casa do Código
user_data = {
'email' => 'cicrano@example.com',
'full_name' => 'Cicrano'
}
user_data['address']
# nil
Este caso é o que chamamos de falha silenciosa, ou seja, o código falhou, po-
rém, como não há erro, a execução do código segue adiante, ficando difícil perceber
quando há bugs. Dessa forma, quando fizer sentido tornar essa falha mais aparente,
podemos usar o #fetch sem fallback, disparando uma exceção KeyError quando a
chave não existe:
user_data = {
'email' => 'cicrano@example.com',
'full_name' => 'Cicrano'
}
user_data.fetch('address')
# KeyError: key not found: "address"
Símbolos
Símbolos são strings especiais. Símbolos são usados internamente pelo inter-
pretador MRI para localizar o método a ser executado em um objeto, portanto sua
implementação é tal que a torna imutável e única na instância do interpretador Ruby.
Ou seja, uma vez um símbolo mencionado e criado, ele vai existir por todo o período
de vida de execução do interpretador e nunca vai ser coletado pelo garbage collector:
a = "123"
b = "123"
a.object_id
# => 7022...7060
b.object_id
# => 7022...1360
a = :hello
b = :hello
22
Casa do Código
Capítulo 2. Conhecendo Ruby
a.object_id
# => 456328
b.object_id
# => 456328
O que é Garbage collector?
O garbage-collector é um mecanismo importante e complexo que faz
parte do interpretador do Ruby. Ele é capaz de rastrear todos os recursos
usados e não usados no sistema, de forma a liberar memória não utili-
zada ou alocar mais memória para seu programa quando necessário.
Em termos práticos, no Ruby 1.9.3, versão que usamos, isso não significa muita
coisa. Porém, símbolos possuem outro valor: são usados no lugar de strings para
serem identificadores especiais quando estamos nos referindo a métodos:
"string".respond_to? :upcase
# true
Também acontece para chaves de Hash, com
uma pequena diferença:
Símbolos devem ser usados quando nos tratamos de metadado e não dado em
si. O que isso quer dizer? Usamos símbolos quando estamos descrevendo o
tipo do dado e não dando um valor possível;
Strings devem ser usadas quando a chave é um valor e não um descritor de
dados.
Exemplificando as duas regras:
# Exemplo 1 - usando símbolos
{
:name => 'Fulano',
:email => 'fulano@example.com',
}
# Exemplo 2 - usando ambos
{
:people => {
'Fulano' => {
23
2.3. Tipos e estrutura de dados
Casa do Código
:email => 'fulano@example.com'
}
}
}
Podemos facilmente converter strings em símbolos e vice-versa:
"um_simbolo".to_sym
# :um_simbolo
:um_simbolo.to_s
# "um_simbolo"
"E se a palavra for muito grande?".to_sym
# :"E se a palavra for muito grande?"
# Ainda é possível usar a notação de string para fazer interpolação!
method = 'flatten'
:"#{method}!"
# :flatten!
Ranges
Já vimos Ranges algumas vezes em outros exemplos. Range é um tipo interes-
sante e simples. Um objeto Range possui um início, um fim e dois ou três pontos
entre eles:
1..5 # Números inteiros entre 1 a 5, com o 5 inclusive
1...5 # Números inteiros entre 1 a 4, o 5 fica de fora