Project

General

Profile

Feature #15563

Updated by 3limin4t0r (Johan Wentholt) about 5 years ago

Ruby 2.3.0 introduced `#dig` for *Array*, *Hash* and *Struct*. Both *Array* and *Hash* have `#fetch` which does the same as `#[]`, but instead of returning the default value an exception is raised (unless a second argument or block is given). *Hash* also has `#fetch_values` which does the same as `#values_at`, raising an exception if an key is missing. For `#dig` there is no such option. 

 My proposal is to add a method which does the same as `#dig`, but instead of using the `#[]` accessor it uses `#fetch`. 

 This method would look something like this: 

 ```Ruby 
 module Traversable DigWithException 
   def traverse(key, dig_e(key, *others) 
     value = fetch(key) 
     return value if value.nil? || others.empty? 

     if value.respond_to?(__method__, true) 
       value.send(__method__, *others) 
     else 
       raise TypeError, "#{value.class} does not have ##{__method__} method" 
     end 
   end 
 end 

 Array.include(Traversable) Array.include(DigWithException) 
 Hash.include(Traversable) Hash.include(DigWithException) 
 ``` 

 The exception raised is also taken from `#dig` (`[1].dig(0, 1) #=> TypeError: Integer does not have #dig method`). I personally have my issues with the name `#dig_e`, but I haven't found a better name yet. 

 There are also a few other things that I haven't thought out yet. 

  1. Should this method be able to accept a block which, will be passed to the `#fetch` call and recursive `#dig_e` calls?   

     ```Ruby 
 yaml 
     module DigWithException 
       def dig_e(key, *others, &block) 
         value = YAML.load_file('some_file.yml') 

 # fetch(key, &block) 
         return value if value.nil? || others.empty? 

         if value.respond_to?(__method__, true) 
           value.send(__method__, *others, &block) 
         else 
           raise TypeError, "#{value.class} does not have ##{__method__} method" 
         end 
       end 
     end 

     Array.include(DigWithException) 
     Hash.include(DigWithException) 
     ``` 

  2. I currently kept the code compatible with the `#dig` description. 

     > Extracts the nested value specified by the sequence of *key* objects by calling `dig` at each step, returning `nil` if any intermediate step is `nil`. 

     However, with this change new version of the method one could consider dropping the *"returning `nil` if any intermediate step is meant to make chaining #fetch calls more easy 
 yaml.fetch('foo').fetch('bar').fetch('baz') 

 # `nil`"* part, since this would be replaced with 
 yaml.traverse('foo', 'bar', 'baz') 
 the more strict version. 

     ```Ruby 
     module DigWithException 
       def dig_e(key, *others) 
         value = fetch(key) 
         return value if others.empty? 

         if value.respond_to?(__method__, true) 
           value.send(__method__, *others) 
         else 
           raise TypeError, "#{value.class} does not have ##{__method__} method" 
         end 
       end 
     end 

     Array.include(DigWithException) 
     Hash.include(DigWithException) 
     ``` 

 I'm curious to hear what you guys think about the idea as a whole, the method name and the two points described above. 
 

Back