File: hash-schemas.html.md

package info (click to toggle)
ruby-dry-types 1.2.2-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, forky, sid, trixie
  • size: 504 kB
  • sloc: ruby: 3,059; makefile: 4
file content (169 lines) | stat: -rw-r--r-- 4,427 bytes parent folder | download
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
---
title: Hash Schemas
layout: gem-single
name: dry-types
---

It is possible to define a type for a hash with a known set of keys and corresponding value types. Let's say you want to describe a hash containing the name and the age of a user:

```ruby
# using simple kernel coercions
user_hash = Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer)

user_hash[name: 'Jane', age: '21']
# => { name: 'Jane', age: 21 }
# :name left untouched and :age was coerced to Integer
```

If a value doesn't conform to the type, an error is raised:

```ruby
user_hash[name: :Jane, age: '21']
# => Dry::Types::SchemaError: :Jane (Symbol) has invalid type
#    for :name violates constraints (type?(String, :Jane) failed)
```

All keys are required by default:

```ruby
user_hash[name: 'Jane']
# => Dry::Types::MissingKeyError: :age is missing in Hash input
```

Extra keys are omitted by default:

```ruby
user_hash[name: 'Jane', age: '21', city: 'London']
# => { name: 'Jane', age: 21 }
```

### Default values

Default types are **only** evaluated if the corresponding key is missing in the input:

```ruby
user_hash = Types::Hash.schema(
  name: Types::String,
  age: Types::Integer.default(18)
)
user_hash[name: 'Jane']
# => { name: 'Jane', age: 18 }

# nil violates the constraint
user_hash[name: 'Jane', age: nil]
# => Dry::Types::SchemaError: nil (NilClass) has invalid type
#    for :age violates constraints (type?(Integer, nil) failed)
```

In order to evaluate default types on `nil`, wrap your type with a constructor and map `nil` to `Dry::Types::Undefined`:

```ruby
user_hash = Types::Hash.schema(
  name: Types::String,
  age: Types::Integer.
         default(18).
         constructor { |value|
           value.nil? ? Dry::Types::Undefined : value
         }
)

user_hash[name: 'Jane', age: nil]
# => { name: 'Jane', age: 18 }
```

The process of converting types to constructors like that can be automated, see "Type transformations" below.

### Optional keys

By default, all keys are required to present in the input. You can mark a key as optional by adding `?` to its name:

```ruby
user_hash = Types::Hash.schema(name: Types::String, age?: Types::Integer)

user_hash[name: 'Jane']
# => { name: 'Jane' }
```

### Extra keys

All keys not declared in the schema are silently ignored. This behavior can be changed by calling `.strict` on the schema:

```ruby
user_hash = Types::Hash.schema(name: Types::String).strict
user_hash[name: 'Jane', age: 21]
# => Dry::Types::UnknownKeysError: unexpected keys [:age] in Hash input
```

### Transforming input keys

Keys are supposed to be symbols but you can attach a key tranformation to a schema, e.g. for converting strings into symbols:

```ruby
user_hash = Types::Hash.schema(name: Types::String).with_key_transform(&:to_sym)
user_hash['name' => 'Jane']

# => { name: 'Jane' }
```

### Inheritance

Hash schemas can be inherited in a sense you can define a new schema based on an existing one. Declared keys will be merged, key and type transformations will be preserved. The `strict` option is also passed to the new schema if present.

```ruby
# Building an empty base schema
StrictSymbolizingHash = Types::Hash.schema({}).strict.with_key_transform(&:to_sym)

user_hash = StrictSymbolizingHash.schema(
  name: Types::String
)

user_hash['name' => 'Jane']
# => { name: 'Jane' }

user_hash['name' => 'Jane', 'city' => 'London']
# => Dry::Types::UnknownKeysError: unexpected keys [:city] in Hash input
```

### Transforming types

A schema can transform types with a block. For example, the following code makes all keys optional:

```ruby
user_hash = Types::Hash.with_type_transform { |type| type.required(false) }.schema(
  name: Types::String,
  age: Types::Integer
)

user_hash[name: 'Jane']
# => { name: 'Jane' }
user_hash[{}]
# => {}
```

Type transformations work perfectly with inheritance, you don't have to define same rules more than once:

```ruby
SymbolizeAndOptionalSchema = Types::Hash
  .schema({})
  .with_key_transform(&:to_sym)
  .with_type_transform { |type| type.required(false) }

user_hash = SymbolizeAndOptionalSchema.schema(
  name: Types::String,
  age: Types::Integer
)

user_hash['name' => 'Jane']
```

You can check key name by calling `.name` on the type argument:

```ruby
Types::Hash.with_type_transform do |key|
  if key.name.to_s.end_with?('_at')
    key.constructor { |v| Time.iso8601(v) }
  else
    key
  end
end
```