ruby-changes:62570
From: Burdette <ko1@a...>
Date: Fri, 14 Aug 2020 03:16:42 +0900 (JST)
Subject: [ruby-changes:62570] 22fd617aa5 (master): Adding doc/dig_methods.rdoc and links to it (#3416)
https://git.ruby-lang.org/ruby.git/commit/?id=22fd617aa5 From 22fd617aa5a8dd9c8426a546e0cb8a64b45c230b Mon Sep 17 00:00:00 2001 From: Burdette Lamar <BurdetteLamar@Y...> Date: Thu, 13 Aug 2020 13:16:27 -0500 Subject: Adding doc/dig_methods.rdoc and links to it (#3416) Adds a full discussion of #dig, along with links from Array, Hash, Struct, and OpenStruct. CSV::Table and CSV::Row are over in ruby/csv. I'll get to them soon. The art to the thing is to figure out how much (or how little) to say at each #dig. diff --git a/array.c b/array.c index a07757f..6c667a3 100644 --- a/array.c +++ b/array.c @@ -8639,19 +8639,20 @@ rb_ary_one_p(int argc, VALUE *argv, VALUE ary) https://github.com/ruby/ruby/blob/trunk/array.c#L8639 } /* - * call-seq: - * ary.dig(idx, ...) -> object - * - * Extracts the nested value specified by the sequence of <i>idx</i> - * objects by calling +dig+ at each step, returning +nil+ if any - * intermediate step is +nil+. + * call-seq: + * array.dig(index, *identifiers) -> object * - * a = [[1, [2, 3]]] + * Finds and returns the object in nested objects + * that is specified by +index+ and +identifiers+. + * The nested objects may be instances of various classes. + * See {Dig Methods}[doc/dig_methods_rdoc.html]. * - * a.dig(0, 1, 1) #=> 3 - * a.dig(1, 2, 3) #=> nil - * a.dig(0, 0, 0) #=> TypeError: Integer does not have #dig method - * [42, {foo: :bar}].dig(1, :foo) #=> :bar + * Examples: + * a = [:foo, [:bar, :baz, [:bat, :bam]]] + * a.dig(1) # => [:bar, :baz, [:bat, :bam]] + * a.dig(1, 2) # => [:bat, :bam] + * a.dig(1, 2, 0) # => :bat + * a.dig(1, 2, 3) # => nil */ static VALUE diff --git a/doc/dig_methods.rdoc b/doc/dig_methods.rdoc new file mode 100644 index 0000000..366275d --- /dev/null +++ b/doc/dig_methods.rdoc @@ -0,0 +1,82 @@ https://github.com/ruby/ruby/blob/trunk/doc/dig_methods.rdoc#L1 += Dig Methods + +Ruby's +dig+ methods are useful for accessing nested data structures. + +Consider this data: + item = { + id: "0001", + type: "donut", + name: "Cake", + ppu: 0.55, + batters: { + batter: [ + {id: "1001", type: "Regular"}, + {id: "1002", type: "Chocolate"}, + {id: "1003", type: "Blueberry"}, + {id: "1004", type: "Devil's Food"} + ] + }, + topping: [ + {id: "5001", type: "None"}, + {id: "5002", type: "Glazed"}, + {id: "5005", type: "Sugar"}, + {id: "5007", type: "Powdered Sugar"}, + {id: "5006", type: "Chocolate with Sprinkles"}, + {id: "5003", type: "Chocolate"}, + {id: "5004", type: "Maple"} + ] + } + +Without a +dig+ method, you can write: + item[:batters][:batter][1][:type] # => "Chocolate" + +With a +dig+ method, you can write: + item.dig(:batters, :batter, 1, :type) # => "Chocolate" + +Without a +dig+ method, you can write, erroneously +(raises <tt>NoMethodError (undefined method `[]' for nil:NilClass)</tt>): + item[:batters][:BATTER][1][:type] + +With a +dig+ method, you can write (still erroneously, but avoiding the exception): + item.dig(:batters, :BATTER, 1, :type) # => nil + +== Why Is +dig+ Better? + +- It has fewer syntactical elements (to get wrong). +- It reads better. +- It does not raise an exception if an item is not found. + +== How Does +dig+ Work? + +The call sequence is: + obj.dig(*identifiers) + +The +identifiers+ define a "path" into the nested data structures: +- For each identifier in +identifiers+, calls method \#dig on a receiver + with that identifier. +- The first receiver is +self+. +- Each successive receiver is the value returned by the previous call to +dig+. +- The value finally returned is the value returned by the last call to +dig+. + +A +dig+ method raises an exception if any receiver does not respond to \#dig: + h = { foo: 1 } + # Raises TypeError (Integer does not have #dig method): + h.dig(:foo, :bar) + +== What Else? + +The structure above has \Hash objects and \Array objects, +both of which have instance method +dig+. + +Altogether there are six built-in Ruby classes that have method +dig+, +three in the core classes and three in the standard library. + +In the core: +- Array#dig: the first argument is an \Integer index. +- Hash#dig: the first argument is a key. +- Struct#dig: the first argument is a key. + +In the standard library: +- OpenStruct#dig: the first argument is a \String name. +- CSV::Table#dig: the first argument is an \Integer index or a \String header. +- CSV::Row#dig: the first argument is an \Integer index or a \String header. diff --git a/hash.c b/hash.c index f6581ac..329aa33 100644 --- a/hash.c +++ b/hash.c @@ -5080,53 +5080,19 @@ rb_hash_any_p(int argc, VALUE *argv, VALUE hash) https://github.com/ruby/ruby/blob/trunk/hash.c#L5080 /* * call-seq: - * hash.dig(*keys) -> value + * hash.dig(key, *identifiers) -> object * - * Returns the value for a specified object in nested objects. - * - * For nested objects: - * - For each key in +keys+, calls method \#dig on a receiver. - * - The first receiver is +self+. - * - Each successive receiver is the value returned by the previous call to \#dig. - * - The value finally returned is the value returned by the last call to \#dig. + * Finds and returns the object in nested objects + * that is specified by +key+ and +identifiers+. + * The nested objects may be instances of various classes. + * See {Dig Methods}[doc/dig_methods_rdoc.html]. * * Examples: - * h = {foo: 0} - * h.dig(:foo) # => 0 - * - * h = {foo: {bar: 1}} - * h.dig(:foo, :bar) # => 1 - * * h = {foo: {bar: {baz: 2}}} + * h.dig(:foo) # => {:bar=>{:baz=>2}} + * h.dig(:foo, :bar) # => {:bar=>{:baz=>2}} * h.dig(:foo, :bar, :baz) # => 2 - * - * Returns +nil+ if any key is not found: - * h = { foo: {bar: {baz: 2}}} - * h.dig(:foo, :nosuch) # => nil - * - * The nested objects may include any that respond to \#dig. See: - * - Hash#dig - * - Array#dig - * - Struct#dig - * - OpenStruct#dig - * - CSV::Table#dig - * - CSV::Row#dig - * - * Example: - * h = {foo: {bar: [:a, :b, :c]}} - * h.dig(:foo, :bar, 2) # => :c - * - * --- - * - * Raises an exception if any given key is invalid - * (see {Invalid Hash Keys}[#class-Hash-label-Invalid+Hash+Keys]): - * # Raises NoMethodError (undefined method `hash' for #<BasicObject>) - * h.dig(BasicObject.new) - * - * Raises an exception if any receiver does not respond to \#dig: - * h = { foo: 1 } - * # Raises TypeError: Integer does not have #dig method - * h.dig(:foo, 1) + * h.dig(:foo, :bar, :BAZ) # => nil */ static VALUE diff --git a/lib/ostruct.rb b/lib/ostruct.rb index e062fbd..7192a07 100644 --- a/lib/ostruct.rb +++ b/lib/ostruct.rb @@ -255,26 +255,20 @@ class OpenStruct https://github.com/ruby/ruby/blob/trunk/lib/ostruct.rb#L255 modifiable?[new_ostruct_member!(name)] = value end - # # :call-seq: - # ostruct.dig(name, ...) -> object + # ostruct.dig(name, *identifiers) -> object # - # Extracts the nested value specified by the sequence of +name+ - # objects by calling +dig+ at each step, returning +nil+ if any - # intermediate step is +nil+. + # Finds and returns the object in nested objects + # that is specified by +name+ and +identifiers+. + # The nested objects may be instances of various classes. + # See {Dig Methods}[doc/dig_methods_rdoc.html]. # + # Examples: # require "ostruct" # address = OpenStruct.new("city" => "Anytown NC", "zip" => 12345) # person = OpenStruct.new("name" => "John Smith", "address" => address) - # - # person.dig(:address, "zip") # => 12345 - # person.dig(:business_address, "zip") # => nil - # - # data = OpenStruct.new(:array => [1, [2, 3]]) - # - # data.dig(:array, 1, 0) # => 2 - # data.dig(:array, 0, 0) # TypeError: Integer does not have #dig method - # + # person.dig(:address, "zip") # => 12345 + # person.dig(:business_address, "zip") # => nil def dig(name, *names) begin name = name.to_sym diff --git a/struct.c b/struct.c index 0ff0ec3..efd6bc0 100644 --- a/struct.c +++ b/struct.c @@ -1328,18 +1328,21 @@ rb_struct_size(VALUE s) https://github.com/ruby/ruby/blob/trunk/struct.c#L1328 /* * call-seq: - * struct.dig(key, ...) -> object + * struct.dig(key, *identifiers) -> object * - * 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+. + * Finds and returns the object in nested objects + * that is specified by +key+ and +identifiers+. + * The nested objects may be instances of various classes. + * See {Dig Methods}[doc/dig_methods_rdoc.html]. * + * Examples: * Foo = Struct.new(:a) * f = Foo.new(Foo.new({b: [1, 2, 3]})) - * - * f.dig(:a, :a, :b, 0) # => 1 - * f.dig(:b, 0) # => nil - * f.dig(:a, :a, :b, :c) # TypeError: no implicit conversion of Symbol into Integer + * f.dig(:a) # => #<struct Foo a={:b=>[1, 2, 3]}> + * f.dig(:a, :a) # => {:b=>[1, 2, 3]} + * f.dig(:a, :a, :b) # => [1, 2, 3] + * f.dig(:a, :a, :b, 0) # => 1 + * f.dig(:b, 0) # => nil */ static VALUE -- cgit v0.10.2 -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/