Feature #14564


`dig` opposite method

Added by nilcolor (Aleksey Blinov) over 4 years ago. Updated about 1 month ago.

Target version:


We have nice dig method that helps a lot.
Though we didn't have an opposite method that allows setting a value.
I know we already have these:
Both were closed because of name or lack of use cases. Let me promote the new name for this:

class Hash
  def expand(*where, value)
    where[0..-2].reduce(self) { |h, key|
      h[key] = h[key] || {}
    }[where[-1]] = value

{}.expand(:a, :b, :c, 42)                 # => {:a=>{:b=>{:c=>42}}}
{}.expand(:a, 0, :c, 42)                  # => {:a=>{0=>{:c=>42}}}
{a: {}}.expand(:a, :b, :c, 42)            # => {:a=>{:b=>{:c=>42}}}
{a: {b: nil}}.expand(:a, :b, :c, 42)      # => {:a=>{:b=>{:c=>42}}}
{a: {foo: "bar"}}.expand(:a, :b, :c, 42)  # => {:a=>{:foo=>"bar", :b=>{:c=>42}}}
{a: {b: "wat"}}.expand(:a, :b, :c, 42)    # => TypeError: no implicit conversion of Symbol into Integer

class Array
  def expand(*where, value)
    where[0..-2].reduce(self) { |a, idx|
      a[idx] = a[idx] || []
    }[where[-1]] = value

[].expand(2, 1, 3, "?")              # => [nil, nil, [nil, [nil, nil, nil, "?"]]]
[1, [0, 2], []].expand(1, 1, "BAM")  # => [1, [0, "BAM"], []]
[1, [0, 2], []].expand(2, 0, "BAM")  # => [1, [0, 2], ["BAM"]]

Use cases: working with deeply nested structures, used as parameters (params[:a][:nested][:some_id] = 42).
In general, I think it's mostly useful for Hashes. Though having this on Array may be useful as well.

Updated by shevegen (Robert A. Heiler) over 4 years ago

I have nothing against the functionality, but I think the name .expand()
is not a good one. When I read .expand, I think of the opposite of
flatten; or it reminds me of .extend.

I don't have a better name suggestion myself, though.

Updated by zverok (Victor Shepelev) over 4 years ago

Was already proposed as a "bury" (direct antonym to dig) and rejected: and

Matz's response:

  • "It's not clear to generate either Hash, Array, or Struct (or whatever) to bury a value.
    So it's better to reject now." to first and
  • "You have to come up with a better name candidate and concrete use-case." to second.

BTW, you may be interested to take a look at my experimental hm gem, which defines some declarative hash processing helpers, including bury. It, BTW, decides to generate Array on numeric bury key, and Hash on any other, but I understand that it could be too vague for some cases.

Hm({a: {foo: "bar"}}).bury(:a, :b, :c, 42).to_h
# => {:a=>{:foo=>"bar", :b=>{:c=>42}}} 

Hm({a: {b: "wat"}}).bury(:a, :b, :c, 42).to_h
# TypeError: String is not diggable

Hm([1, [0, 2], []]).bury(2, 0, "BAM").to_h # well, to_h is weird here, but works
# => [1, [0, 2], ["BAM"]]

Hm([]).bury(2, 1, 3, "?").to_h
# => [nil, nil, [nil, [nil, nil, nil, "?"]]] 

Updated by nilcolor (Aleksey Blinov) over 4 years ago

I know about those 2 proposals. I references them in the description )

Name is a hard topic. As for types - I'd say that having same type as a receiver is enough.
So, {}.whatever_name(*) will generate nested Hashes. [].whatever_name(*) - Arrays. This will cover the majority of cases.

Names... Personally, I find bury nice ) Though a bit of horrory. expand - yeah, maybe not that good.

What about "ruin", "embed", or "melt"?

Updated by phluid61 (Matthew Kerwin) over 4 years ago

Clearly the only accurate name for this method is store_recursive_with_autovivification

A lot of people ask for it, but I still can't see how this is a good thing to add to the language. In your personal code, sure -- maybe even in a gem -- but not the core.

Updated by professor (Todd Sedano) about 1 month ago

Often my team needs to modify deep hash structures and we created another implementation of the bury method.

We suggested our code as a modification to Hash in ActiveSupport PR. First, we wanted to verify that the ruby language does not want a bury method on a Hash.

I find the code in our PR easier to understand than the implementation suggested in this issue 14564 and in 13179.

Actions #6

Updated by chrisseaton (Chris Seaton) about 1 month ago

Instead of different dig and bury tools, a 'lens' abstraction could combine the two.

deep_hash = {a: {b: {c: {d: 100}}}}

p deep_hash.dig(:a, :b, :c, :d)

class Lens
  def self.lens(*keys)
    lens =
    lens =, lens) until keys.empty?

  class Node
    def initialize(key, child)
      @key = key
      @child = child

    def get(object)
      @child.get object[@key]

    def set(object, value)
      @child.set object[@key], value

  class Leaf
    def initialize(key)
      @key = key

    def get(object)

    def set(object, value)
      object[@key] = value

lens = Lens.lens(:a, :b, :c, :d)
p lens.get(deep_hash)
lens.set deep_hash, 14
p lens.get(deep_hash)

Also available in: Atom PDF