Desde a versão 2.3, a linguagem de programação Ruby disponibilizou um novo método para as classes Array e Hash . O propósito desse método é, como o nome sugere, “escavar” objetos que podem não possuir uma estrutura muito bem definida (como objetos de API’s de terceiros, por exemplo). Basicamente essa funcionalidade retorna o valor recuperado dado um conjunto de chaves para vasculhar. Se o valor não for encontrado, é retornado nulo. Abaixo demonstro alguns exemplos de aplicação.
Rails – parâmetros
Quando você manipula valores de formulários e seus atributos, eventualmente terá que lidar com hashes aninhadas. Vamos supor que você queira converter os valores de checkboxes para inteiros:
1 2 3 4 |
def coerce_user_choices choices = params.dig(:user, :choices) || [] choices.map(&:to_i) end |
O código fica consideravelmente mais legível do que a forma extensa abaixo, especialmente considerando casos onde o nível do aninhamento é alto e essa extensão compromete a legibilidade.
1 |
params[:user] && params[:user][:choices] || [] |
Lidando com API’s
Imagine que você está recebendo os seguintes valores de uma API JSON:
1 2 3 4 5 6 7 8 9 10 11 12 |
{ person: { attributes: { address: {https://tiagoamaro.com.br/2016/08/27/ruby-2-3-dig/ street: "Abbey Road", country: { name: "Tibet", } } } } } |
Com o método #dig você pode navegar pela estrutura sem se preocupar com valores nulos ou atributos inválidos, sem a necessidade de lidar com exceções do objeto de resposta. Suponhamos que você quer selecionar alguns dados do objeto e apresentar na sua view:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
module Presenter class Person attr_reader :json PERSON_ATTRS = [:person, :attributes] COUNTRY_NAME = PERSON_ATTRS + [:address, :country, :name] STREET_NAME = PERSON_ATTRS + [:address, :street] def initialize(person_json) @json = person_json end def country json.dig(*COUNTRY_NAME) end def street json.dig(*STREET_NAME) end end end |
1 2 3 |
presenter = Presenter::Person.new(person_json) presenter.country # => "Tibet" presenter.street # => "Abbey Road" |
Fácil, certo? E muito mais elegante do que usar uma série de métodos #try do ActiveSupport . É apeans Ruby, nativo!
E quanto ao desempenho?
Vamos comparar o método #dig com velhas formas de acessar os valores em Hashes e Arrays:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
require 'benchmark/ips' Benchmark.ips do |x| example_hash = { a: { b: { c: 3 } } } example_array = [1, [2, [3]]] x.report("Array#dig found") { example_array.dig(1, 1, 0) } x.report("Array#dig not found") { example_array.dig(1, 1, 1, 1) } x.report("Array navigation found") { example_array[1][1][0] } x.report("Array navigation not found") { example_array[1][1][1] } x.report("Hash#dig found") { example_hash.dig(:a, :b, :c) } x.report("Hash#dig not found") { example_hash.dig(:a, :b, :foo, :bar) } x.report("Hash navigation found") { example_hash[:a][:b][:c] } x.report("Hash navigation not found") { example_hash[:a][:b][:d] } x.compare! end |
1 2 3 4 5 6 7 8 9 |
Array navigation not found: 10120102.0 i/s Array navigation found: 9955150.5 i/s Array#dig found: 7998485.3 i/s - 1.27x mais lento Array#dig not found: 7855449.0 i/s - 1.29x mais lento Hash navigation not found: 7713907.2 i/s - 1.31x mais lento Hash navigation found: 7582034.4 i/s - 1.33x mais lento Hash#dig found: 6896451.4 i/s - 1.47x mais lento Hash#dig not found: 6483433.9 i/s - 1.56x mais lento |
No pior cenário, o método demanda 1.5x mais tempo do que o acesso direto. Levando-se em consideração o número de operações por segundo do processamento X o tamanho do objeto, a performance não se mostra um empecilho, especialmente considerando o ganho em versatilidade. Quaisquer dúvidas ou sugestões, utilize a área de comentários ou entre em contato!
Nenhum Comentário