monadic 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +21 -1
- data/README.md +26 -8
- data/lib/monadic/either.rb +9 -0
- data/lib/monadic/validation.rb +12 -0
- data/lib/monadic/version.rb +1 -1
- data/spec/either_spec.rb +9 -9
- data/spec/examples/hotel_booking_spec.rb +12 -41
- data/spec/maybe_spec.rb +1 -0
- data/spec/try_spec.rb +4 -4
- data/spec/validation_spec.rb +28 -0
- metadata +4 -4
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,26 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## v0.
|
3
|
+
## v0.7
|
4
|
+
**BREAKING CHANGES**
|
5
|
+
|
6
|
+
Rename `Either#else` to `Either#or` to be consistent with `Maybe`
|
7
|
+
Using `Either#else` will give you a deprecation warning for now, it will be removed in v1.0
|
8
|
+
|
9
|
+
New `Validation#fill` method for validating filling Structs.
|
10
|
+
|
11
|
+
ExampleStruct = Struct.new(:a, :b)
|
12
|
+
module ExampleValidator
|
13
|
+
extend self
|
14
|
+
def a(params); Try { params[0] }.or 'a cannot be empty'; end
|
15
|
+
def b(params); Try { params[1] }.or 'b cannot be empty'; end
|
16
|
+
end
|
17
|
+
|
18
|
+
result = Validation.fill(ExampleStruct, [1, 2], ExampleValidator) == Sucess
|
19
|
+
example = result.fetch
|
20
|
+
example.a == 1
|
21
|
+
example.b == 2
|
22
|
+
|
23
|
+
## v0.6
|
4
24
|
|
5
25
|
Extend `Try` to also support lambdas.
|
6
26
|
|
data/README.md
CHANGED
@@ -90,10 +90,12 @@ Maybe(1).to_s == '1'
|
|
90
90
|
```
|
91
91
|
|
92
92
|
`#or`
|
93
|
+
```ruby
|
93
94
|
Maybe(nil).or(1) == Just(1)
|
94
95
|
Maybe(1).or(2) == Just(1)
|
95
96
|
Maybe(nil).or(1) == Just(1)
|
96
97
|
Maybe(nil).or(nil) == Nothing
|
98
|
+
```
|
97
99
|
|
98
100
|
Falsey values (kind-of) examples:
|
99
101
|
|
@@ -217,17 +219,17 @@ Success(params).
|
|
217
219
|
bind ->(path) { load_stuff(params) } #
|
218
220
|
```
|
219
221
|
|
220
|
-
`Either#
|
222
|
+
`Either#or` allows to provide alternate values in case of `Failure`:
|
221
223
|
|
222
224
|
```ruby
|
223
|
-
Either(false == true).
|
224
|
-
Success('truth needs no sugar coating').
|
225
|
+
Either(false == true).or('false was not true') == Failure(false was not true)
|
226
|
+
Success('truth needs no sugar coating').or('all lies') == Success('truth needs no sugar coating')
|
225
227
|
```
|
226
228
|
|
227
|
-
`Either#
|
229
|
+
`Either#or` supports also a block
|
228
230
|
|
229
231
|
```ruby
|
230
|
-
Failure(1).
|
232
|
+
Failure(1).or {|other| 1 + 2 } == Failure(3)
|
231
233
|
```
|
232
234
|
|
233
235
|
Storing intermediate results in instance variables is possible, although it is not very elegant:
|
@@ -251,16 +253,16 @@ Try { Date.parse('2012-02-30') } == Failu
|
|
251
253
|
Try { Date.parse('2012-02-28') } == Success
|
252
254
|
|
253
255
|
date_s = '2012-02-30'
|
254
|
-
Try { Date.parse(date_s) }.
|
256
|
+
Try { Date.parse(date_s) }.or {|e| "#{e.message} #{date_s}" } == Failure("invalid date 2012-02-30")
|
255
257
|
|
256
258
|
# with a predicate
|
257
259
|
Try(true) == Success(true)
|
258
260
|
Try(false) { "string" } == Failure("string")
|
259
|
-
Try(false) { "success"}.
|
261
|
+
Try(false) { "success"}.or("fail") == Failure("fail")
|
260
262
|
|
261
263
|
VALID_TITLES = %w[MR MRS]
|
262
264
|
title = 'MS'
|
263
|
-
Try(VALID_TITLES.inlude?(title)) { title }.
|
265
|
+
Try(VALID_TITLES.inlude?(title)) { title }.or { "title must be on of '#{VALID_TITLES.join(', ')}'' but was '#{title}'"}
|
264
266
|
== "title must be on of 'MR, MR' but was 'MS'"
|
265
267
|
```
|
266
268
|
|
@@ -306,6 +308,22 @@ The above example, returns either `Success([32, :sober, :male])` or `Failure(['A
|
|
306
308
|
|
307
309
|
See also [examples/validation.rb](https://github.com/pzol/monadic/blob/master/examples/validation.rb) and [examples/validation_module](https://github.com/pzol/monadic/blob/master/examples/validation_module.rb)
|
308
310
|
|
311
|
+
`Validation#fill` method for validating filling Structs:
|
312
|
+
|
313
|
+
```ruby
|
314
|
+
ExampleStruct = Struct.new(:a, :b)
|
315
|
+
module ExampleValidator
|
316
|
+
extend self
|
317
|
+
def a(params); Try { params[0] }.or 'a cannot be empty'; end
|
318
|
+
def b(params); Try { params[1] }.or 'b cannot be empty'; end
|
319
|
+
end
|
320
|
+
|
321
|
+
result = Validation.fill(ExampleStruct, [1, 2], ExampleValidator) == Sucess
|
322
|
+
example = result.fetch
|
323
|
+
example.a == 1
|
324
|
+
example.b == 2
|
325
|
+
```
|
326
|
+
|
309
327
|
### Monad
|
310
328
|
All Monads include this module. Standalone it is an Identity monad. Not useful on its own. It's methods are usable on all its descendants.
|
311
329
|
|
data/lib/monadic/either.rb
CHANGED
@@ -36,7 +36,15 @@ module Monadic
|
|
36
36
|
|
37
37
|
# If it is a Failure it will return a new Failure with the provided value
|
38
38
|
# @return [Success, Failure]
|
39
|
+
def or(value=nil, &block)
|
40
|
+
return Failure(block.call(@value)) if failure? && block_given?
|
41
|
+
return Failure(value) if failure?
|
42
|
+
return self
|
43
|
+
end
|
44
|
+
|
45
|
+
#@deprecated
|
39
46
|
def else(value=nil, &block)
|
47
|
+
warn 'Either#else is deprecated and will be removed in a future version, use Either#or instead'
|
40
48
|
return Failure(block.call(@value)) if failure? && block_given?
|
41
49
|
return Failure(value) if failure?
|
42
50
|
return self
|
@@ -83,6 +91,7 @@ module Monadic
|
|
83
91
|
def bind(proc=nil, &block)
|
84
92
|
@chain << (proc || block)
|
85
93
|
end
|
94
|
+
alias :>= :bind
|
86
95
|
end
|
87
96
|
|
88
97
|
# @private instance and class methods for +Success+ and +Failure+
|
data/lib/monadic/validation.rb
CHANGED
@@ -29,5 +29,17 @@ module Monadic
|
|
29
29
|
# @failure.fetch << result.fetch) if result.failure?
|
30
30
|
@result << result
|
31
31
|
end
|
32
|
+
|
33
|
+
# Useful to populate a data structure like a Struct, which takes values in its constructor
|
34
|
+
def self.fill(obj, params, validator)
|
35
|
+
properties = validator.methods - Module.methods
|
36
|
+
values = properties.collect {|property| validator.send(property, params) }
|
37
|
+
|
38
|
+
if values.all?(&:success?)
|
39
|
+
Success(obj.new(*values.collect(&:fetch)))
|
40
|
+
else
|
41
|
+
Failure(obj.new(*values))
|
42
|
+
end
|
43
|
+
end
|
32
44
|
end
|
33
45
|
end
|
data/lib/monadic/version.rb
CHANGED
data/spec/either_spec.rb
CHANGED
@@ -57,17 +57,17 @@ describe Monadic::Either do
|
|
57
57
|
error.message.should == 'error'
|
58
58
|
end
|
59
59
|
|
60
|
-
it '#
|
61
|
-
Failure(false).
|
62
|
-
Either(nil).
|
63
|
-
Failure(1).
|
64
|
-
Success(true).
|
65
|
-
Either(true).
|
66
|
-
Success(false).
|
60
|
+
it '#or returns an alternative value considered Success if it is Nothing' do
|
61
|
+
Failure(false).or(true).should == Failure(true)
|
62
|
+
Either(nil).or(true).should == Failure(true)
|
63
|
+
Failure(1).or(nil).should == Failure(nil)
|
64
|
+
Success(true).or(false).should == Success(true)
|
65
|
+
Either(true).or(false).should == Success(true)
|
66
|
+
Success(false).or(true).should == Success(false)
|
67
67
|
end
|
68
68
|
|
69
|
-
it '#
|
70
|
-
(Failure(1).
|
69
|
+
it '#or with a block gets the original value passed' do
|
70
|
+
(Failure(1).or { |other| other + 1 }).should == Failure(2)
|
71
71
|
end
|
72
72
|
|
73
73
|
class User
|
@@ -1,62 +1,30 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'active_support/time'
|
3
3
|
|
4
|
-
module Monadic
|
5
|
-
class Struct < ::Struct
|
6
|
-
def self.create(params, params_module)
|
7
|
-
properties = self.create_properties(params || {}, params_module)
|
8
|
-
|
9
|
-
if properties.values.all?(&:success?)
|
10
|
-
Success(properties)
|
11
|
-
else
|
12
|
-
Failure(properties)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def to_hash
|
17
|
-
Hash[*members.zip(values).flatten]
|
18
|
-
end
|
19
|
-
|
20
|
-
def unwrap
|
21
|
-
::Struct.new(*members).new(*values.map(&:fetch))
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
def self.create_properties(params, params_module)
|
26
|
-
properties = params_module.methods - Module.methods
|
27
|
-
request_klas = self.new(*properties)
|
28
|
-
properties.reduce(request_klas.new) do |request, property|
|
29
|
-
request[property] = params_module.send(property, params)
|
30
|
-
request
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
4
|
describe 'Hotel Booking Example' do
|
37
5
|
module HotelBookingRequestParams
|
38
6
|
extend self
|
39
7
|
|
40
8
|
def hotel_code(params)
|
41
9
|
param = params['hotel_code']
|
42
|
-
Try(param =~ /^[A-Z]{3}[A-Z0-9]{4}$/) { param }.
|
10
|
+
Try(param =~ /^[A-Z]{3}[A-Z0-9]{4}$/) { param }.or "hotel_code must be of pattern XXX0001, got '#{param}'"
|
43
11
|
end
|
44
12
|
|
45
13
|
def nights(params)
|
46
14
|
value = params.fetch('nights', 0).to_i
|
47
|
-
Try(value > 0) { value }.
|
15
|
+
Try(value > 0) { value }.or "nights must be a number greater than 0, got '#{params['nights']}'"
|
48
16
|
end
|
49
17
|
|
50
18
|
ROOM_OCCUPANCIES = {'SR' => 1, 'DR' => 2, 'TR' => 3, 'QR' => 4 }
|
51
19
|
VALID_ROOM_TYPES = ROOM_OCCUPANCIES.keys
|
52
20
|
def room_type(params)
|
53
21
|
param = params['room_type']
|
54
|
-
Try(VALID_ROOM_TYPES.include?(param)) { param }.
|
22
|
+
Try(VALID_ROOM_TYPES.include?(param)) { param }.or "room_type must be one of '#{VALID_ROOM_TYPES.join(', ')}', got '#{param}'"
|
55
23
|
end
|
56
24
|
|
57
25
|
def check_in(params)
|
58
26
|
param = params['check_in']
|
59
|
-
Try { Date.parse(param) }.
|
27
|
+
Try { Date.parse(param) }.or {|e| "check_in #{e.message}, got '#{param}'" }
|
60
28
|
end
|
61
29
|
|
62
30
|
OCCUPANCIES = [1, 2, 3, 4]
|
@@ -64,7 +32,7 @@ describe 'Hotel Booking Example' do
|
|
64
32
|
guests = params.select {|p| p =~ /^guest/ }.values.compact
|
65
33
|
rt = params['room_type']
|
66
34
|
occupancy = ROOM_OCCUPANCIES.fetch(rt, 99)
|
67
|
-
Try(guests.count == occupancy) { guests }.
|
35
|
+
Try(guests.count == occupancy) { guests }.or "guests number must match the room_type '#{rt}':#{occupancy}, got #{guests.count}: '#{guests.join(', ')}'"
|
68
36
|
end
|
69
37
|
|
70
38
|
end
|
@@ -81,7 +49,7 @@ describe 'Hotel Booking Example' do
|
|
81
49
|
it 'builds a valid request' do
|
82
50
|
result = prepare params_proto
|
83
51
|
result.should be_a Success
|
84
|
-
request = result.fetch
|
52
|
+
request = result.fetch # the valid object, ready to use
|
85
53
|
request.hotel_code.should == "STO0001"
|
86
54
|
request.check_in.should == Date.new(2012, 06, 15)
|
87
55
|
request.nights.should == 3
|
@@ -90,7 +58,9 @@ describe 'Hotel Booking Example' do
|
|
90
58
|
end
|
91
59
|
|
92
60
|
def prepare(params)
|
93
|
-
|
61
|
+
properties = HotelBookingRequestParams.methods - Module.methods
|
62
|
+
klas = Struct.new(*properties)
|
63
|
+
Monadic::Validation.fill(klas, params, HotelBookingRequestParams)
|
94
64
|
end
|
95
65
|
|
96
66
|
it 'reports too few guests' do
|
@@ -113,13 +83,14 @@ describe 'Hotel Booking Example' do
|
|
113
83
|
end
|
114
84
|
|
115
85
|
it 'builds a failure request with no params' do
|
116
|
-
result = prepare(
|
86
|
+
result = prepare({})
|
117
87
|
result.should be_a Failure
|
118
88
|
request = result.fetch
|
119
89
|
request.hotel_code.should == Failure("hotel_code must be of pattern XXX0001, got ''")
|
120
90
|
request.nights.should == Failure("nights must be a number greater than 0, got ''")
|
121
91
|
request.room_type.should == Failure("room_type must be one of 'SR, DR, TR, QR', got ''")
|
122
|
-
request.check_in.should
|
92
|
+
request.check_in.should be_a Failure
|
93
|
+
request.check_in.fetch.should =~ /check_in.*, got ''/
|
123
94
|
request.guests.should == Failure("guests number must match the room_type '':99, got 0: ''")
|
124
95
|
end
|
125
96
|
|
data/spec/maybe_spec.rb
CHANGED
data/spec/try_spec.rb
CHANGED
@@ -23,7 +23,7 @@ describe 'Monadic::Try' do
|
|
23
23
|
it 'with a param and with a block it returns the block result' do
|
24
24
|
Try(true) { 1 }.should == Success(1)
|
25
25
|
Try(false) { 2 }.should == Failure(2)
|
26
|
-
Try(false) { 1 }.
|
26
|
+
Try(false) { 1 }.or { 2 }.should == Failure(2)
|
27
27
|
end
|
28
28
|
|
29
29
|
it 'with a proc and no block it evaluates the proc' do
|
@@ -40,8 +40,8 @@ describe 'Monadic::Try' do
|
|
40
40
|
Try { "string" }.should == Success("string")
|
41
41
|
end
|
42
42
|
|
43
|
-
it 'combined with
|
44
|
-
Try { Date.parse('2012-02-30') }.
|
45
|
-
Try { Date.parse('2012-02-28') }.
|
43
|
+
it 'combined with or and a block' do
|
44
|
+
Try { Date.parse('2012-02-30') }.or {|e| "Exception: #{e.message}" }.should == Failure("Exception: invalid date")
|
45
|
+
Try { Date.parse('2012-02-28') }.or {|e| "Exception: #{e.message}" }.should == Success(Date.parse('2012-02-28'))
|
46
46
|
end
|
47
47
|
end
|
data/spec/validation_spec.rb
CHANGED
@@ -44,4 +44,32 @@ describe Monadic::Validation do
|
|
44
44
|
success.should be_a Success
|
45
45
|
success.should == Success([30, :sober, :male])
|
46
46
|
end
|
47
|
+
|
48
|
+
describe '#fill' do
|
49
|
+
ExampleStruct = Struct.new(:a, :b)
|
50
|
+
module ExampleValidator
|
51
|
+
extend self
|
52
|
+
def a(params); Try { params[0] }.or 'a cannot be empty'; end
|
53
|
+
def b(params); Try { params[1] }.or 'b cannot be empty'; end
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'works with a struct, returns the object wrapped in Success' do
|
57
|
+
params = [1, 2]
|
58
|
+
result = Validation.fill(ExampleStruct, params, ExampleValidator)
|
59
|
+
result.should be_a Success
|
60
|
+
example = result.fetch
|
61
|
+
|
62
|
+
example.a.should == 1
|
63
|
+
example.b.should == 2
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'on Failure, each individual field contains a Success or Failure' do
|
67
|
+
params = [1]
|
68
|
+
result = Validation.fill(ExampleStruct, params, ExampleValidator)
|
69
|
+
result.should be_a Failure
|
70
|
+
example = result.fetch
|
71
|
+
example.a.should == Success(1)
|
72
|
+
example.b.should == Failure('b cannot be empty')
|
73
|
+
end
|
74
|
+
end
|
47
75
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: monadic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-07-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -177,7 +177,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
177
177
|
version: '0'
|
178
178
|
segments:
|
179
179
|
- 0
|
180
|
-
hash:
|
180
|
+
hash: 494077126848857519
|
181
181
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
182
182
|
none: false
|
183
183
|
requirements:
|
@@ -186,7 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
186
186
|
version: '0'
|
187
187
|
segments:
|
188
188
|
- 0
|
189
|
-
hash:
|
189
|
+
hash: 494077126848857519
|
190
190
|
requirements: []
|
191
191
|
rubyforge_project:
|
192
192
|
rubygems_version: 1.8.24
|