/
attributes.rb
141 lines (131 loc) · 3.8 KB
/
attributes.rb
1
2
3
4
5
6
7
8
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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
108
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
require 'hanami/utils/hash'
module Hanami
module Utils
# A set of attributes.
#
# It internally stores the data as a Hash.
#
# All the operations convert keys to strings.
# This strategy avoids memory attacks due to Symbol abuses when parsing
# untrusted input.
#
# At the same time, this allows to get/set data with the original key or
# with the string representation. See the examples below.
#
# @since 0.3.2
class Attributes
# Initialize a set of attributes
# All the keys of the given Hash are recursively converted to strings.
#
# @param hash [#to_h] a Hash or any object that implements #to_h
#
# @return [Hanami::Utils::Attributes] self
#
# @since 0.3.2
#
# @example
# require 'hanami/utils/attributes'
#
# attributes = Hanami::Utils::Attributes.new(a: 1, b: { 2 => [3, 4] })
# attributes.to_h # => { "a" => 1, "b" => { "2" => [3, 4] } }
def initialize(hash = {})
@attributes = Utils::Hash.new(hash, &nil).stringify!
end
# Get the value associated with the given attribute
#
# @param attribute [#to_s] a String or any object that implements #to_s
#
# @return [Object,NilClass] the associated value, if present
#
# @since 0.3.2
#
# @example
# require 'hanami/utils/attributes'
#
# attributes = Hanami::Utils::Attributes.new(a: 1, 'b' => 2, 23 => 'foo')
#
# attributes.get(:a) # => 1
# attributes.get('a') # => 1
# attributes[:a] # => 1
# attributes['a'] # => 1
#
# attributes.get(:b) # => 2
# attributes.get('b') # => 2
# attributes[:b] # => 2
# attributes['b'] # => 2
#
# attributes.get(23) # => "foo"
# attributes.get('23') # => "foo"
# attributes[23] # => "foo"
# attributes['23'] # => "foo"
#
# attributes.get(:unknown) # => nil
# attributes.get('unknown') # => nil
# attributes[:unknown] # => nil
# attributes['unknown'] # => nil
def get(attribute)
value = @attributes
keys = attribute.to_s.split('.')
keys.each do |key|
break unless value
value = value[key]
end
value.is_a?(Hash) ? self.class.new(value) : value
end
# @since 0.3.4
alias [] get
# Set the given value for the given attribute
#
# @param attribute [#to_s] a String or any object that implements #to_s
# @param value [Object] any value
#
# @return [NilClass]
#
# @since 0.3.2
#
# @example
# require 'hanami/utils/attributes'
#
# attributes = Hanami::Utils::Attributes.new
#
# attributes.set(:a, 1)
# attributes.get(:a) # => 1
# attributes.get('a') # => 1
#
# attributes.set('b', 2)
# attributes.get(:b) # => 2
# attributes.get('b') # => 2
#
# attributes.set(23, 'foo')
# attributes.get(23) # => "foo"
# attributes.get('23') # => "foo"
def set(attribute, value)
@attributes[attribute.to_s] = value
nil
end
# Returns a deep duplicated copy of the attributes as a Hash
#
# @return [::Hash]
#
# @since 0.3.2
def to_h
::Hash[].tap do |result|
@attributes.each do |k, v|
result[k] = _read_value(v)
end
end
end
private
# @since 0.4.1
# @api private
def _read_value(value)
case val = value
when ::Hash, ::Hanami::Utils::Hash, ->(v) { v.respond_to?(:hanami_nested_attributes?) }
val.to_h
else
val
end
end
end
end
end