monadic 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,6 +1,26 @@
1
1
  # Changelog
2
2
 
3
- ## v0.6 (unreleased)
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#else` allows to provide alternate values in case of `Failure`:
222
+ `Either#or` allows to provide alternate values in case of `Failure`:
221
223
 
222
224
  ```ruby
223
- Either(false == true).else('false was not true') == Failure(false was not true)
224
- Success('truth needs no sugar coating').else('all lies') == 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#else` supports also a block
229
+ `Either#or` supports also a block
228
230
 
229
231
  ```ruby
230
- Failure(1).else {|other| 1 + 2 } == Failure(3)
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) }.else {|e| "#{e.message} #{date_s}" } == Failure("invalid date 2012-02-30")
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"}.else("fail") == Failure("fail")
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 }.else { "title must be on of '#{VALID_TITLES.join(', ')}'' but was '#{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
 
@@ -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+
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Monadic
2
- VERSION = "0.6.0"
2
+ VERSION = "0.7.0"
3
3
  end
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 '#else returns an alternative value considered Success if it is Nothing' do
61
- Failure(false).else(true).should == Failure(true)
62
- Either(nil).else(true).should == Failure(true)
63
- Failure(1).else(nil).should == Failure(nil)
64
- Success(true).else(false).should == Success(true)
65
- Either(true).else(false).should == Success(true)
66
- Success(false).else(true).should == 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 '#else with a block gets the original value passed' do
70
- (Failure(1).else { |other| other + 1 }).should == Failure(2)
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 }.else "hotel_code must be of pattern XXX0001, got '#{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 }.else "nights must be a number greater than 0, got '#{params['nights']}'"
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 }.else "room_type must be one of '#{VALID_ROOM_TYPES.join(', ')}', got '#{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) }.else {|e| "check_in #{e.message}, got '#{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 }.else "guests number must match the room_type '#{rt}':#{occupancy}, got #{guests.count}: '#{guests.join(', ')}'"
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.unwrap # the valid object, ready to use
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
- Monadic::Struct.create(params, HotelBookingRequestParams)
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(nil)
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 == Failure("check_in can't convert nil into String, got ''")
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
@@ -64,6 +64,7 @@ describe Monadic::Maybe do
64
64
  Nothing.or(Just(nil)).should == Just(nil)
65
65
  Nothing.something.or(1).should == Just(1)
66
66
  Nothing.something.or(Just(nil)).should == Just(nil)
67
+ Nothing.or('').should == Just('')
67
68
  end
68
69
  end
69
70
 
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 }.else { 2 }.should == Failure(2)
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 else and a block' do
44
- Try { Date.parse('2012-02-30') }.else {|e| "Exception: #{e.message}" }.should == Failure("Exception: invalid date")
45
- Try { Date.parse('2012-02-28') }.else {|e| "Exception: #{e.message}" }.should == Success(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
@@ -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.6.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-06-24 00:00:00.000000000 Z
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: 2246458514498594220
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: 2246458514498594220
189
+ hash: 494077126848857519
190
190
  requirements: []
191
191
  rubyforge_project:
192
192
  rubygems_version: 1.8.24