dry-data 0.4.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/CHANGELOG.md +21 -0
- data/Gemfile +0 -2
- data/README.md +204 -144
- data/dry-data.gemspec +1 -0
- data/lib/dry/data.rb +1 -7
- data/lib/dry/data/constrained.rb +41 -0
- data/lib/dry/data/constraints.rb +11 -3
- data/lib/dry/data/decorator.rb +44 -0
- data/lib/dry/data/default.rb +22 -0
- data/lib/dry/data/enum.rb +24 -0
- data/lib/dry/data/optional.rb +4 -9
- data/lib/dry/data/struct.rb +2 -2
- data/lib/dry/data/sum_type.rb +5 -9
- data/lib/dry/data/type.rb +14 -46
- data/lib/dry/data/type/array.rb +1 -1
- data/lib/dry/data/type/hash.rb +1 -1
- data/lib/dry/data/type_builder.rb +31 -0
- data/lib/dry/data/types.rb +3 -3
- data/lib/dry/data/types/form.rb +9 -9
- data/lib/dry/data/value.rb +2 -0
- data/lib/dry/data/version.rb +1 -1
- metadata +21 -5
- data/lib/dry/data/dsl.rb +0 -15
- data/lib/dry/data/type/constrained.rb +0 -35
- data/lib/dry/data/type/enum.rb +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b1120eb4914c94c999641d81a01ede2deb790f2
|
4
|
+
data.tar.gz: 58700f1d2f8d0de68428212b15feba6111de499f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12003922d8e399f312bb39ebc53c5f37bde3be4a7c5d5d9e3d667fb97358df733810fe096c60c0ca831f9dbcad7ed3b9b8cd81f2017699d12cbe8b950d4bfa05
|
7
|
+
data.tar.gz: ff1ee72ab2ea48e10458352e82a1d07521f644760107b33f66b67bda5f1067c7dcb77c9fb0d609489a2d278a92c1fe11d139c88d508fe67415a87155fdd5701d
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
# v0.5.0 2016-01-11
|
2
|
+
|
3
|
+
## Added
|
4
|
+
|
5
|
+
* `Type#default` interface for defining a type with a default value (solnic)
|
6
|
+
|
7
|
+
## Changed
|
8
|
+
|
9
|
+
* [BREAKING] `Dry::Data::Type.new` accepts constructor and *options* now (solnic)
|
10
|
+
* Renamed `Dry::Data::Type::{Enum,Constrained}` => `Dry::Data::{Enum,Constrained}` (solnic)
|
11
|
+
* `dry-logic` is now a dependency for constrained types (solnic)
|
12
|
+
* Constrained types are now always available (solnic)
|
13
|
+
* `strict.*` category uses constrained types with `:type?` predicate (solnic)
|
14
|
+
* `SumType#call` no longer needs to rescue from `TypeError` (solnic)
|
15
|
+
|
16
|
+
## Fixed
|
17
|
+
|
18
|
+
* `attribute` raises proper error when type definition is missing (solnic)
|
19
|
+
|
20
|
+
[Compare v0.4.2...v0.5.0](https://github.com/dryrb/dry-data/compare/v0.4.2...v0.5.0)
|
21
|
+
|
1
22
|
# v0.4.2 2015-12-27
|
2
23
|
|
3
24
|
## Added
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -14,7 +14,50 @@
|
|
14
14
|
[![Test Coverage](https://codeclimate.com/github/dryrb/dry-data/badges/coverage.svg)][codeclimate]
|
15
15
|
[![Inline docs](http://inch-ci.org/github/dryrb/dry-data.svg?branch=master)][inchpages]
|
16
16
|
|
17
|
-
A simple type system for Ruby with support for coercions
|
17
|
+
A simple and extendible type system for Ruby with support for kernel coercions,
|
18
|
+
form coercions, sum types, constrained types and default-value types.
|
19
|
+
|
20
|
+
Used by:
|
21
|
+
|
22
|
+
* [dry-validation](https://github.com/dryrb/dry-validation) for params coercions
|
23
|
+
* [rom-repository](https://github.com/rom-rb/rom-repository) for auto-mapped structs
|
24
|
+
* [rom](https://github.com/rom-rb/rom)'s adapters for relation schema definitions
|
25
|
+
* your project...?
|
26
|
+
|
27
|
+
Articles:
|
28
|
+
|
29
|
+
* ["Invalid Object Is An Anti-Pattern"](http://solnic.eu/2015/12/28/invalid-object-is-an-anti-pattern.html)
|
30
|
+
|
31
|
+
## dry-data vs virtus
|
32
|
+
|
33
|
+
[Virtus](https://github.com/solnic/virtus) has been a successful library, unfortunately
|
34
|
+
it is "only" a by-product of an ActiveRecord ORM which carries many issues typical
|
35
|
+
to ActiveRecord-like features that we all know from Rails, especially when it
|
36
|
+
comes to very complicated coercion logic, mixing unrelated concerns, polluting
|
37
|
+
application layer with concerns that should be handled at the bounderies etc.
|
38
|
+
|
39
|
+
`dry-data` has been created to become a better tool that solves *similar* (but
|
40
|
+
not identical!) problems related to type-safety and coercions. It is a superior
|
41
|
+
solution because:
|
42
|
+
|
43
|
+
* Types are [categorized](#built-in-type-categories), which is especially important for coercions
|
44
|
+
* Types are objects and they are easily reusable
|
45
|
+
* Has [structs](#structs) and [values](#values) with *a simple DSL*
|
46
|
+
* Has [constrained types](#constrained-types)
|
47
|
+
* Has [optional types](#optional-types)
|
48
|
+
* Has [defaults](#defaults)
|
49
|
+
* Has [sum-types](#sum-types)
|
50
|
+
* Has [enums](#enums)
|
51
|
+
* Has [hash type with type schemas](#hashes)
|
52
|
+
* Has [array type with member type](#arrays)
|
53
|
+
* Suitable for many use-cases while remaining simple, in example:
|
54
|
+
* Params coercions
|
55
|
+
* Domain "models"
|
56
|
+
* Defining various domain-specific, shared information using enums or values
|
57
|
+
* Annotating objects
|
58
|
+
* and more...
|
59
|
+
* There's no const-missing magic and complicated const lookups like in Virtus
|
60
|
+
* AND is roughly 10-12x faster than Virtus
|
18
61
|
|
19
62
|
## Installation
|
20
63
|
|
@@ -46,107 +89,139 @@ Built-in types are grouped under 5 categories:
|
|
46
89
|
- `form` - non-strict coercion types suitable for form params
|
47
90
|
- `maybe` - accepts either a nil or something else
|
48
91
|
|
92
|
+
### Configuring Types Module
|
93
|
+
|
94
|
+
In `dry-data` a type is an object with a constructor that knows how to handle
|
95
|
+
input. On top of that there are high-level types like a sum-type, constrained type,
|
96
|
+
optional type or default value type.
|
97
|
+
|
98
|
+
To acccess all the built-in type objects you can configure `dry-data` with a
|
99
|
+
namespace module:
|
100
|
+
|
101
|
+
``` ruby
|
102
|
+
module Types
|
103
|
+
end
|
104
|
+
|
105
|
+
Dry::Data.configure do |config|
|
106
|
+
config.namespace = Types
|
107
|
+
end
|
108
|
+
|
109
|
+
# after defining your custom types (if you've got any) you can finalize setup
|
110
|
+
Dry::Data.finalize
|
111
|
+
|
112
|
+
# this defines all types under your namespace, in example:
|
113
|
+
Types::Coercible::String
|
114
|
+
# => #<Dry::Data::Type:0x007feffb104aa8 @constructor=#<Method: Kernel.String>, @primitive=String>
|
115
|
+
```
|
116
|
+
|
117
|
+
With types accessible as constants you can easily compose more complex types,
|
118
|
+
like sum-types or constrained types, in hash schemas or structs:
|
119
|
+
|
120
|
+
``` ruby
|
121
|
+
Dry::Data.configure do |config|
|
122
|
+
config.namespace = Types
|
123
|
+
end
|
124
|
+
|
125
|
+
Dry::Data.finalize
|
126
|
+
|
127
|
+
module Types
|
128
|
+
Email = String.constrained(format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i)
|
129
|
+
Age = Int.constrained(gt: 18)
|
130
|
+
end
|
131
|
+
|
132
|
+
class User < Dry::Data::Struct
|
133
|
+
attribute :name, Types::String
|
134
|
+
attribute :email, Types::Email
|
135
|
+
attribute :age, Types::Age
|
136
|
+
end
|
137
|
+
```
|
138
|
+
|
49
139
|
### Built-in Type Categories
|
50
140
|
|
141
|
+
Assuming you configured types under `Types` module namespace:
|
142
|
+
|
51
143
|
Non-coercible:
|
52
144
|
|
53
|
-
- `
|
54
|
-
- `
|
55
|
-
- `
|
56
|
-
- `
|
57
|
-
- `
|
58
|
-
- `
|
59
|
-
- `
|
60
|
-
- `
|
145
|
+
- `Types::Nil`
|
146
|
+
- `Types::Symbol`
|
147
|
+
- `Types::Class`
|
148
|
+
- `Types::True`
|
149
|
+
- `Types::False`
|
150
|
+
- `Types::Date`
|
151
|
+
- `Types::DateTime`
|
152
|
+
- `Types::Time`
|
61
153
|
|
62
154
|
Coercible types using kernel coercion methods:
|
63
155
|
|
64
|
-
- `
|
65
|
-
- `
|
66
|
-
- `
|
67
|
-
- `
|
68
|
-
- `
|
69
|
-
- `
|
156
|
+
- `Types::Coercible::String`
|
157
|
+
- `Types::Coercible::Int`
|
158
|
+
- `Types::Coercible::Float`
|
159
|
+
- `Types::Coercible::Decimal`
|
160
|
+
- `Types::Coercible::Array`
|
161
|
+
- `Types::Coercible::Hash`
|
70
162
|
|
71
163
|
Optional strict types:
|
72
164
|
|
73
|
-
- `
|
74
|
-
- `
|
75
|
-
- `
|
76
|
-
- `
|
77
|
-
- `
|
78
|
-
- `
|
165
|
+
- `Types::Maybe::Strict::String`
|
166
|
+
- `Types::Maybe::Strict::Int`
|
167
|
+
- `Types::Maybe::Strict::Float`
|
168
|
+
- `Types::Maybe::Strict::Decimal`
|
169
|
+
- `Types::Maybe::Strict::Array`
|
170
|
+
- `Types::Maybe::Strict::Hash`
|
79
171
|
|
80
172
|
Optional coercible types:
|
81
173
|
|
82
|
-
- `
|
83
|
-
- `
|
84
|
-
- `
|
85
|
-
- `
|
86
|
-
- `
|
87
|
-
- `
|
174
|
+
- `Types::Maybe::Coercible::String`
|
175
|
+
- `Types::Maybe::Coercible::Int`
|
176
|
+
- `Types::Maybe::Coercible::Float`
|
177
|
+
- `Types::Maybe::Coercible::Decimal`
|
178
|
+
- `Types::Maybe::Coercible::Array`
|
179
|
+
- `Types::Maybe::Coercible::Hash`
|
88
180
|
|
89
181
|
Coercible types suitable for form param processing:
|
90
182
|
|
91
|
-
- `
|
92
|
-
- `
|
93
|
-
- `
|
94
|
-
- `
|
95
|
-
- `
|
96
|
-
- `
|
97
|
-
- `
|
98
|
-
- `
|
99
|
-
- `
|
100
|
-
- `
|
183
|
+
- `Types::Form::Nil`
|
184
|
+
- `Types::Form::Date`
|
185
|
+
- `Types::Form::DateTime`
|
186
|
+
- `Types::Form::Time`
|
187
|
+
- `Types::Form::True`
|
188
|
+
- `Types::Form::False`
|
189
|
+
- `Types::Form::Bool`
|
190
|
+
- `Types::Form::Int`
|
191
|
+
- `Types::Form::Float`
|
192
|
+
- `Types::Form::Decimal`
|
101
193
|
|
102
|
-
###
|
194
|
+
### Strict vs Coercible Types
|
103
195
|
|
104
196
|
``` ruby
|
105
|
-
#
|
106
|
-
|
107
|
-
|
108
|
-
float[3.2] # => 3.2
|
109
|
-
float["3.2"] # "3.2"
|
110
|
-
|
111
|
-
# strict type-check category
|
112
|
-
int = Dry::Data["strict.int"]
|
113
|
-
|
114
|
-
int[1] # => 1
|
115
|
-
int['1'] # => raises TypeError
|
197
|
+
Types::Strict::Int[1] # => 1
|
198
|
+
Types::Strict::Int['1'] # => raises Dry::Data::ConstraintError
|
116
199
|
|
117
200
|
# coercible type-check group
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
string[:foo] # => 'foo'
|
122
|
-
array[:foo] # => [:foo]
|
201
|
+
Types::Coercible::String[:foo] # => 'foo'
|
202
|
+
Types::Coercible::Array[:foo] # => [:foo]
|
123
203
|
|
124
204
|
# form group
|
125
|
-
|
126
|
-
date['2015-11-29'] # => #<Date: 2015-11-29 ((2457356j,0s,0n),+0s,2299161j)>
|
205
|
+
Types::Form::Date['2015-11-29'] # => #<Date: 2015-11-29 ((2457356j,0s,0n),+0s,2299161j)>
|
127
206
|
```
|
128
207
|
|
129
|
-
### Optional
|
208
|
+
### Optional Types
|
130
209
|
|
131
210
|
All built-in types have their optional versions too, you can access them under
|
132
|
-
`"
|
211
|
+
`"Types::Maybe::Strict"` and `"Maybe::Coercible"` categories:
|
133
212
|
|
134
213
|
``` ruby
|
135
|
-
|
136
|
-
|
137
|
-
maybe_int[nil] # None
|
138
|
-
maybe_int[123] # Some(123)
|
214
|
+
Types::Maybe::Int[nil] # None
|
215
|
+
Types::Maybe::Int[123] # Some(123)
|
139
216
|
|
140
|
-
|
141
|
-
|
142
|
-
maybe_int[nil] # None
|
143
|
-
maybe_int['12.3'] # Some(12.3)
|
217
|
+
Types::Maybe::Coercible::Float[nil] # None
|
218
|
+
Types::Maybe::Coercible::Float['12.3'] # Some(12.3)
|
144
219
|
```
|
145
220
|
|
146
221
|
You can define your own optional types too:
|
147
222
|
|
148
223
|
``` ruby
|
149
|
-
maybe_string =
|
224
|
+
maybe_string = Types::Strict::String.optional
|
150
225
|
|
151
226
|
maybe_string[nil]
|
152
227
|
# => None
|
@@ -164,19 +239,31 @@ maybe_string['something'].fmap(&:upcase).value
|
|
164
239
|
# => "SOMETHING"
|
165
240
|
```
|
166
241
|
|
242
|
+
### Defaults
|
243
|
+
|
244
|
+
A type with a default value will return the configured value when the input is `nil`:
|
245
|
+
|
246
|
+
``` ruby
|
247
|
+
PostStatus = Types::Strict::String.default('draft')
|
248
|
+
|
249
|
+
PostStatus[nil] # "draft"
|
250
|
+
PostStatus["published"] # "published"
|
251
|
+
PostStatus[true] # raises ConstraintError
|
252
|
+
```
|
253
|
+
|
167
254
|
### Sum-types
|
168
255
|
|
169
256
|
You can specify sum types using `|` operator, it is an explicit way of defining
|
170
257
|
what are the valid types of a value.
|
171
258
|
|
172
259
|
In example `dry-data` defines `bool` type which is a sum-type consisting of `true`
|
173
|
-
and `false` types which is expressed as `
|
260
|
+
and `false` types which is expressed as `Types::True | Types::False`
|
174
261
|
(and it has its strict version, too).
|
175
262
|
|
176
263
|
Another common case is defining that something can be either `nil` or something else:
|
177
264
|
|
178
265
|
``` ruby
|
179
|
-
nil_or_string =
|
266
|
+
nil_or_string = Types::Nil | Types::Strict::String
|
180
267
|
|
181
268
|
nil_or_string[nil] # => nil
|
182
269
|
nil_or_string["hello"] # => "hello"
|
@@ -191,20 +278,11 @@ a lower level guarantee that you're not instantiating objects that are broken.
|
|
191
278
|
All types support constraints API, but not all constraints are suitable for a
|
192
279
|
particular primitive, it's up to you to set up constraints that make sense.
|
193
280
|
|
194
|
-
Under the hood it uses [`dry-
|
281
|
+
Under the hood it uses [`dry-logic`](https://github.com/dryrb/dry-logic)
|
195
282
|
and all of its predicates are supported.
|
196
283
|
|
197
|
-
IMPORTANT: `dry-data` does not have a runtime dependency on `dry-validation` so
|
198
|
-
if you want to use contrained types you need to add it to your Gemfile
|
199
|
-
|
200
|
-
If you want to use constrained type you need to require it explicitly:
|
201
|
-
|
202
284
|
``` ruby
|
203
|
-
|
204
|
-
```
|
205
|
-
|
206
|
-
``` ruby
|
207
|
-
string = Dry::Data["strict.string"].constrained(min_size: 3)
|
285
|
+
string = Types::Strict::String.constrained(min_size: 3)
|
208
286
|
|
209
287
|
string['foo']
|
210
288
|
# => "foo"
|
@@ -212,7 +290,7 @@ string['foo']
|
|
212
290
|
string['fo']
|
213
291
|
# => Dry::Data::ConstraintError: "fo" violates constraints
|
214
292
|
|
215
|
-
email =
|
293
|
+
email = Types::Strict::String.constrained(
|
216
294
|
format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
|
217
295
|
)
|
218
296
|
|
@@ -223,48 +301,7 @@ email["jane"]
|
|
223
301
|
# => Dry::Data::ConstraintError: "fo" violates constraints
|
224
302
|
```
|
225
303
|
|
226
|
-
###
|
227
|
-
|
228
|
-
Types can be stored as easily accessible constants in a configured namespace:
|
229
|
-
|
230
|
-
``` ruby
|
231
|
-
module Types; end
|
232
|
-
|
233
|
-
Dry::Data.configure do |config|
|
234
|
-
config.namespace = Types
|
235
|
-
end
|
236
|
-
|
237
|
-
# after defining your custom types (if you've got any) you can finalize setup
|
238
|
-
Dry::Data.finalize
|
239
|
-
|
240
|
-
# this defines all types under your namespace
|
241
|
-
Types::Coercible::String
|
242
|
-
# => #<Dry::Data::Type:0x007feffb104aa8 @constructor=#<Method: Kernel.String>, @primitive=String>
|
243
|
-
```
|
244
|
-
|
245
|
-
With types accessible as constants you can easily compose more complex types,
|
246
|
-
like sum-types or constrained types, in hash schemas or structs:
|
247
|
-
|
248
|
-
``` ruby
|
249
|
-
Dry::Data.configure do |config|
|
250
|
-
config.namespace = Types
|
251
|
-
end
|
252
|
-
|
253
|
-
Dry::Data.finalize
|
254
|
-
|
255
|
-
module Types
|
256
|
-
Email = String.constrained(format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i)
|
257
|
-
Age = Int.constrained(gt: 18)
|
258
|
-
end
|
259
|
-
|
260
|
-
class User < Dry::Data::Struct
|
261
|
-
attribute :name, Types::String
|
262
|
-
attribute :email, Types::Email
|
263
|
-
attribute :age, Types::Age
|
264
|
-
end
|
265
|
-
```
|
266
|
-
|
267
|
-
### Defining Enums
|
304
|
+
### Enums
|
268
305
|
|
269
306
|
In many cases you may want to define an enum. For example in a blog application
|
270
307
|
a post may have a finite list of statuses. Apart from accessing the current status
|
@@ -272,8 +309,6 @@ value it is useful to have all possible values accessible too. Furthermore an
|
|
272
309
|
enum is a `int => value` map, so you can store integers somewhere and have them
|
273
310
|
mapped to enum values conveniently.
|
274
311
|
|
275
|
-
You can define enums for every type but it probably only makes sense for `string`:
|
276
|
-
|
277
312
|
``` ruby
|
278
313
|
# assuming we have types loaded into `Types` namespace
|
279
314
|
# we can easily define an enum for our post struct
|
@@ -303,77 +338,102 @@ Post::Statuses[nil]
|
|
303
338
|
# => Dry::Data::ConstraintError: nil violates constraints
|
304
339
|
```
|
305
340
|
|
306
|
-
###
|
341
|
+
### Hashes
|
307
342
|
|
308
343
|
The built-in hash type has constructors that you can use to define hashes with
|
309
344
|
explicit schemas and coercible values using the built-in types.
|
310
345
|
|
311
|
-
|
346
|
+
#### Hash Schema
|
312
347
|
|
313
348
|
``` ruby
|
314
349
|
# using simple kernel coercions
|
315
|
-
hash =
|
350
|
+
hash = Types::Hash.schema(name: Types::String, age: Types::Coercible::Int)
|
316
351
|
|
317
352
|
hash[name: 'Jane', age: '21']
|
318
353
|
# => { :name => "Jane", :age => 21 }
|
319
354
|
|
320
355
|
# using form param coercions
|
321
|
-
hash =
|
356
|
+
hash = Types::Hash.schema(name: Types::String, birthdate: Form::Date)
|
322
357
|
|
323
358
|
hash[name: 'Jane', birthdate: '1994-11-11']
|
324
359
|
# => { :name => "Jane", :birthdate => #<Date: 1994-11-11 ((2449668j,0s,0n),+0s,2299161j)> }
|
325
360
|
```
|
326
361
|
|
327
|
-
|
362
|
+
#### Strict Schema
|
328
363
|
|
329
364
|
Strict hash will raise errors when keys are missing or value types are incorrect.
|
330
365
|
|
331
366
|
``` ruby
|
332
|
-
hash =
|
367
|
+
hash = Types::Hash.strict(name: 'string', age: 'coercible.int')
|
333
368
|
|
334
369
|
hash[email: 'jane@doe.org', name: 'Jane', age: 21]
|
335
370
|
# => Dry::Data::SchemaKeyError: :email is missing in Hash input
|
336
371
|
```
|
337
372
|
|
338
|
-
|
373
|
+
#### Symbolized Schema
|
339
374
|
|
340
375
|
Symbolized hash will turn string key names into symbols
|
341
376
|
|
342
377
|
``` ruby
|
343
|
-
hash =
|
378
|
+
hash = Types::Hash.symbolized(name: Types::String, age: Types::Coercible::Int)
|
344
379
|
|
345
380
|
hash['name' => 'Jane', 'age' => '21']
|
346
381
|
# => { :name => "Jane", :age => 21 }
|
347
382
|
```
|
348
383
|
|
349
|
-
###
|
384
|
+
### Arrays
|
350
385
|
|
351
|
-
|
352
|
-
|
386
|
+
The built-in array type supports defining member type:
|
387
|
+
|
388
|
+
``` ruby
|
389
|
+
PostStatuses = Types::Strict::Array.member(Types::Coercible::String)
|
390
|
+
|
391
|
+
PostStatuses[[:foo, :bar]] # ["foo", "bar"]
|
392
|
+
```
|
393
|
+
|
394
|
+
### Structs
|
395
|
+
|
396
|
+
You can define struct objects which will have readers for specified attributes
|
397
|
+
using a simple dsl:
|
353
398
|
|
354
399
|
``` ruby
|
355
400
|
class User < Dry::Data::Struct
|
356
|
-
attribute :name,
|
357
|
-
attribute :age,
|
401
|
+
attribute :name, Types::Maybe::Coercible::String
|
402
|
+
attribute :age, Types::Coercible::Int
|
358
403
|
end
|
359
404
|
|
360
|
-
|
361
|
-
user_type = Dry::Data["user"]
|
362
|
-
|
363
|
-
user = user_type[name: nil, age: '21']
|
405
|
+
user = User.new(name: nil, age: '21')
|
364
406
|
|
365
407
|
user.name # None
|
366
408
|
user.age # 21
|
367
409
|
|
368
|
-
user =
|
410
|
+
user = User(name: 'Jane', age: '21')
|
369
411
|
|
370
412
|
user.name # => Some("Jane")
|
371
413
|
user.age # => 21
|
372
414
|
```
|
373
415
|
|
416
|
+
### Values
|
417
|
+
|
418
|
+
You can define value objects which will behave like structs and have equality
|
419
|
+
methods too:
|
420
|
+
|
421
|
+
``` ruby
|
422
|
+
class Location < Dry::Data::Value
|
423
|
+
attribute :lat, Types::Strict::Float
|
424
|
+
attribute :lat, Types::Strict::Float
|
425
|
+
end
|
426
|
+
|
427
|
+
loc1 = Location.new(lat: 1.23, lng: 4.56)
|
428
|
+
loc2 = Location.new(lat: 1.23, lng: 4.56)
|
429
|
+
|
430
|
+
loc1 == loc2
|
431
|
+
# true
|
432
|
+
```
|
433
|
+
|
374
434
|
## Status and Roadmap
|
375
435
|
|
376
|
-
This library is in an early stage of development but you are
|
436
|
+
This library is in an early stage of development but you are encouraged to try it
|
377
437
|
out and provide feedback.
|
378
438
|
|
379
439
|
For planned features check out [the issues](https://github.com/dryrb/dry-data/labels/feature).
|
data/dry-data.gemspec
CHANGED
@@ -31,6 +31,7 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.add_runtime_dependency 'dry-container', '~> 0.2'
|
32
32
|
spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
|
33
33
|
spec.add_runtime_dependency 'dry-configurable', '~> 0.1'
|
34
|
+
spec.add_runtime_dependency 'dry-logic', '~> 0.1'
|
34
35
|
spec.add_runtime_dependency 'inflecto', '~> 0.0.0', '>= 0.0.2'
|
35
36
|
spec.add_runtime_dependency 'kleisli', '~> 0.2'
|
36
37
|
|
data/lib/dry/data.rb
CHANGED
@@ -13,7 +13,6 @@ require 'dry/data/container'
|
|
13
13
|
require 'dry/data/type'
|
14
14
|
require 'dry/data/struct'
|
15
15
|
require 'dry/data/value'
|
16
|
-
require 'dry/data/dsl'
|
17
16
|
|
18
17
|
module Dry
|
19
18
|
module Data
|
@@ -53,7 +52,7 @@ module Dry
|
|
53
52
|
def self.register_class(klass)
|
54
53
|
container.register(
|
55
54
|
Inflecto.underscore(klass).gsub('/', '.'),
|
56
|
-
Type.new(klass.method(:new), klass)
|
55
|
+
Type.new(klass.method(:new), primitive: klass)
|
57
56
|
)
|
58
57
|
end
|
59
58
|
|
@@ -98,11 +97,6 @@ module Dry
|
|
98
97
|
Inflecto.underscore(klass).gsub('/', '.')
|
99
98
|
end
|
100
99
|
|
101
|
-
def self.type(*args, &block)
|
102
|
-
dsl = DSL.new(container)
|
103
|
-
block ? yield(dsl) : registry[args.first]
|
104
|
-
end
|
105
|
-
|
106
100
|
def self.type_map
|
107
101
|
@type_map ||= ThreadSafe::Cache.new
|
108
102
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'dry/data/decorator'
|
2
|
+
require 'dry/data/constraints'
|
3
|
+
|
4
|
+
module Dry
|
5
|
+
module Data
|
6
|
+
class Constrained
|
7
|
+
include Decorator
|
8
|
+
include TypeBuilder
|
9
|
+
|
10
|
+
attr_reader :rule
|
11
|
+
|
12
|
+
def initialize(type, options)
|
13
|
+
super
|
14
|
+
@rule = options.fetch(:rule)
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(input)
|
18
|
+
result = try(input)
|
19
|
+
|
20
|
+
if valid?(result)
|
21
|
+
result
|
22
|
+
else
|
23
|
+
raise ConstraintError, "#{input.inspect} violates constraints"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
alias_method :[], :call
|
27
|
+
|
28
|
+
def try(input)
|
29
|
+
type[input]
|
30
|
+
end
|
31
|
+
|
32
|
+
def valid?(input)
|
33
|
+
super && rule.(input).success?
|
34
|
+
end
|
35
|
+
|
36
|
+
def constrained(options)
|
37
|
+
with(rule: rule & Data.Rule(primitive, options))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/dry/data/constraints.rb
CHANGED
@@ -1,8 +1,16 @@
|
|
1
|
-
require 'dry/
|
2
|
-
require 'dry/
|
1
|
+
require 'dry/logic/rule_compiler'
|
2
|
+
require 'dry/logic/predicates'
|
3
3
|
|
4
4
|
module Dry
|
5
5
|
module Data
|
6
|
+
module Predicates
|
7
|
+
include Logic::Predicates
|
8
|
+
|
9
|
+
predicate(:type?) do |type, value|
|
10
|
+
value.kind_of?(type)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
6
14
|
def self.Rule(primitive, options)
|
7
15
|
rule_compiler.(
|
8
16
|
options.map { |key, val|
|
@@ -12,7 +20,7 @@ module Dry
|
|
12
20
|
end
|
13
21
|
|
14
22
|
def self.rule_compiler
|
15
|
-
@rule_compiler ||=
|
23
|
+
@rule_compiler ||= Logic::RuleCompiler.new(Data::Predicates)
|
16
24
|
end
|
17
25
|
end
|
18
26
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Dry
|
2
|
+
module Data
|
3
|
+
module Decorator
|
4
|
+
attr_reader :type, :options
|
5
|
+
|
6
|
+
def initialize(type, options = {})
|
7
|
+
@type = type
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def constructor
|
12
|
+
type.constructor
|
13
|
+
end
|
14
|
+
|
15
|
+
def valid?(input)
|
16
|
+
type.valid?(input)
|
17
|
+
end
|
18
|
+
|
19
|
+
def respond_to_missing?(meth, include_private = false)
|
20
|
+
super || type.respond_to?(meth)
|
21
|
+
end
|
22
|
+
|
23
|
+
def with(new_options)
|
24
|
+
self.class.new(type, options.merge(new_options))
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def method_missing(meth, *args, &block)
|
30
|
+
if type.respond_to?(meth)
|
31
|
+
response = type.__send__(meth, *args, &block)
|
32
|
+
|
33
|
+
if response.kind_of?(type.class)
|
34
|
+
self.class.new(response, options)
|
35
|
+
else
|
36
|
+
response
|
37
|
+
end
|
38
|
+
else
|
39
|
+
super
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'dry/data/decorator'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Data
|
5
|
+
class Default
|
6
|
+
include Decorator
|
7
|
+
include TypeBuilder
|
8
|
+
|
9
|
+
attr_reader :value
|
10
|
+
|
11
|
+
def initialize(type, options)
|
12
|
+
super
|
13
|
+
@value = options.fetch(:value)
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(input)
|
17
|
+
input.nil? ? value : type[input]
|
18
|
+
end
|
19
|
+
alias_method :[], :call
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'dry/data/decorator'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Data
|
5
|
+
class Enum
|
6
|
+
include Decorator
|
7
|
+
|
8
|
+
attr_reader :values
|
9
|
+
|
10
|
+
def initialize(type, options)
|
11
|
+
super
|
12
|
+
@values = options.fetch(:values).freeze
|
13
|
+
@values.each(&:freeze)
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(input)
|
17
|
+
case input
|
18
|
+
when Fixnum then type[values[input]]
|
19
|
+
else type[input] end
|
20
|
+
end
|
21
|
+
alias_method :[], :call
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/dry/data/optional.rb
CHANGED
@@ -1,15 +1,10 @@
|
|
1
|
+
require 'dry/data/decorator'
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
module Data
|
3
5
|
class Optional
|
4
|
-
|
5
|
-
|
6
|
-
def initialize(type)
|
7
|
-
@type = type
|
8
|
-
end
|
9
|
-
|
10
|
-
def valid?(input)
|
11
|
-
type.valid?(input)
|
12
|
-
end
|
6
|
+
include Decorator
|
7
|
+
include TypeBuilder
|
13
8
|
|
14
9
|
def call(input)
|
15
10
|
Maybe(type[input])
|
data/lib/dry/data/struct.rb
CHANGED
data/lib/dry/data/sum_type.rb
CHANGED
@@ -16,16 +16,12 @@ module Dry
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def call(input)
|
19
|
-
|
20
|
-
value = left[input]
|
19
|
+
value = left.try(input)
|
21
20
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
27
|
-
rescue TypeError
|
28
|
-
right[input]
|
21
|
+
if left.valid?(value)
|
22
|
+
value
|
23
|
+
else
|
24
|
+
right[value]
|
29
25
|
end
|
30
26
|
end
|
31
27
|
alias_method :[], :call
|
data/lib/dry/data/type.rb
CHANGED
@@ -1,35 +1,18 @@
|
|
1
1
|
require 'dry/data/type/hash'
|
2
2
|
require 'dry/data/type/array'
|
3
|
-
require 'dry/data/
|
4
|
-
|
5
|
-
require 'dry/data/sum_type'
|
6
|
-
require 'dry/data/optional'
|
3
|
+
require 'dry/data/type_builder'
|
7
4
|
|
8
5
|
module Dry
|
9
6
|
module Data
|
10
7
|
class Type
|
11
|
-
|
12
|
-
|
8
|
+
include Dry::Equalizer(:constructor, :options)
|
9
|
+
include TypeBuilder
|
13
10
|
|
14
|
-
|
15
|
-
attr_reader :rule
|
16
|
-
|
17
|
-
def initialize(constructor, primitive, rule)
|
18
|
-
super(constructor, primitive)
|
19
|
-
@rule = rule
|
20
|
-
end
|
11
|
+
attr_reader :constructor
|
21
12
|
|
22
|
-
|
23
|
-
result = super(input)
|
13
|
+
attr_reader :options
|
24
14
|
|
25
|
-
|
26
|
-
result
|
27
|
-
else
|
28
|
-
raise ConstraintError, "#{input.inspect} violates constraints"
|
29
|
-
end
|
30
|
-
end
|
31
|
-
alias_method :[], :call
|
32
|
-
end
|
15
|
+
attr_reader :primitive
|
33
16
|
|
34
17
|
def self.[](primitive)
|
35
18
|
if primitive == ::Array
|
@@ -41,29 +24,14 @@ module Dry
|
|
41
24
|
end
|
42
25
|
end
|
43
26
|
|
44
|
-
def self.
|
45
|
-
if input.is_a?(primitive)
|
46
|
-
input
|
47
|
-
else
|
48
|
-
raise TypeError, "#{input.inspect} has invalid type"
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def self.passthrough_constructor(input)
|
27
|
+
def self.constructor(input)
|
53
28
|
input
|
54
29
|
end
|
55
30
|
|
56
|
-
def initialize(constructor,
|
31
|
+
def initialize(constructor, options = {})
|
57
32
|
@constructor = constructor
|
58
|
-
@
|
59
|
-
|
60
|
-
|
61
|
-
def enum(*values)
|
62
|
-
Enum.new(values, constrained(inclusion: values))
|
63
|
-
end
|
64
|
-
|
65
|
-
def optional
|
66
|
-
Optional.new(Data['nil'] | self)
|
33
|
+
@options = options
|
34
|
+
@primitive = options.fetch(:primitive)
|
67
35
|
end
|
68
36
|
|
69
37
|
def name
|
@@ -75,12 +43,12 @@ module Dry
|
|
75
43
|
end
|
76
44
|
alias_method :[], :call
|
77
45
|
|
78
|
-
def
|
79
|
-
input
|
46
|
+
def try(input)
|
47
|
+
call(input)
|
80
48
|
end
|
81
49
|
|
82
|
-
def
|
83
|
-
|
50
|
+
def valid?(input)
|
51
|
+
input.is_a?(primitive)
|
84
52
|
end
|
85
53
|
end
|
86
54
|
end
|
data/lib/dry/data/type/array.rb
CHANGED
@@ -16,7 +16,7 @@ module Dry
|
|
16
16
|
array_constructor = self.class
|
17
17
|
.method(:constructor).to_proc.curry.(constructor, member_constructor)
|
18
18
|
|
19
|
-
self.class.new(array_constructor, primitive)
|
19
|
+
self.class.new(array_constructor, primitive: primitive, member: member_constructor)
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
data/lib/dry/data/type/hash.rb
CHANGED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Dry
|
2
|
+
module Data
|
3
|
+
module TypeBuilder
|
4
|
+
def |(other)
|
5
|
+
SumType.new(self, other)
|
6
|
+
end
|
7
|
+
|
8
|
+
def optional
|
9
|
+
Optional.new(Data['nil'] | self)
|
10
|
+
end
|
11
|
+
|
12
|
+
def constrained(options)
|
13
|
+
Constrained.new(self, rule: Data.Rule(primitive, options))
|
14
|
+
end
|
15
|
+
|
16
|
+
def default(value)
|
17
|
+
Default.new(self, value: value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def enum(*values)
|
21
|
+
Enum.new(constrained(inclusion: values), values: values)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'dry/data/default'
|
28
|
+
require 'dry/data/constrained'
|
29
|
+
require 'dry/data/enum'
|
30
|
+
require 'dry/data/optional'
|
31
|
+
require 'dry/data/sum_type'
|
data/lib/dry/data/types.rb
CHANGED
@@ -26,7 +26,7 @@ module Dry
|
|
26
26
|
COERCIBLE.each do |name, primitive|
|
27
27
|
register(
|
28
28
|
"coercible.#{name}",
|
29
|
-
Type[primitive].new(Kernel.method(primitive.name), primitive)
|
29
|
+
Type[primitive].new(Kernel.method(primitive.name), primitive: primitive)
|
30
30
|
)
|
31
31
|
end
|
32
32
|
|
@@ -34,7 +34,7 @@ module Dry
|
|
34
34
|
ALL_PRIMITIVES.each do |name, primitive|
|
35
35
|
register(
|
36
36
|
"strict.#{name}",
|
37
|
-
Type[primitive].new(Type.method(:
|
37
|
+
Type[primitive].new(Type.method(:constructor), primitive: primitive).constrained(type: primitive)
|
38
38
|
)
|
39
39
|
end
|
40
40
|
|
@@ -42,7 +42,7 @@ module Dry
|
|
42
42
|
ALL_PRIMITIVES.each do |name, primitive|
|
43
43
|
register(
|
44
44
|
name.to_s,
|
45
|
-
Type[primitive].new(Type.method(:
|
45
|
+
Type[primitive].new(Type.method(:constructor), primitive: primitive)
|
46
46
|
)
|
47
47
|
end
|
48
48
|
|
data/lib/dry/data/types/form.rb
CHANGED
@@ -3,27 +3,27 @@ require 'dry/data/coercions/form'
|
|
3
3
|
module Dry
|
4
4
|
module Data
|
5
5
|
register('form.nil') do
|
6
|
-
Type.new(Coercions::Form.method(:to_nil), NilClass)
|
6
|
+
Type.new(Coercions::Form.method(:to_nil), primitive: NilClass)
|
7
7
|
end
|
8
8
|
|
9
9
|
register('form.date') do
|
10
|
-
Type.new(Coercions::Form.method(:to_date), Date)
|
10
|
+
Type.new(Coercions::Form.method(:to_date), primitive: Date)
|
11
11
|
end
|
12
12
|
|
13
13
|
register('form.date_time') do
|
14
|
-
Type.new(Coercions::Form.method(:to_date_time), DateTime)
|
14
|
+
Type.new(Coercions::Form.method(:to_date_time), primitive: DateTime)
|
15
15
|
end
|
16
16
|
|
17
17
|
register('form.time') do
|
18
|
-
Type.new(Coercions::Form.method(:to_time), Time)
|
18
|
+
Type.new(Coercions::Form.method(:to_time), primitive: Time)
|
19
19
|
end
|
20
20
|
|
21
21
|
register('form.true') do
|
22
|
-
Type.new(Coercions::Form.method(:to_true), TrueClass)
|
22
|
+
Type.new(Coercions::Form.method(:to_true), primitive: TrueClass)
|
23
23
|
end
|
24
24
|
|
25
25
|
register('form.false') do
|
26
|
-
Type.new(Coercions::Form.method(:to_false), FalseClass)
|
26
|
+
Type.new(Coercions::Form.method(:to_false), primitive: FalseClass)
|
27
27
|
end
|
28
28
|
|
29
29
|
register('form.bool') do
|
@@ -31,15 +31,15 @@ module Dry
|
|
31
31
|
end
|
32
32
|
|
33
33
|
register('form.int') do
|
34
|
-
Type.new(Coercions::Form.method(:to_int), Fixnum)
|
34
|
+
Type.new(Coercions::Form.method(:to_int), primitive: Fixnum)
|
35
35
|
end
|
36
36
|
|
37
37
|
register('form.float') do
|
38
|
-
Type.new(Coercions::Form.method(:to_float), Float)
|
38
|
+
Type.new(Coercions::Form.method(:to_float), primitive: Float)
|
39
39
|
end
|
40
40
|
|
41
41
|
register('form.decimal') do
|
42
|
-
Type.new(Coercions::Form.method(:to_decimal), BigDecimal)
|
42
|
+
Type.new(Coercions::Form.method(:to_decimal), primitive: BigDecimal)
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
data/lib/dry/data/value.rb
CHANGED
data/lib/dry/data/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-data
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Solnica
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-01-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thread_safe
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0.1'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: dry-logic
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.1'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.1'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: inflecto
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -166,17 +180,19 @@ files:
|
|
166
180
|
- lib/dry/data.rb
|
167
181
|
- lib/dry/data/coercions/form.rb
|
168
182
|
- lib/dry/data/compiler.rb
|
183
|
+
- lib/dry/data/constrained.rb
|
169
184
|
- lib/dry/data/constraints.rb
|
170
185
|
- lib/dry/data/container.rb
|
171
|
-
- lib/dry/data/
|
186
|
+
- lib/dry/data/decorator.rb
|
187
|
+
- lib/dry/data/default.rb
|
188
|
+
- lib/dry/data/enum.rb
|
172
189
|
- lib/dry/data/optional.rb
|
173
190
|
- lib/dry/data/struct.rb
|
174
191
|
- lib/dry/data/sum_type.rb
|
175
192
|
- lib/dry/data/type.rb
|
176
193
|
- lib/dry/data/type/array.rb
|
177
|
-
- lib/dry/data/type/constrained.rb
|
178
|
-
- lib/dry/data/type/enum.rb
|
179
194
|
- lib/dry/data/type/hash.rb
|
195
|
+
- lib/dry/data/type_builder.rb
|
180
196
|
- lib/dry/data/types.rb
|
181
197
|
- lib/dry/data/types/form.rb
|
182
198
|
- lib/dry/data/value.rb
|
data/lib/dry/data/dsl.rb
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
require 'dry/data/constraints'
|
2
|
-
|
3
|
-
module Dry
|
4
|
-
module Data
|
5
|
-
class Type
|
6
|
-
class Constrained < Type
|
7
|
-
attr_reader :rule
|
8
|
-
|
9
|
-
def initialize(constructor, primitive, rule)
|
10
|
-
super(constructor, primitive)
|
11
|
-
@rule = rule
|
12
|
-
end
|
13
|
-
|
14
|
-
def call(input)
|
15
|
-
result = super(input)
|
16
|
-
|
17
|
-
if valid?(result)
|
18
|
-
result
|
19
|
-
else
|
20
|
-
raise ConstraintError, "#{input.inspect} violates constraints"
|
21
|
-
end
|
22
|
-
end
|
23
|
-
alias_method :[], :call
|
24
|
-
|
25
|
-
def valid?(input)
|
26
|
-
super && rule.(input).success?
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def constrained(options)
|
31
|
-
Constrained.new(constructor, primitive, Data.Rule(primitive, options))
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
data/lib/dry/data/type/enum.rb
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
module Dry
|
2
|
-
module Data
|
3
|
-
class Type
|
4
|
-
class Enum
|
5
|
-
attr_reader :values
|
6
|
-
attr_reader :type
|
7
|
-
|
8
|
-
def initialize(values, type)
|
9
|
-
@values = values.freeze
|
10
|
-
@type = type
|
11
|
-
values.each(&:freeze)
|
12
|
-
end
|
13
|
-
|
14
|
-
def primitive
|
15
|
-
type.primitive
|
16
|
-
end
|
17
|
-
|
18
|
-
def call(input)
|
19
|
-
case input
|
20
|
-
when Fixnum then type[values[input]]
|
21
|
-
else type[input] end
|
22
|
-
end
|
23
|
-
alias_method :[], :call
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|