philiprehberger-deep_freeze

Tests Gem Version Last updated

Recursive deep freeze and deep dup with circular reference detection and key exclusion

Requirements

  • Ruby >= 3.1

Installation

Add to your Gemfile:

gem "philiprehberger-deep_freeze"

Or install directly:

gem install philiprehberger-deep_freeze

Usage

require "philiprehberger/deep_freeze"

data = { users: [{ name: "Alice", tags: ["admin"] }] }
Philiprehberger::DeepFreeze.deep_freeze(data)
data[:users][0][:name].frozen? # => true

Key Exclusion

Skip specific hash keys from being frozen with the except: option:

config = { cache: [], settings: { debug: true } }
Philiprehberger::DeepFreeze.deep_freeze(config, except: [:cache])
config[:cache].frozen?    # => false
config[:settings].frozen? # => true

Checking Frozen State

Verify that an object and all of its nested children are frozen:

data = { users: [{ name: "Alice" }] }
Philiprehberger::DeepFreeze.deep_freeze(data)
Philiprehberger::DeepFreeze.deep_frozen?(data) # => true

partial = { list: ["a", "b"] }
partial.freeze
Philiprehberger::DeepFreeze.deep_frozen?(partial) # => false (nested strings are not frozen)

Deep Dup

Create a fully unfrozen deep copy of a frozen object:

original = { users: [{ name: "Alice" }] }
Philiprehberger::DeepFreeze.deep_freeze(original)

copy = Philiprehberger::DeepFreeze.deep_dup(original)
copy.frozen?                   # => false
copy[:users][0][:name].frozen? # => false

Data Class Support (Ruby 3.2+)

require "philiprehberger/deep_freeze"

Point = Data.define(:x, :y)
point = Point.new(x: "origin", y: [1, 2])

frozen_point = Philiprehberger::DeepFreeze.deep_freeze(point)
frozen_point.x.frozen?  # => true
frozen_point.y.frozen?  # => true

Batch Freezing

Freeze multiple objects at once, sharing a single visited-set for cross-object circular reference detection:

config = { db: { host: "localhost" } }
cache = { store: config[:db] }

Philiprehberger::DeepFreeze.deep_freeze_all(config, cache)
config.frozen? # => true
cache.frozen?  # => true

Deep Clone

Create a deeply frozen copy of an object without modifying the original:

original = { users: [{ name: "Alice" }] }
clone = Philiprehberger::DeepFreeze.deep_clone(original)

clone.frozen?                     # => true
clone[:users][0][:name].frozen?   # => true
original.frozen?                  # => false

Keys-Only Freeze

Recursively freeze only hash keys, leaving values mutable:

schema = { "name" => "Alice", "tags" => ["admin"] }
Philiprehberger::DeepFreeze.freeze_hash_keys(schema)

schema.keys.first.frozen?  # => true
schema["name"].frozen?      # => false (value stays mutable)

Structural Equality

Compare two object graphs without caring about frozen state or object identity:

original = { users: [{ name: "Alice", tags: ["admin"] }] }
Philiprehberger::DeepFreeze.deep_freeze(original)

copy = Philiprehberger::DeepFreeze.deep_dup(original)
Philiprehberger::DeepFreeze.deep_equal?(original, copy) # => true

Structural Diff

Find exactly where two object graphs differ:

a = { users: [{ name: "Alice", age: 30 }] }
b = { users: [{ name: "Bob", age: 30 }] }

Philiprehberger::DeepFreeze.deep_diff(a, b)
# => { [:users, 0, :name] => { left: "Alice", right: "Bob" } }

Returns {} when the objects are structurally equal.

API

Method Description
DeepFreeze.deep_freeze(obj, except: []) Recursively freeze an object and all nested objects (Hash, Array, Set, Struct, Data); skips keys in except
DeepFreeze.deep_freeze_all(*objects, except: []) Freeze multiple objects sharing one visited-set for cross-object circular reference detection
DeepFreeze.deep_clone(obj, except: []) Deep dup + deep freeze in one pass — returns a frozen deep copy without modifying the original
DeepFreeze.freeze_hash_keys(hash) Recursively freeze only hash keys, leaving values mutable
DeepFreeze.deep_frozen?(obj) Return true if the object and all nested objects (including Struct and Data members) are frozen
DeepFreeze.deep_dup(obj) Recursively duplicate an object to create a fully unfrozen deep copy (supports Struct and Data)
DeepFreeze.deep_equal?(a, b) Structural equality across nested Hash, Array, Set, Struct, and Data — ignores frozen state
DeepFreeze.deep_diff(a, b) Return a hash of path => { left:, right: } pairs for every structural difference

Development

bundle install
bundle exec rspec
bundle exec rubocop

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT