Module: Philiprehberger::DeepFreeze

Defined in:
lib/philiprehberger/deep_freeze.rb,
lib/philiprehberger/deep_freeze/version.rb

Constant Summary collapse

VERSION =
'0.5.0'

Class Method Summary collapse

Class Method Details

.deep_clone(obj, except: []) ⇒ Object



54
55
56
57
58
# File 'lib/philiprehberger/deep_freeze.rb', line 54

def deep_clone(obj, except: [])
  copy = deep_dup(obj)
  deep_freeze(copy, except: except)
  copy
end

.deep_diff(a, b, path: []) ⇒ Boolean

Deep structural equality check that descends into nested Hash, Array, Set, Struct, and Data objects. Unlike ‘==`, this method compares structural content rather than frozen-state or object identity, making it safe to compare a deeply frozen graph against an unfrozen copy.

Parameters:

  • a (Object)

    first object

  • b (Object)

    second object

Returns:

  • (Boolean)

    true if the two graphs are structurally equal



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/philiprehberger/deep_freeze.rb', line 162

def deep_diff(a, b, path: [])
  return {} if a.equal?(b)

  unless a.instance_of?(b.class)
    return { path => { left: a, right: b } }
  end

  if defined?(Data) && a.is_a?(Data)
    return diff_members(a, b, a.class.members, path)
  end

  case a
  when Hash then diff_hashes(a, b, path)
  when Array then diff_arrays(a, b, path)
  when Struct then diff_members(a, b, a.members, path)
  else
    a == b ? {} : { path => { left: a, right: b } }
  end
end

.deep_dup(obj, seen: nil) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/philiprehberger/deep_freeze.rb', line 109

def deep_dup(obj, seen: nil)
  seen ||= {}
  return seen[obj.object_id] if seen.key?(obj.object_id)

  if defined?(Data) && obj.is_a?(Data)
    duped_attrs = {}
    obj.class.members.each do |member|
      duped_attrs[member] = deep_dup(obj.send(member), seen: seen)
    end
    result = obj.class.new(**duped_attrs)
    seen[obj.object_id] = result
    return result
  end

  case obj
  when Hash
    copy = {}
    seen[obj.object_id] = copy
    obj.each do |key, value|
      copy[deep_dup(key, seen: seen)] = deep_dup(value, seen: seen)
    end
    copy
  when Array
    copy = []
    seen[obj.object_id] = copy
    obj.each { |item| copy << deep_dup(item, seen: seen) }
    copy
  when Set
    copy = Set.new
    seen[obj.object_id] = copy
    obj.each { |item| copy.add(deep_dup(item, seen: seen)) }
    copy
  when String
    copy = obj.dup
    seen[obj.object_id] = copy
    copy
  when Numeric, Symbol, TrueClass, FalseClass, NilClass
    obj
  else
    copy = obj.dup
    seen[obj.object_id] = copy
    copy
  end
end

.deep_equal?(a, b) ⇒ Boolean

Returns:

  • (Boolean)


182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/philiprehberger/deep_freeze.rb', line 182

def deep_equal?(a, b)
  return true if a.equal?(b)
  return false unless a.instance_of?(b.class)

  if defined?(Data) && a.is_a?(Data)
    return a.class.members.all? { |m| deep_equal?(a.send(m), b.send(m)) }
  end

  case a
  when Hash
    return false unless a.size == b.size

    a.all? { |k, v| b.key?(k) && deep_equal?(v, b[k]) }
  when Array
    return false unless a.size == b.size

    a.each_with_index.all? { |item, i| deep_equal?(item, b[i]) }
  when Set
    return false unless a.size == b.size

    a.all? { |item| b.any? { |other| deep_equal?(item, other) } }
  when Struct
    a.each_pair.all? { |key, value| deep_equal?(value, b[key]) }
  else
    a == b
  end
end

.deep_freeze(obj, except: [], seen: nil) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/philiprehberger/deep_freeze.rb', line 9

def deep_freeze(obj, except: [], seen: nil)
  seen ||= Set.new
  return obj if obj.frozen? || seen.include?(obj.object_id)

  seen.add(obj.object_id)

  if defined?(Data) && obj.is_a?(Data)
    frozen_attrs = {}
    obj.class.members.each do |member|
      frozen_attrs[member] = deep_freeze(obj.send(member), except: except, seen: seen)
    end
    return obj.class.new(**frozen_attrs)
  end

  case obj
  when Hash
    obj.each do |key, value|
      next if except.include?(key)

      deep_freeze(key, except: except, seen: seen)
      deep_freeze(value, except: except, seen: seen)
    end
  when Array
    obj.each { |item| deep_freeze(item, except: except, seen: seen) }
  when Set
    obj.each { |item| deep_freeze(item, except: except, seen: seen) }
  when Struct
    obj.each_pair do |key, value|
      next if except.include?(key)

      deep_freeze(value, except: except, seen: seen)
    end
  when String
  end
  obj.freeze

  obj
end

.deep_freeze_all(*objects, except: []) ⇒ Object



48
49
50
51
52
# File 'lib/philiprehberger/deep_freeze.rb', line 48

def deep_freeze_all(*objects, except: [])
  seen = Set.new
  objects.each { |obj| deep_freeze(obj, except: except, seen: seen) }
  objects
end

.deep_frozen?(obj, seen: nil) ⇒ Boolean

Returns:

  • (Boolean)


81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/philiprehberger/deep_freeze.rb', line 81

def deep_frozen?(obj, seen: nil)
  seen ||= Set.new
  return true if seen.include?(obj.object_id)
  return false unless obj.frozen?

  seen.add(obj.object_id)

  if defined?(Data) && obj.is_a?(Data)
    return obj.class.members.all? { |m| deep_frozen?(obj.send(m), seen: seen) }
  end

  case obj
  when Hash
    obj.each do |key, value|
      return false unless deep_frozen?(key, seen: seen)
      return false unless deep_frozen?(value, seen: seen)
    end
  when Array
    obj.each { |item| return false unless deep_frozen?(item, seen: seen) }
  when Set
    obj.each { |item| return false unless deep_frozen?(item, seen: seen) }
  when Struct
    obj.each_pair { |_key, value| return false unless deep_frozen?(value, seen: seen) }
  end

  true
end

.freeze_hash_keys(hash, seen: nil) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/philiprehberger/deep_freeze.rb', line 60

def freeze_hash_keys(hash, seen: nil)
  seen ||= Set.new
  return hash if seen.include?(hash.object_id)

  seen.add(hash.object_id)

  case hash
  when Hash
    hash.each do |key, value|
      deep_freeze_key(key, seen)
      freeze_hash_keys(value, seen: seen)
    end
  when Array
    hash.each { |item| freeze_hash_keys(item, seen: seen) }
  when Set
    hash.each { |item| freeze_hash_keys(item, seen: seen) }
  end

  hash
end