monadic 0.5.0 → 0.6.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,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.6 (unreleased)
4
+
5
+ Extend `Try` to also support lambdas.
6
+
7
+ The first parameter of `Try` can be used as a predicate and the block as formatter
8
+
9
+ Try(true) == Success(true)
10
+ Try(false) { "string" } == Failure("string")
11
+ Try(false) { "success" }.else("fail") == Failure("fail")
12
+
13
+ `Maybe` supports `#proxy` to avoid naming clashes between the underlying value and `Maybe` itself.
14
+
15
+ Maybe({a: 1}).proxy.fetch(:a) == Maybe(1)
16
+ # this is in effect syntactic sugar for
17
+ Maybe({a: 1}).map {|e| e.fetch(:a) }
18
+
19
+ `Nothing#or` coerces the or value into a Maybe, thus
20
+
21
+ Maybe(nil).or(1) == Just(1)
22
+ Maybe(nil).or(nil) == Nothing
23
+
24
+ `Either` coerces now `Just` and `Nothing` into `Success` and `Failure`
25
+
26
+ Either(Just.new(1)) == Success(1)
27
+ Either(Nothing) == Failure(Nothing)
28
+
3
29
  ## v0.5.0
4
30
 
5
31
  Add the `Try` helper which works similar to Either, but takes a block
@@ -38,7 +64,7 @@ Instead the new `Monad#flat_map` function operates on the underlying value as on
38
64
  ## v0.1.1
39
65
 
40
66
  `Either()` coerces only `StandardError` to `Failure`, other exceptions higher in the hierarchy are will break the flow.
41
- Thanks to @pithyless for the suggestion.
67
+ Thanks to @pithyless for the suggestion.
42
68
 
43
69
  Monad is now a Module instead of a Class as previously. This fits the Monad metaphor better. Each monad must now implement the unit method itself, which is the correct way to do anyway.
44
70
 
@@ -60,7 +86,7 @@ See [examples/validation.rb](https://github.com/pzol/monadic/blob/master/example
60
86
  and [examples/validation_module](https://github.com/pzol/monadic/blob/master/examples/validation_module.rb)
61
87
 
62
88
 
63
- ## v0.0.7
89
+ ## v0.0.7
64
90
 
65
91
  Implements the #map method for all Monads. It works on value types and on Enumerable collections.
66
92
  Provide a proc or a block and it will return a transformed value or collection boxed back in the monad.
@@ -88,7 +114,7 @@ Removed `Maybe#fetch` call with block`
88
114
  Added Travis-Ci integration, [![Build Status](https://secure.travis-ci.org/pzol/monadic.png?branch=master)](http://travis-ci.org/pzol/monadic)
89
115
 
90
116
 
91
- ## v0.0.5
117
+ ## v0.0.5
92
118
 
93
119
  Removed the `#chain` method alias for bind in `Either`.
94
120
 
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  helps dealing with exceptional situations, it comes from the sphere of functional programming and bringing the goodies I have come to love in [Scala](http://www.scala-lang.org/) and [Haskell](http://www.haskell.org/) to my ruby projects.
5
5
 
6
- My motivation to create this gem was that I often work with nested Hashes and need to reach deeply inside of them so my code is sprinkled with things like some_hash.fetch(:one, {}).fetch(:two, {}).fetch(:three, "unknown").
6
+ My motivation to create this gem was that I often work with nested Hashes and need to reach deeply inside of them so my code is sprinkled with things like some_hash.fetch(:one, {}).fetch(:two, {}).fetch(:three, "unknown").
7
7
 
8
8
  We have the following monadics (monads, functors, applicatives and variations):
9
9
 
@@ -21,11 +21,11 @@ A monad is most effectively described as a computation that eventually returns a
21
21
  ### Maybe
22
22
  Most people probably will be interested in the Maybe monad, as it solves the problem with nil invocations, similar to [andand](https://github.com/raganwald/andand) and others.
23
23
 
24
- Maybe is an optional type, which helps to handle error conditions gracefully. The one thing to remember about option is: 'What goes into the Maybe, stays in the Maybe'.
24
+ Maybe is an optional type, which helps to handle error conditions gracefully. The one thing to remember about option is: 'What goes into the Maybe, stays in the Maybe'.
25
25
 
26
26
 
27
27
  ```ruby
28
- Maybe(User.find(123)).name._ # ._ is a shortcut for .fetch
28
+ Maybe(User.find(123)).name._ # ._ is a shortcut for .fetch
29
29
 
30
30
  # if you prefer the alias Maybe instead of option
31
31
  Maybe(User.find(123)).name._
@@ -52,7 +52,7 @@ Maybe(nil).truly? == false
52
52
  # Just stays Just, unless you unbox it
53
53
  Maybe('FOO').downcase == Just('foo')
54
54
  Maybe('FOO').downcase.fetch == "foo" # unboxing the value
55
- Maybe('FOO').downcase._ == "foo"
55
+ Maybe('FOO').downcase._ == "foo"
56
56
  Maybe('foo').empty? == false # always non-empty
57
57
  Maybe('foo').truly? == true # depends on the boxed value
58
58
  Maybe(false).empty? == false
@@ -60,8 +60,8 @@ Maybe(false).truly? == false
60
60
  ```
61
61
 
62
62
  Map, select:
63
-
64
- ```ruby
63
+
64
+ ```ruby
65
65
  Maybe(123).map { |value| User.find(value) } == Just(someUser) # if user found
66
66
  Maybe(0).map { |value| User.find(value) } == Nothing # if user not found
67
67
  Maybe([1,2]).map { |value| value.to_s } == Just(["1, 2"]) # for all Enumerables
@@ -90,9 +90,10 @@ Maybe(1).to_s == '1'
90
90
  ```
91
91
 
92
92
  `#or`
93
- Maybe(nil).or(1) == 1
94
- Maybe(1).or(2) == 1
95
- Maybe(nil).or(nil) == Just(nil)
93
+ Maybe(nil).or(1) == Just(1)
94
+ Maybe(1).or(2) == Just(1)
95
+ Maybe(nil).or(1) == Just(1)
96
+ Maybe(nil).or(nil) == Nothing
96
97
 
97
98
  Falsey values (kind-of) examples:
98
99
 
@@ -110,6 +111,14 @@ Remember! a Maybe is never false (in Ruby terms), if you want to know if it is f
110
111
 
111
112
  `#truly?` will return true or false, always.
112
113
 
114
+ `Maybe` supports `#proxy` to avoid naming clashes between the underlying value and `Maybe` itself.
115
+
116
+ ```ruby
117
+ Maybe({a: 1}).proxy.fetch(:a) == Maybe(1)
118
+ # this is in effect syntactic sugar for
119
+ Maybe({a: 1}).map {|e| e.fetch(:a) }
120
+ ```
121
+
113
122
  Slug example
114
123
 
115
124
  ```ruby
@@ -158,11 +167,11 @@ The `Either()` wrapper works like a coercon. It will treat all falsey values `ni
158
167
  ```ruby
159
168
  result = parse_and_validate_params(params). # must return a Success or Failure inside
160
169
  bind ->(user_id) { User.find(user_id) }. # if #find returns null it will become a Failure
161
- bind ->(user) { authorized?(user); user }. # if authorized? raises an Exception, it will be a Failure
170
+ bind ->(user) { authorized?(user); user }. # if authorized? raises an Exception, it will be a Failure
162
171
  bind ->(user) { UserDecorator(user) }
163
172
 
164
173
  if result.success?
165
- @user = result.fetch # result.fetch or result._ contains the
174
+ @user = result.fetch # result.fetch or result._ contains the inner value
166
175
  render 'page'
167
176
  else
168
177
  @error = result.fetch
@@ -205,7 +214,7 @@ Another example:
205
214
  ```ruby
206
215
  Success(params).
207
216
  bind ->(params) { Either(params.fetch(:path)) } # fails if params does not contain :path
208
- bind ->(path) { load_stuff(params) } #
217
+ bind ->(path) { load_stuff(params) } #
209
218
  ```
210
219
 
211
220
  `Either#else` allows to provide alternate values in case of `Failure`:
@@ -231,18 +240,29 @@ result = Either.chain do
231
240
  end
232
241
 
233
242
  result == Success(101)
234
- ```
243
+ ```
235
244
 
236
245
  #### Try
237
246
 
238
- `Try` helper which works similar to Either, but takes a block
247
+ `Try` helper which works similar to Either, but takes a block. Think of it as a secure if-then-else.
239
248
 
240
249
  ```ruby
241
250
  Try { Date.parse('2012-02-30') } == Failure
242
251
  Try { Date.parse('2012-02-28') } == Success
243
- Try { Date.parse('2012-02-30') }.else {|e| "Exception: #{e.message}" } == Failure("Exception: invalid date")
244
- ```
245
252
 
253
+ date_s = '2012-02-30'
254
+ Try { Date.parse(date_s) }.else {|e| "#{e.message} #{date_s}" } == Failure("invalid date 2012-02-30")
255
+
256
+ # with a predicate
257
+ Try(true) == Success(true)
258
+ Try(false) { "string" } == Failure("string")
259
+ Try(false) { "success"}.else("fail") == Failure("fail")
260
+
261
+ VALID_TITLES = %w[MR MRS]
262
+ title = 'MS'
263
+ Try(VALID_TITLES.inlude?(title)) { title }.else { "title must be on of '#{VALID_TITLES.join(', ')}'' but was '#{title}'"}
264
+ == "title must be on of 'MR, MR' but was 'MS'"
265
+ ```
246
266
 
247
267
  ### Validation
248
268
  The Validation applicative functor, takes a list of checks within a block. Each check must return either Success of Failure.
@@ -267,13 +287,13 @@ def validate(person)
267
287
  when :sober, :tipsy; Success(sobriety)
268
288
  when :drunk ; Failure('No drunks allowed')
269
289
  else Failure("Sobriety state '#{sobriety}' is not allowed")
270
- end
290
+ end
271
291
  }
272
292
 
273
293
  check_gender = ->(gender) {
274
294
  gender == :male || gender == :female ? Success(gender) : Failure("Invalid gender #{gender}")
275
295
  }
276
-
296
+
277
297
  Validation() do
278
298
  check { check_age.(person.age); }
279
299
  check { check_sobriety.(person.sobriety) }
@@ -306,8 +326,8 @@ Identity.unit([1,2]).map {|v| v + 1} == Identity([2,
306
326
 
307
327
  __#bind__ allows (priviledged) access to the boxed value. This is the traditional _no-magic_ `#bind` as found in Haskell,
308
328
  You` are responsible for re-wrapping the value into a Monad again.
309
-
310
- ```ruby
329
+
330
+ ```ruby
311
331
  # due to the way it works, it will simply return the value, don't rely on this though, different Monads may
312
332
  # implement bind differently (e.g. Maybe involves some _magic_)
313
333
  Identity.unit('foo').bind(&:capitalize) == Foo
@@ -356,8 +376,8 @@ Or install it yourself as:
356
376
  $ gem install monadic
357
377
 
358
378
  ## Compatibility
359
- Monadic is tested under ruby MRI 1.9.2, 1.9.3, jruby 1.9 mode, rbx 1.9 mode.
360
-
379
+ Monadic is tested under ruby MRI 1.9.2, 1.9.3
380
+ jruby 1.9 mode, rbx 1.9 mode are currently not passing the tests on travis
361
381
 
362
382
  ## Contributing
363
383
 
@@ -8,7 +8,9 @@ module Monadic
8
8
 
9
9
  def self.unit(value)
10
10
  return value if value.is_a? Either
11
- return Failure.new(value) if value.nil? || (value.respond_to?(:empty?) && value.empty?) || !value
11
+ return Nothing if value.is_a? Nothing
12
+ return Success.new(value.fetch) if value.is_a? Just
13
+ return Failure.new(value) if value.nil? || (value.respond_to?(:empty?) && value.empty?) || !value
12
14
  return Success.new(value)
13
15
  end
14
16
 
data/lib/monadic/maybe.rb CHANGED
@@ -1,13 +1,13 @@
1
1
  module Monadic
2
- class Maybe
2
+ class Maybe
3
3
  include Monadic::Monad
4
-
4
+
5
5
  def self.unit(value)
6
6
  return Nothing if value.nil? || (value.respond_to?(:empty?) && value.empty?)
7
7
  return Just.new(value)
8
8
  end
9
9
 
10
- # Initialize is private, because it always would return an instance of Maybe, but Just or Nothing
10
+ # Initialize is private, because it always would return an instance of Maybe, but Just or Nothing
11
11
  # are required (Maybe is abstract).
12
12
  private_class_method :new
13
13
 
@@ -44,6 +44,20 @@ module Monadic
44
44
  Maybe(@value.__send__(m, *args))
45
45
  end
46
46
 
47
+ class Proxy < BasicObject
48
+ def initialize(maybe)
49
+ @maybe = maybe
50
+ end
51
+
52
+ def method_missing(m, *args)
53
+ @maybe.map { |e| e.__send__(m, *args) }
54
+ end
55
+ end
56
+
57
+ def proxy
58
+ @proxy ||= Proxy.new(self)
59
+ end
60
+
47
61
  # @return always self for Just
48
62
  def or(other)
49
63
  self
@@ -66,13 +80,12 @@ module Monadic
66
80
  self
67
81
  end
68
82
 
69
- # @return an alternative value, the passed value is NOT coerced into Maybe, thus Nothing.or(nil) will be Just(nil)
83
+ # @return an alternative value, the passed value is coerced into Maybe, thus Nothing.or(1) will be Just(1)
70
84
  def or(other)
71
- Just.new(other)
85
+ Maybe.unit(other)
72
86
  end
73
87
 
74
88
  # def respond_to?
75
-
76
89
  # end
77
90
 
78
91
  def to_ary
@@ -87,6 +100,10 @@ module Monadic
87
100
  def truly?
88
101
  false
89
102
  end
103
+
104
+ def empty?
105
+ true
106
+ end
90
107
  end
91
108
  end
92
109
 
data/lib/monadic/try.rb CHANGED
@@ -1,8 +1,12 @@
1
1
  module Monadic
2
- def Try(proc = nil, &block)
2
+ ## Wrap a block or a predicate to always return Success or Failure.
3
+ ## It will catch StandardError and return as a Failure.
4
+ def Try(arg = nil, &block)
3
5
  begin
4
- return Either(proc) unless proc.nil? || proc.is_a?(Proc)
5
- return Either((proc || block).call)
6
+ predicate = arg.is_a?(Proc) ? arg.call : arg
7
+ return Either(block.call) if block_given? && predicate.nil?
8
+ return Either(predicate).class.unit(block.call) if block_given? # use predicate for Success/Failure and block for value
9
+ return Either(predicate)
6
10
  rescue => error
7
11
  Failure(error)
8
12
  end
@@ -1,3 +1,3 @@
1
1
  module Monadic
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
data/spec/either_spec.rb CHANGED
@@ -39,6 +39,10 @@ describe Monadic::Either do
39
39
  Failure(1).should_not == Failure(2)
40
40
  end
41
41
 
42
+ it 'Either(Nothing) is a Failure' do
43
+ Either(Nothing).should == Failure(Nothing)
44
+ end
45
+
42
46
  it 'wraps a nil result to Failure' do
43
47
  Success(nil).bind { nil }.should == Failure(nil)
44
48
  end
@@ -241,6 +245,14 @@ describe Monadic::Either do
241
245
  either.fetch.should be_a KeyError
242
246
  end
243
247
 
248
+ it 'Either(Nothing) returns a Failure' do
249
+ Either(Nothing).should == Failure(Nothing)
250
+ end
251
+
252
+ it 'Either(Just) returns a Success' do
253
+ Either(Just.new(1)).should == Success(1)
254
+ end
255
+
244
256
  it 'instance variables' do
245
257
  result = Either.chain do
246
258
  bind { @map = { one: 1, two: 2 } }
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ class BSONTestConverter
4
+ class InvalidBSONString < StandardError; end
5
+ def self.from_string(s)
6
+ raise InvalidBSONString unless s =~ /^[0-9a-z]{24}$/
7
+ return self.new(s)
8
+ end
9
+
10
+ def initialize(s)
11
+ @bson = s
12
+ end
13
+
14
+ def to_s
15
+ @bson
16
+ end
17
+
18
+ def ==(other)
19
+ return 0 unless other.is_a? BSONTestConverter
20
+ return to_s <=> other.to_s
21
+ end
22
+ end
23
+
24
+ class DatabaseReader
25
+ def self.find(id)
26
+ return { 'request_id' => '197412101130' } if id.to_s == '4fe48bcfcf79520644088180'
27
+ end
28
+ end
29
+
30
+ module Transaction
31
+ def self.fetch(params)
32
+ return Failure('params must be a Hash') unless params.is_a? Hash
33
+
34
+ Either(Maybe(params)['id']).else('id is missing').
35
+ >= {|v| Try { BSONTestConverter.from_string(v) }.else("id '#{v}' is not a valid BSON id") }.
36
+ >= {|v| Try { DatabaseReader.find(v) }.else("'#{v}' not found") }
37
+ end
38
+
39
+ def self.logs(request_id)
40
+ Success([request_id])
41
+ end
42
+ end
43
+
44
+ describe 'builder' do
45
+ # id = BSON::ObjectId.from_string(params[:id])
46
+ # @tr = MongoMapper.database.collection("log").find_one(id)
47
+ # @rid = @tr['request_id']
48
+ # @logs = LogReader.new.links(@rid)
49
+ it 'builds' do
50
+ Transaction.fetch({}).should == Failure('id is missing')
51
+ Transaction.fetch({'id' => '123' }).should == Failure("id '123' is not a valid BSON id")
52
+ Transaction.fetch({'id' => '000000000000000000000000'}).should == Failure("'000000000000000000000000' not found")
53
+
54
+ doc = Transaction.fetch({'id' => '4fe48bcfcf79520644088180'})
55
+ doc.should == Success({"request_id"=>"197412101130"})
56
+
57
+ doc.bind { Failure('logs not found') }.should == Failure('logs not found')
58
+
59
+ logs = doc.
60
+ >= {|v| Transaction.logs(v['request_id']) }
61
+
62
+ logs.should == Success(['197412101130'])
63
+ end
64
+ end
@@ -1,16 +1,15 @@
1
1
  require 'spec_helper'
2
2
  require 'active_support/time'
3
3
 
4
- describe 'Hotel Booking Example' do
5
-
6
- class Request < Struct
4
+ module Monadic
5
+ class Struct < ::Struct
7
6
  def self.create(params, params_module)
8
- value = self.create_value(params || {}, params_module)
7
+ properties = self.create_properties(params || {}, params_module)
9
8
 
10
- if value.values.all?(&:success?)
11
- Success(value)
9
+ if properties.values.all?(&:success?)
10
+ Success(properties)
12
11
  else
13
- Failure(value)
12
+ Failure(properties)
14
13
  end
15
14
  end
16
15
 
@@ -19,12 +18,11 @@ describe 'Hotel Booking Example' do
19
18
  end
20
19
 
21
20
  def unwrap
22
- Struct.new(*members).new(*values.map(&:fetch))
23
- # self.class.new(*values.map(&:fetch))
21
+ ::Struct.new(*members).new(*values.map(&:fetch))
24
22
  end
25
23
 
26
24
  private
27
- def self.create_value(params, params_module)
25
+ def self.create_properties(params, params_module)
28
26
  properties = params_module.methods - Module.methods
29
27
  request_klas = self.new(*properties)
30
28
  properties.reduce(request_klas.new) do |request, property|
@@ -33,57 +31,42 @@ describe 'Hotel Booking Example' do
33
31
  end
34
32
  end
35
33
  end
34
+ end
36
35
 
36
+ describe 'Hotel Booking Example' do
37
37
  module HotelBookingRequestParams
38
38
  extend self
39
39
 
40
40
  def hotel_code(params)
41
- value = params['hotel_code']
42
- case
43
- when value.nil?
44
- Failure('hotel_code must not be empty')
45
- when not(value =~ /^[A-Z]{3}[A-Z0-9]{4}$/)
46
- Failure('hotel_code must be of pattern XXX0001')
47
- else
48
- Success(value)
49
- end
41
+ 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}'"
50
43
  end
51
44
 
52
45
  def nights(params)
53
46
  value = params.fetch('nights', 0).to_i
54
- if value <= 0
55
- Failure("nights must be a number greater than 0 (got '#{params['nights']}')")
56
- else
57
- Success(value)
58
- end
47
+ Try(value > 0) { value }.else "nights must be a number greater than 0, got '#{params['nights']}'"
59
48
  end
60
49
 
61
- VALID_ROOM_TYPE = %w[SR DR TR QR]
50
+ ROOM_OCCUPANCIES = {'SR' => 1, 'DR' => 2, 'TR' => 3, 'QR' => 4 }
51
+ VALID_ROOM_TYPES = ROOM_OCCUPANCIES.keys
62
52
  def room_type(params)
63
- case value = params['room_type']
64
- when nil
65
- Failure('room_type must not be empty')
66
- when is_valid_room_type
67
- Success(value)
68
- else
69
- Failure("room_type '#{value}' must be one of #{VALID_ROOM_TYPE.join(', ')}")
70
- end
53
+ 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}'"
71
55
  end
72
56
 
73
57
  def check_in(params)
74
58
  param = params['check_in']
75
- return Failure("check_in must not be empty") if param.nil?
76
- return Try { Date.parse(param) }.else {|e| "check_in #{e.message} '#{param}'" }
59
+ Try { Date.parse(param) }.else {|e| "check_in #{e.message}, got '#{param}'" }
77
60
  end
78
61
 
79
- private
80
- def is_valid_room_type
81
- Object.new.tap {|matcher|
82
- def matcher.===(value)
83
- VALID_ROOM_TYPE.include? value
84
- end
85
- }
62
+ OCCUPANCIES = [1, 2, 3, 4]
63
+ def guests(params)
64
+ guests = params.select {|p| p =~ /^guest/ }.values.compact
65
+ rt = params['room_type']
66
+ 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(', ')}'"
86
68
  end
69
+
87
70
  end
88
71
 
89
72
  let(:params_proto) do
@@ -96,38 +79,48 @@ describe 'Hotel Booking Example' do
96
79
  end
97
80
 
98
81
  it 'builds a valid request' do
99
- result = Request.create(params_proto, HotelBookingRequestParams)
82
+ result = prepare params_proto
100
83
  result.should be_a Success
101
- request = result.fetch.unwrap
102
- request.hotel_code.should eq "STO0001"
103
- request.nights.should eq 3
84
+ request = result.fetch.unwrap # the valid object, ready to use
85
+ request.hotel_code.should == "STO0001"
86
+ request.check_in.should == Date.new(2012, 06, 15)
87
+ request.nights.should == 3
88
+ request.room_type.should == 'DR'
89
+ request.guests.should == ['Max Payne', 'Jenny Payne']
90
+ end
91
+
92
+ def prepare(params)
93
+ Monadic::Struct.create(params, HotelBookingRequestParams)
94
+ end
95
+
96
+ it 'reports too few guests' do
97
+ result = prepare params_proto.reject {|key| key == 'guest2'}
98
+ result.should be_a Failure
99
+ result.fetch.guests.should == Failure("guests number must match the room_type 'DR':2, got 1: 'Max Payne'")
104
100
  end
105
101
 
106
102
  it 'reports an invalid room_type' do
107
- params = params_proto.merge 'room_type' => 'XX'
108
- result = Request.create(params, HotelBookingRequestParams)
103
+ result = prepare params_proto.merge 'room_type' => 'XX'
109
104
  result.should be_a Failure
110
- request = result.fetch
111
- request.room_type.should be_a Failure
105
+ result.fetch.room_type.should be_a Failure
112
106
  end
113
107
 
114
108
  it 'reports an invalid check_in time' do
115
- params = params_proto.merge 'check_in' => '2012-11-31'
116
- result = Request.create(params, HotelBookingRequestParams)
109
+ result = prepare params_proto.merge 'check_in' => '2012-11-31'
117
110
  result.should be_a Failure
118
111
  request = result.fetch
119
- request.check_in.should be_a Failure
120
- request.check_in.should == Failure("check_in invalid date '2012-11-31'")
112
+ request.check_in.should == Failure("check_in invalid date, got '2012-11-31'")
121
113
  end
122
114
 
123
115
  it 'builds a failure request with no params' do
124
- result = Request.create(nil, HotelBookingRequestParams)
116
+ result = prepare(nil)
125
117
  result.should be_a Failure
126
118
  request = result.fetch
127
- request.hotel_code.should be_a Failure
128
- request.nights.should be_a Failure
129
- request.room_type.should be_a Failure
130
- request.check_in.should be_a Failure
119
+ request.hotel_code.should == Failure("hotel_code must be of pattern XXX0001, got ''")
120
+ request.nights.should == Failure("nights must be a number greater than 0, got ''")
121
+ 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 ''")
123
+ request.guests.should == Failure("guests number must match the room_type '':99, got 0: ''")
131
124
  end
132
125
 
133
126
  end
data/spec/maybe_spec.rb CHANGED
@@ -7,12 +7,17 @@ describe Monadic::Maybe do
7
7
 
8
8
  it 'Maybe cannot be created using #new, use #unit instead' do
9
9
  expect { Maybe.new(1) }.to raise_error NoMethodError
10
- end
10
+ end
11
11
 
12
12
  it 'nil as value always returns Nothing()' do
13
13
  Maybe(nil).a.b.c.should == Nothing
14
14
  end
15
15
 
16
+ it 'Maybe(Just) should return Just' do
17
+ Maybe(Just(nil)).should == Just(nil)
18
+ Maybe(Nothing).should == Nothing
19
+ end
20
+
16
21
  it 'on non-existant methods, returns Nothing' do
17
22
  Maybe({}).a.b.c.should == Nothing
18
23
  end
@@ -26,7 +31,7 @@ describe Monadic::Maybe do
26
31
  Maybe(nil).fetch.should == Nothing
27
32
  Maybe(nil)._.should == Nothing
28
33
  Maybe(nil).empty?.should be_true
29
- end
34
+ end
30
35
 
31
36
  it 'Nothing#to_s is "Nothing"' do
32
37
  option = Maybe(nil)
@@ -55,6 +60,10 @@ describe Monadic::Maybe do
55
60
 
56
61
  it 'Nothing#or returns the alternative' do
57
62
  Maybe(nil).or(1).should == Just(1)
63
+ Nothing.or(1).should == Just(1)
64
+ Nothing.or(Just(nil)).should == Just(nil)
65
+ Nothing.something.or(1).should == Just(1)
66
+ Nothing.something.or(Just(nil)).should == Just(nil)
58
67
  end
59
68
  end
60
69
 
@@ -64,18 +73,19 @@ describe Monadic::Maybe do
64
73
  end
65
74
 
66
75
  it 'Just stays Just' do
67
- Maybe('foo').should be_kind_of(Just)
76
+ Maybe('foo').should be_kind_of(Just)
68
77
  Maybe('foo').empty?.should be_false
69
78
  end
70
79
 
71
80
  it 'Just#to_s is "value"' do
72
81
  Just.unit(123).to_s.should == "Just(123)"
82
+ Just("4fe48bcfcf79520644088180").to_s.should == 'Just("4fe48bcfcf79520644088180")'
73
83
  end
74
84
 
75
85
  it 'Just#or return self' do
76
86
  Maybe(1).or(2).should == Just(1)
77
- Maybe(nil).or(nil).should == Just.new(nil)
78
- Maybe(nil).something.or('').fetch.should == ''
87
+ Maybe(nil).or(nil).should == Nothing
88
+ Maybe(nil).something.or(Just('')).fetch.should == Just('')
79
89
  end
80
90
 
81
91
  it 'Just#inspect returns Just(value)' do
@@ -163,7 +173,12 @@ describe Monadic::Maybe do
163
173
 
164
174
  format_date_in_march(nil).should == "not in march!"
165
175
  format_date_in_march(Time.parse('2009-01-01 01:02')).should == "not in march!"
166
- format_date_in_march(Time.parse('2011-03-21 12:34')).should == "20110321"
176
+ format_date_in_march(Time.parse('2011-03-21 12:34')).should == "20110321"
167
177
  end
168
178
 
179
+ it 'delegates method calls with #proxy' do
180
+ Maybe({a: 1}).proxy.fetch(:a).should == Maybe(1)
181
+ Maybe("FOO").proxy.downcase.capitalize.should == Maybe("Foo")
182
+ Maybe(nil).proxy.downcase.capitalize.bar.should == Nothing
183
+ end
169
184
  end
data/spec/try_spec.rb CHANGED
@@ -14,14 +14,25 @@ describe 'Monadic::Try' do
14
14
  Try { [] }.should be_a Failure
15
15
  end
16
16
 
17
- it 'without a block it works like Either' do
17
+ it 'with a param, without a block it returns the predicate' do
18
18
  Try(true).should be_a Success
19
19
  Try(false).should be_a Failure
20
20
  Try(nil).should be_a Failure
21
21
  end
22
22
 
23
- it 'with a proc' do
24
- pending
23
+ it 'with a param and with a block it returns the block result' do
24
+ Try(true) { 1 }.should == Success(1)
25
+ Try(false) { 2 }.should == Failure(2)
26
+ Try(false) { 1 }.else { 2 }.should == Failure(2)
27
+ end
28
+
29
+ it 'with a proc and no block it evaluates the proc' do
30
+ Try(-> { 1 }).should == Success(1)
31
+ Try(-> { 1 }) { 2 }.should == Success(2)
32
+ end
33
+
34
+ it 'no params is the same as passing nil as the predicatea' do
35
+ Try().should == Failure(nil)
25
36
  end
26
37
 
27
38
  it 'returns Success for non-nil values' do
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.5.0
4
+ version: 0.6.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-17 00:00:00.000000000 Z
12
+ date: 2012-06-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -154,6 +154,7 @@ files:
154
154
  - spec/core_ext/object_spec.rb
155
155
  - spec/either_spec.rb
156
156
  - spec/examples/applicative_spec.rb
157
+ - spec/examples/builder_spec.rb
157
158
  - spec/examples/hotel_booking_spec.rb
158
159
  - spec/jruby_fixes.rb
159
160
  - spec/maybe_spec.rb
@@ -176,7 +177,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
176
177
  version: '0'
177
178
  segments:
178
179
  - 0
179
- hash: 44748107420070269
180
+ hash: 2246458514498594220
180
181
  required_rubygems_version: !ruby/object:Gem::Requirement
181
182
  none: false
182
183
  requirements:
@@ -185,7 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
185
186
  version: '0'
186
187
  segments:
187
188
  - 0
188
- hash: 44748107420070269
189
+ hash: 2246458514498594220
189
190
  requirements: []
190
191
  rubyforge_project:
191
192
  rubygems_version: 1.8.24
@@ -196,6 +197,7 @@ test_files:
196
197
  - spec/core_ext/object_spec.rb
197
198
  - spec/either_spec.rb
198
199
  - spec/examples/applicative_spec.rb
200
+ - spec/examples/builder_spec.rb
199
201
  - spec/examples/hotel_booking_spec.rb
200
202
  - spec/jruby_fixes.rb
201
203
  - spec/maybe_spec.rb