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
|
module ActiveRecord
module Attributes
module Typecasting
# Typecasts values during access based on their key mapping to a Type.
#
# Example:
# class Attributes < Hash
# include Typecasting
# end
#
# attributes = Attributes.new
# attributes.types['comments_count'] = Type::Integer
# attributes['comments_count'] = '5'
#
# attributes['comments_count']
# => 5
#
# To support keys not mapped to a typecaster, add a default to types.
# attributes.types.default = Type::Unknown
# attributes['age'] = '25'
# attributes['age']
# => '25'
#
# A valid type supports #cast, #precast, #boolean, and #appendable? methods.
#
def [](key)
value = super(key)
typecast_read(key, value)
end
def []=(key, value)
super(key, typecast_write(key, value))
end
def to_h
hash = {}
hash.merge!(self)
hash
end
def dup # :nodoc:
copy = super
copy.types = types.dup
copy
end
# Provides a duplicate with typecasting disabled.
#
# Example:
# attributes = Attributes.new
# attributes.types['comments_count'] = Type::Integer
# attributes['comments_count'] = '5'
#
# attributes.without_typecast['comments_count']
# => '5'
#
def without_typecast
dup.without_typecast!
end
def without_typecast!
types.clear
self
end
def typecast!
keys.each { |key| self[key] = self[key] }
self
end
# Check if key has a value that typecasts to true.
#
# attributes = Attributes.new
# attributes.types['comments_count'] = Type::Integer
#
# attributes['comments_count'] = 0
# attributes.has?('comments_count')
# => false
#
# attributes['comments_count'] = 1
# attributes.has?('comments_count')
# => true
#
def has?(key)
value = self[key]
boolean_typecast(key, value)
end
def types
@types ||= {}
end
protected
def types=(other_types)
@types = other_types
end
def boolean_typecast(key, value)
value ? types[key].boolean(value) : false
end
def typecast_read(key, value)
type = types[key]
value = type.cast(value)
self[key] = value if type.appendable? && !frozen?
value
end
def typecast_write(key, value)
types[key].precast(value)
end
end
end
end
|