just_maybe 0.1.0 → 0.1.1
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/README.md +41 -18
- data/lib/just_maybe/maybe.rb +26 -30
- data/lib/just_maybe/version.rb +1 -1
- data/spec/maybe_spec.rb +61 -9
- metadata +1 -1
data/README.md
CHANGED
@@ -1,17 +1,22 @@
|
|
1
1
|
# JustMaybe
|
2
2
|
|
3
|
-
A simple implementation of the "Maybe"
|
4
|
-
|
5
|
-
|
3
|
+
A simple implementation of the "Maybe" - "Special Case" pattern.
|
4
|
+
|
5
|
+
Maybe provides a generic null object that allows you to defer
|
6
|
+
the interpretation of what it means to be null until you need
|
7
|
+
to use the object.
|
8
|
+
|
9
|
+
Specifically they allow you to safely chain any number of method
|
10
|
+
calls on an object before returning it or a default value.
|
6
11
|
|
7
12
|
```ruby
|
8
|
-
|
13
|
+
Maybe(nil).foo.bar.or_else { 'default' }
|
9
14
|
```
|
10
15
|
## Installation
|
11
16
|
|
12
17
|
Add this line to your application's Gemfile:
|
13
18
|
|
14
|
-
gem
|
19
|
+
gem "just_maybe", "~> 0.1.0"
|
15
20
|
|
16
21
|
And then execute:
|
17
22
|
|
@@ -20,30 +25,48 @@ And then execute:
|
|
20
25
|
## Background
|
21
26
|
|
22
27
|
I created JustMaybe because I wanted a really simple solution that didn't
|
23
|
-
introduce any changes
|
28
|
+
introduce any changes on Object and I found this paticular syntax very
|
29
|
+
readable.
|
30
|
+
|
31
|
+
My paticular use case was for presenter objects that needed to aggregate
|
32
|
+
lots of information by walking method chains of a supplied objects. In
|
33
|
+
this sitation the Presenter is aware of the appropriate default value to
|
34
|
+
use if the reference fails.
|
24
35
|
|
25
|
-
|
26
|
-
|
27
|
-
use case the Presenter always knows what it wants, where it's at, and
|
28
|
-
what it should use as an alternative if it's not available.
|
36
|
+
Using Maybe allows me to get the benefit of null objects without having
|
37
|
+
to modify the underlying code.
|
29
38
|
|
30
39
|
## Examples
|
31
40
|
|
32
41
|
```ruby
|
33
|
-
|
34
|
-
|
42
|
+
require 'just_maybe'
|
43
|
+
# => true
|
35
44
|
|
36
|
-
#
|
37
|
-
Maybe(42)
|
45
|
+
# Calling Maybe(object) returns that object wrapped in a instance of Maybe
|
46
|
+
Maybe(42)
|
47
|
+
# => #<Maybe:0x000000010e7c88> @value=42
|
38
48
|
|
39
|
-
#
|
49
|
+
# An instance of maybe will pass all valid messages to the underlying value
|
50
|
+
# and wrap the result in a new instance Maybe
|
51
|
+
Maybe(42).next
|
52
|
+
# => <Maybe:0x0000000110b0e8> @value=43
|
53
|
+
|
54
|
+
# If the Maybe's underlying value doesn't respond to the message it returns
|
55
|
+
# nil wrapped in a new instance of Maybe
|
56
|
+
Maybe(42).foo
|
57
|
+
# => #<Maybe:0x00000001113540> @value=nil
|
58
|
+
|
59
|
+
# This allows you to make arbitrarily long method chains with no risk of raising
|
40
60
|
# NoMethodError on a nil object
|
41
61
|
Maybe(nil).foo.bar.baz # => Maybe(nil)
|
62
|
+
# => #<Maybe:0x00000001120830> @value=nil
|
42
63
|
|
43
64
|
# Finally Maybe responds to #or_else { ... }. This causes the Maybe to return the
|
44
65
|
# underlying value if it exists or the result of the block if it does not.
|
45
|
-
Maybe(
|
46
|
-
|
66
|
+
Maybe('FOO').downcase.or_else { 'unknown' }
|
67
|
+
# => "foo"
|
68
|
+
Maybe(nil).downcase.or_else { 'unknown' }
|
69
|
+
# => "unknown"
|
47
70
|
|
48
71
|
# There are a number of convenience methods that operate like or_else for common null values
|
49
72
|
Maybe(nil).foo.bar.or_a # => []
|
@@ -58,11 +81,11 @@ Maybe(nil).foo.bar.or_s # => ''
|
|
58
81
|
Maybe(42).__value__ # => 42
|
59
82
|
Maybe(42).foo.bar.baz.__value__ # => nil
|
60
83
|
```
|
61
|
-
|
62
84
|
## Related
|
63
85
|
* [Virtuous Code](http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/)
|
64
86
|
* [mondadic](https://github.com/pzol/monadic)
|
65
87
|
* [rumonade](https://github.com/ms-ati/rumonade)
|
88
|
+
* [thoughtbot](http://robots.thoughtbot.com/post/8181879506/if-you-gaze-into-nil-nil-gazes-also-into-you)
|
66
89
|
|
67
90
|
## Contributing
|
68
91
|
|
data/lib/just_maybe/maybe.rb
CHANGED
@@ -1,72 +1,68 @@
|
|
1
|
-
|
2
|
-
Maybe.new(object)
|
3
|
-
end
|
4
|
-
|
5
|
-
class Maybe
|
1
|
+
class Maybe < BasicObject
|
6
2
|
def initialize(object)
|
7
|
-
if object.kind_of?(Maybe)
|
8
|
-
@
|
3
|
+
if object.kind_of?(::Maybe)
|
4
|
+
@object = object.__value__
|
9
5
|
else
|
10
|
-
@
|
6
|
+
@object = object
|
11
7
|
end
|
12
8
|
end
|
13
9
|
|
10
|
+
def instance_of?(klass)
|
11
|
+
::Maybe == klass
|
12
|
+
end
|
13
|
+
|
14
14
|
def __value__
|
15
|
-
|
15
|
+
@object
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
def inspect
|
19
|
-
'
|
19
|
+
::Kernel.sprintf('#<Maybe:0x%0.14x> @object=%s', __id__ * 2, @object.inspect)
|
20
20
|
end
|
21
21
|
|
22
|
-
def method_missing(method, *args)
|
23
|
-
|
24
|
-
Maybe(@
|
25
|
-
|
26
|
-
Maybe(nil)
|
22
|
+
def method_missing(method, *args, &block)
|
23
|
+
begin
|
24
|
+
::Maybe.new(@object.__send__(method, *args, &block))
|
25
|
+
rescue ::NoMethodError
|
26
|
+
::Maybe.new(nil)
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
30
|
def or_a
|
31
|
-
return @
|
31
|
+
return @object unless @object.nil?
|
32
32
|
[]
|
33
33
|
end
|
34
34
|
|
35
35
|
def or_else(&block)
|
36
|
-
return @
|
36
|
+
return @object unless @object.nil?
|
37
37
|
yield
|
38
38
|
end
|
39
39
|
|
40
40
|
def or_f
|
41
|
-
return @
|
41
|
+
return @object unless @object.nil?
|
42
42
|
0.0
|
43
43
|
end
|
44
44
|
|
45
45
|
def or_h
|
46
|
-
return @
|
46
|
+
return @object unless @object.nil?
|
47
47
|
{}
|
48
48
|
end
|
49
49
|
|
50
50
|
def or_i
|
51
|
-
return @
|
51
|
+
return @object unless @object.nil?
|
52
52
|
0
|
53
53
|
end
|
54
54
|
|
55
55
|
def or_nil
|
56
|
-
return @
|
56
|
+
return @object unless @object.nil?
|
57
57
|
nil
|
58
58
|
end
|
59
59
|
|
60
60
|
def or_s
|
61
|
-
return @
|
61
|
+
return @object unless @object.nil?
|
62
62
|
''
|
63
63
|
end
|
64
|
+
end
|
64
65
|
|
65
|
-
|
66
|
-
|
67
|
-
@value == other.__value__
|
68
|
-
else
|
69
|
-
@value == other
|
70
|
-
end
|
71
|
-
end
|
66
|
+
def Maybe(object)
|
67
|
+
Maybe.new(object)
|
72
68
|
end
|
data/lib/just_maybe/version.rb
CHANGED
data/spec/maybe_spec.rb
CHANGED
@@ -3,24 +3,76 @@ require 'just_maybe/maybe'
|
|
3
3
|
describe Maybe do
|
4
4
|
let(:object) { double }
|
5
5
|
|
6
|
-
it "
|
7
|
-
|
6
|
+
it "delegates messages to wrapped object" do
|
7
|
+
object.should_receive(:foo).with('bar')
|
8
|
+
Maybe(object).foo('bar')
|
8
9
|
end
|
9
10
|
|
10
|
-
it "
|
11
|
-
|
12
|
-
Maybe(object).some_method.__value__.should == 'this object'
|
11
|
+
it "calling methods that don't exist return Maybe(nil)" do
|
12
|
+
Maybe(nil).foo.__value__.should be_nil
|
13
13
|
end
|
14
14
|
|
15
|
-
it "
|
16
|
-
Maybe(
|
15
|
+
it "it doesn't nest" do
|
16
|
+
Maybe(Maybe(nil)).__value__.should_not be_instance_of(Maybe)
|
17
17
|
end
|
18
18
|
|
19
|
-
it "
|
19
|
+
it "__value__ unwraps the underlying object" do
|
20
|
+
Maybe(object).__value__.should be(object)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "#or_else returns then underlying object when it's not nil" do
|
20
24
|
Maybe(object).or_else{'unexpected result'}.should == object
|
21
25
|
end
|
22
26
|
|
23
|
-
it "#or_else
|
27
|
+
it "#or_else yields to supplied block when the underlying object is nil" do
|
24
28
|
Maybe(nil).or_else{'expected result'}.should == 'expected result'
|
25
29
|
end
|
30
|
+
|
31
|
+
it "#or_a returns then underlying object when it's not nil" do
|
32
|
+
Maybe(object).or_a.should be(object)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "#or_a defaults to []" do
|
36
|
+
Maybe(nil).or_a.should == []
|
37
|
+
end
|
38
|
+
|
39
|
+
it "#or_f returns then underlying object when it's not nil" do
|
40
|
+
Maybe(object).or_f.should be(object)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "#or_f defaults to 0.0" do
|
44
|
+
Maybe(nil).or_f.should == 0.0
|
45
|
+
end
|
46
|
+
|
47
|
+
it "#or_h returns then underlying object when it's not nil" do
|
48
|
+
Maybe(object).or_h.should be(object)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "#or_h defaults to {}" do
|
52
|
+
Maybe(nil).or_h.should == {}
|
53
|
+
end
|
54
|
+
|
55
|
+
it "#or_i returns then underlying object when it's not nil" do
|
56
|
+
Maybe(object).or_i.should be(object)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "#or_i defaults to 0" do
|
60
|
+
Maybe(nil).or_i.should == 0
|
61
|
+
end
|
62
|
+
|
63
|
+
it "#or_nil returns then underlying object when it's not nil" do
|
64
|
+
Maybe(object).or_nil.should be(object)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "#or_nil defaults to nil" do
|
68
|
+
Maybe(nil).or_nil.should == nil
|
69
|
+
end
|
70
|
+
|
71
|
+
it "#or_s returns then underlying object when it's not nil" do
|
72
|
+
Maybe(object).or_s.should be(object)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "#or_s defaults to ''" do
|
76
|
+
Maybe(nil).or_s.should == ''
|
77
|
+
end
|
26
78
|
end
|