The project is in a healthy, maintained state
Recursively freeze entire object graphs (hashes, arrays, strings, structs, Data) to create truly immutable data structures. Includes deep_dup, deep_frozen?, deep_equal?, and deep_diff. Handles circular references and selective key exclusion.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

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