philiprehberger-deep_freeze
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: