module DeepDup

Deep duplicate any object. Some objects cannot be +dup+ped like nil, false, true, numbers, symbols and method objects. In those cases return themselves.

@example No monkey patching

dupped = DeepDup.deep_dup('chunky')
dupped = DeepDup.deep_dup(['chunky', [:bacon, { hi: 5 }]])
dupped = DeepDup.deep_dup(['a', :a, 1, { bacon: { chunky: 'yeah' } }])
dupped = DeepDup.deep_dup(SomeClass.new)

array = [1, 2]
array << array
dupped = DeepDup.deep_dup(array)

@example With monkey patching

require 'deep_dup/core_ext/object'

dupped = 'chunky'.deep_dup
dupped = ['chunky', [:bacon, { hi: 5 }]].deep_dup
dupped = ['a', :a, 1, { bacon: { chunky: 'yeah' } }].deep_dup
dupped = SomeClass.new.deep_dup

array = [1, 2]
array << array
dupped = array.deep_dup

Constants

VERSION

Version number, happy now?

Public Class Methods

cache_object(object, new_object, cache) { |new_object| ... } click to toggle source

Prevent infinite recursion on recursive data structures.

Imagine an array that has only one item which is a reference to itself. When entering this method, the cache is empty so we create a new array and map the original object's id to this newly created object.

We then give control back to deep_dup so that it can go on and do the adding, which will call itself with the same array and enter this method again.

But this time, since the object is the same, we know the duplicate object because we stored in in our little cache. So just go ahead and return it otherwise it would result in an infinite recursion.

@param object [Array, Hash] Original object reference. @param new_object [Array, Hash] The dupped object reference. @param cache [Hash] Map from original object_id to dupped object.

@yieldparam new_object [Array, Hash] The dupped object reference.

@return [Array, Hash] The dupped object.

# File lib/deep_dup.rb, line 90
def self.cache_object(object, new_object, cache)
  object_id     = object.object_id
  dupped_object = cache[object_id]      # Did we already visit this object?

  return dupped_object if dupped_object # Yes, return it, prevent overflow.

  cache[object_id] = new_object         # Map the original object id to the new object.
  yield(new_object)                     # Let +deep_dup+ do the job.
  new_object                            # Just return it to +deep_dup+.
end
deep_dup(object, cache = {}) click to toggle source

Deep duplicate any object.

@example

dupped = DeepDup.deep_dup('chunky')
dupped = DeepDup.deep_dup(['chunky', [:bacon, { hi: 5 }]])
dupped = DeepDup.deep_dup(['a', :a, 1, { bacon: { chunky: 'yeah' } }])
dupped = DeepDup.deep_dup(SomeClass.new)

array = [1, 2]
array << array
dupped = DeepDup.deep_dup(array)

@param object [Object] Pretty much anything. @param cache [Hash] Cache +object_id+s to prevent stack overflow on

recursive data structures.

@return [Object] Dupped object if possible.

# File lib/deep_dup.rb, line 46
def self.deep_dup(object, cache = {}) # rubocop:disable Metrics/MethodLength
  case object
  when String
    object.dup
  when nil, false, true, Numeric, Symbol, Method
    object
  when Array
    cache_object(object, [], cache) do |new_object|
      object.each do |item|
        new_object << deep_dup(item, cache)
      end
    end
  when Hash
    cache_object(object, {}, cache) do |new_object|
      object.each do |key, value|
        new_object[deep_dup(key, cache)] = deep_dup(value, cache)
      end
    end
  else # Object, Class
    object.dup
  end
end