summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--array.c23
-rw-r--r--doc/dig_methods.rdoc82
-rw-r--r--hash.c50
-rw-r--r--lib/ostruct.rb22
-rw-r--r--struct.c19
5 files changed, 121 insertions, 75 deletions
diff --git a/array.c b/array.c
index a07757f210..6c667a340f 100644
--- a/array.c
+++ b/array.c
@@ -8639,19 +8639,20 @@ rb_ary_one_p(int argc, VALUE *argv, VALUE ary)
}
/*
- * 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 0000000000..366275d451
--- /dev/null
+++ b/doc/dig_methods.rdoc
@@ -0,0 +1,82 @@
+= 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 f6581acfd2..329aa335c6 100644
--- a/hash.c
+++ b/hash.c
@@ -5080,53 +5080,19 @@ rb_hash_any_p(int argc, VALUE *argv, VALUE hash)
/*
* 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 e062fbdc9c..7192a0731c 100644
--- a/lib/ostruct.rb
+++ b/lib/ostruct.rb
@@ -255,26 +255,20 @@ class OpenStruct
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 0ff0ec3722..efd6bc0177 100644
--- a/struct.c
+++ b/struct.c
@@ -1328,18 +1328,21 @@ rb_struct_size(VALUE s)
/*
* 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