Skip to content

Commit e74667d

Browse files
committed
Accept hash-like inputs in from_hash (#104)
Normalize hash inputs via to_hash in from_hash/add_from_hash to support Rails HashWithIndifferentAccess without adding an ActiveSupport dependency. Includes coverage and documentation updates.
1 parent b11d3be commit e74667d

4 files changed

Lines changed: 35 additions & 0 deletions

File tree

API-CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ smooth transition to the new APIs.
2626
[Tree::TreeNode#print_tree_to_s][print_tree_to_s] returns the formatted
2727
output as a string.
2828

29+
* Hash conversion now accepts hash-like inputs (objects responding to
30+
`to_hash`) to improve interoperability with frameworks such as Rails
31+
(see #104).
32+
2933
## Release 2.2.0 Changes
3034

3135
* [Tree::TreeNode#add][add] now raises `ArgumentError` when attempting to add

History.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
* Allow `print_tree` to write to a custom IO and add `print_tree_to_s` for
2020
string output.
2121

22+
* Accept hash-like inputs (`to_hash`) in hash conversion to support Rails
23+
`HashWithIndifferentAccess` data (see #104).
24+
2225
### 2.2.1pre / 2026-02-07
2326

2427
* Simplified development dependency constraints while maintaining Ruby 2.7+

lib/tree/utils/hash_converter.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ def self.included(base)
5454
# class methods on any class mixing in the {Tree::Utils::HashConverter}
5555
# module.
5656
module ClassMethods
57+
def normalize_hash_input(value)
58+
return value.to_hash if value.respond_to?(:to_hash)
59+
60+
value
61+
end
62+
5763
# Factory method builds a {Tree::TreeNode} from a +Hash+.
5864
#
5965
# This method will interpret each key of your +Hash+ as a {Tree::TreeNode}.
@@ -98,11 +104,13 @@ module ClassMethods
98104
# values that are not hashes or nils.
99105

100106
def from_hash(hash)
107+
hash = normalize_hash_input(hash)
101108
raise ArgumentError, 'Argument must be a type of hash' unless hash.is_a?(Hash)
102109

103110
raise ArgumentError, 'Hash must have one top-level element' if hash.size != 1
104111

105112
root, children = hash.first
113+
children = normalize_hash_input(children)
106114

107115
case children
108116
in Hash | nil
@@ -145,6 +153,7 @@ def from_hash(hash)
145153
# @return [Array] Array of child nodes added
146154
# @see ClassMethods#from_hash
147155
def add_from_hash(children)
156+
children = self.class.normalize_hash_input(children)
148157
raise ArgumentError, 'Argument must be a type of hash' unless children.is_a?(Hash)
149158

150159
child_nodes = []

test/test_tree_conversion.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,25 @@ def test_from_hash_with_nils
136136
assert_equal(2, interior_node.children.count)
137137
end
138138

139+
def test_from_hash_accepts_hash_like_inputs
140+
hash_like = Class.new do
141+
def initialize(payload)
142+
@payload = payload
143+
end
144+
145+
def to_hash
146+
@payload
147+
end
148+
end
149+
150+
input = hash_like.new({ A: hash_like.new({ B: nil }) })
151+
tree = Tree::TreeNode.from_hash(input)
152+
153+
assert_equal(:A, tree.name)
154+
assert_equal(true, tree.root?)
155+
assert_equal(true, tree[:B].leaf?)
156+
end
157+
139158
def test_add_from_hash
140159
tree = Tree::TreeNode.new(:A)
141160

0 commit comments

Comments
 (0)