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 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