optional 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +30 -0
- data/LICENSE.md +22 -0
- data/README.md +60 -0
- data/lib/optional/all.rb +4 -0
- data/lib/optional/none.rb +35 -0
- data/lib/optional/option/all.rb +3 -0
- data/lib/optional/option/enumerable.rb +38 -0
- data/lib/optional/option/errors.rb +6 -0
- data/lib/optional/option/match.rb +84 -0
- data/lib/optional/option.rb +24 -0
- data/lib/optional/some.rb +46 -0
- data/lib/optional.rb +1 -0
- data/optional.gemspec +17 -0
- data/spec/lib/optional/none_spec.rb +43 -0
- data/spec/lib/optional/option/enumerable_spec.rb +126 -0
- data/spec/lib/optional/option_spec.rb +42 -0
- data/spec/lib/optional/some_spec.rb +47 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/support/cat.rb +21 -0
- metadata +107 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
optional (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.2.1)
|
10
|
+
multi_json (1.7.2)
|
11
|
+
rspec (2.13.0)
|
12
|
+
rspec-core (~> 2.13.0)
|
13
|
+
rspec-expectations (~> 2.13.0)
|
14
|
+
rspec-mocks (~> 2.13.0)
|
15
|
+
rspec-core (2.13.1)
|
16
|
+
rspec-expectations (2.13.0)
|
17
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
18
|
+
rspec-mocks (2.13.0)
|
19
|
+
simplecov (0.7.1)
|
20
|
+
multi_json (~> 1.0)
|
21
|
+
simplecov-html (~> 0.7.1)
|
22
|
+
simplecov-html (0.7.1)
|
23
|
+
|
24
|
+
PLATFORMS
|
25
|
+
ruby
|
26
|
+
|
27
|
+
DEPENDENCIES
|
28
|
+
optional!
|
29
|
+
rspec
|
30
|
+
simplecov
|
data/LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 On The Beach Ltd
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# id
|
2
|
+
### simple models based on hashes
|
3
|
+
|
4
|
+
JSON is a great way to transfer data between systems, and it's easy to parse into a Ruby hash. But sometimes it's nice to have actual methods to call when you want to get attributes from your data, rather than coupling your entire codebase to the hash representation by littering it with calls to `fetch` or `[]`. The same goes for BSON documents stored in Mongo.
|
5
|
+
|
6
|
+
That's where `id` (as in Freud) comes in. You define your model classes using syntax that should look pretty familiar if you've used any popular Ruby ORMs - but `id` is not an ORM. Model objects defined with `id` have a constructor that accepts a hash, and you define the values of this hash that are made readable as fields - but that hash can come from any source.
|
7
|
+
|
8
|
+
#### Defining a model
|
9
|
+
|
10
|
+
Defining a model looks like this:
|
11
|
+
|
12
|
+
class MyModel
|
13
|
+
include Id::Model
|
14
|
+
|
15
|
+
field :foo
|
16
|
+
field :bar, default: 42
|
17
|
+
field :baz, key: 'barry'
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
my_model = MyModel.new(foo: 7, barry: 'hello')
|
22
|
+
my_model.foo # => 7
|
23
|
+
my_model.bar # => 42
|
24
|
+
my_model.baz # => 'hello'
|
25
|
+
|
26
|
+
As you can see, you can specify default values as well as key aliases.
|
27
|
+
|
28
|
+
#### Associations
|
29
|
+
|
30
|
+
You can also specify has_one or has_many "associations" - what would be nested subdocuments in MongoDB for example - like this:
|
31
|
+
|
32
|
+
class Zoo
|
33
|
+
include Id::Model
|
34
|
+
|
35
|
+
has_many :lions
|
36
|
+
has_many :zebras
|
37
|
+
has_one :zookeeper, type: Person
|
38
|
+
end
|
39
|
+
|
40
|
+
zoo = Zoo.new(lions: [{name: 'Hetty'}],
|
41
|
+
zebras: [{name: 'Lisa'}],
|
42
|
+
zookeeper: {name: 'Russell' d})
|
43
|
+
|
44
|
+
zoo.lions.first.class # => Lion
|
45
|
+
zoo.lions.first.name # => "Hetty"
|
46
|
+
zoo.zookeeper.class # => Person
|
47
|
+
zoo.zookeeper.name # => "Russell"
|
48
|
+
|
49
|
+
Types are inferred from the association name unless one is specified.
|
50
|
+
|
51
|
+
#### Designed for immutability
|
52
|
+
|
53
|
+
`id` models provide accessor methods, but no mutator methods, because they are designed for immutability. How do immutable models work? When you need to change some field of a model object, a new copy of the object is created with the field changed as required. This is handled for you by `id`'s `set` method:
|
54
|
+
|
55
|
+
person = Person.new(name: 'Russell', job: 'programmer')
|
56
|
+
person.set(name: 'Radek') # => returns a new Person whose name is Radek and whose job is 'programmer'
|
57
|
+
|
58
|
+
You can even set fields on nested models in this way:
|
59
|
+
|
60
|
+
person.hat.set(color: 'red') # => returns a new person object with a new hat object with its color set to red
|
data/lib/optional/all.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module None
|
2
|
+
include Option
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def each
|
6
|
+
end
|
7
|
+
|
8
|
+
def none?
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
def some?(type=nil)
|
13
|
+
false
|
14
|
+
end
|
15
|
+
|
16
|
+
def value
|
17
|
+
raise Option::ValueOfNoneError
|
18
|
+
end
|
19
|
+
|
20
|
+
def value_or
|
21
|
+
yield
|
22
|
+
end
|
23
|
+
|
24
|
+
def & other
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def | other
|
29
|
+
other
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
"None"
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Option
|
2
|
+
module Enumerable
|
3
|
+
include ::Enumerable
|
4
|
+
|
5
|
+
def map
|
6
|
+
from_array super
|
7
|
+
end
|
8
|
+
alias_method :collect, :map
|
9
|
+
alias_method :flat_map, :map
|
10
|
+
alias_method :collect_concat, :map
|
11
|
+
|
12
|
+
def detect
|
13
|
+
from_value super
|
14
|
+
end
|
15
|
+
alias_method :find, :detect
|
16
|
+
|
17
|
+
def select
|
18
|
+
from_array super
|
19
|
+
end
|
20
|
+
alias_method :find_all, :select
|
21
|
+
|
22
|
+
def grep(value)
|
23
|
+
from_array super
|
24
|
+
end
|
25
|
+
|
26
|
+
def reject
|
27
|
+
from_array super
|
28
|
+
end
|
29
|
+
|
30
|
+
def reduce(*args, &block)
|
31
|
+
if none? && (args.size < 1 || args.size < 2 && block.nil?)
|
32
|
+
raise ValueOfNoneError
|
33
|
+
end
|
34
|
+
super
|
35
|
+
end
|
36
|
+
alias_method :inject, :reduce
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Option
|
2
|
+
class Match
|
3
|
+
|
4
|
+
def some(guard=always, &block)
|
5
|
+
some_clauses << SomeClause.new(guard, block)
|
6
|
+
end
|
7
|
+
|
8
|
+
def none(&block)
|
9
|
+
self.none_clause = NoneClause.new(block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def evaluate(option)
|
13
|
+
case option
|
14
|
+
when Some
|
15
|
+
matched(option).evaluate(option.value)
|
16
|
+
when None
|
17
|
+
none_clause.evaluate
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def some_clauses
|
24
|
+
@some_clauses ||= []
|
25
|
+
end
|
26
|
+
|
27
|
+
def none_clause
|
28
|
+
@none_clause ||= NoneClause.new(lambda {})
|
29
|
+
end
|
30
|
+
|
31
|
+
def matched(option)
|
32
|
+
some_clauses.find { |clause| clause.matches? option.value }.tap do |match|
|
33
|
+
raise Option::BadMatchError if match.nil?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def always
|
38
|
+
lambda { |x| true }
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_writer :none_clause
|
42
|
+
|
43
|
+
class SomeClause
|
44
|
+
|
45
|
+
def initialize(guard=always, block)
|
46
|
+
@guard = guard
|
47
|
+
@block = block
|
48
|
+
end
|
49
|
+
|
50
|
+
def matches?(value)
|
51
|
+
guard.call(value)
|
52
|
+
end
|
53
|
+
|
54
|
+
def evaluate(value)
|
55
|
+
block.call(value)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
attr_reader :block
|
61
|
+
|
62
|
+
def guard
|
63
|
+
@guard.is_a?(Proc) ? @guard : ->(x) { x == @guard }
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
class NoneClause
|
69
|
+
|
70
|
+
def initialize(block)
|
71
|
+
@block = block
|
72
|
+
end
|
73
|
+
|
74
|
+
def evaluate
|
75
|
+
block.call
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
attr_reader :block
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Option
|
2
|
+
include Option::Enumerable
|
3
|
+
|
4
|
+
def match(&block)
|
5
|
+
Match.new.tap { |m| block.call(m) }.evaluate(self)
|
6
|
+
end
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def and_option(option)
|
11
|
+
none? ? self : Some[*[option.value, value].flatten]
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def from_array(values)
|
17
|
+
values.empty? ? None : Some[values.first]
|
18
|
+
end
|
19
|
+
|
20
|
+
def from_value(value)
|
21
|
+
value.nil? ? None : Some[value]
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Some
|
2
|
+
include Option
|
3
|
+
|
4
|
+
attr_reader :value
|
5
|
+
|
6
|
+
def initialize(value)
|
7
|
+
@value = value
|
8
|
+
end
|
9
|
+
|
10
|
+
def each
|
11
|
+
yield value
|
12
|
+
end
|
13
|
+
|
14
|
+
def none?(&block)
|
15
|
+
block.nil? ? false : super
|
16
|
+
end
|
17
|
+
|
18
|
+
def value_or
|
19
|
+
value
|
20
|
+
end
|
21
|
+
|
22
|
+
def some?(type=value.class)
|
23
|
+
value.class == type
|
24
|
+
end
|
25
|
+
|
26
|
+
def & other
|
27
|
+
other.and_option(self)
|
28
|
+
end
|
29
|
+
|
30
|
+
def == other
|
31
|
+
other.some? && value == other.value
|
32
|
+
end
|
33
|
+
|
34
|
+
def | other
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
"Some[#{value}]"
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.[](*values)
|
43
|
+
new(values.size == 1 ? values.first : values)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
data/lib/optional.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'optional/all'
|
data/optional.gemspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'optional'
|
3
|
+
s.version = '0.0.1'
|
4
|
+
s.date = '2013-04-19'
|
5
|
+
s.summary = "Optional values with pattern matching"
|
6
|
+
s.description = "Make nils go bye bye with Options!"
|
7
|
+
s.authors = ["Russell Dunphy", "Radek Molenda"]
|
8
|
+
s.email = ['russell@russelldunphy.com', 'radek.molenda@gmail.com']
|
9
|
+
s.files = `git ls-files`.split($\)
|
10
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
11
|
+
s.require_paths = ["lib"]
|
12
|
+
s.homepage = 'http://github.com/onthebeach/optional'
|
13
|
+
|
14
|
+
s.add_development_dependency "rspec"
|
15
|
+
s.add_development_dependency "simplecov"
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe None do
|
4
|
+
|
5
|
+
let (:cat) { Cat.new("MOGGIE!") }
|
6
|
+
|
7
|
+
subject { None }
|
8
|
+
|
9
|
+
it { should be_none }
|
10
|
+
it { should_not be_some }
|
11
|
+
it { should_not be_some Cat }
|
12
|
+
|
13
|
+
it { should eq None }
|
14
|
+
it { should_not eq Some[cat] }
|
15
|
+
|
16
|
+
it "does not have a value" do
|
17
|
+
expect { subject.value }.to raise_error Option::ValueOfNoneError
|
18
|
+
end
|
19
|
+
|
20
|
+
it "does, however, allow you to supply a default in place of a value" do
|
21
|
+
subject.value_or { cat }.should eq cat
|
22
|
+
end
|
23
|
+
|
24
|
+
it "can be anded with another none, yielding none" do
|
25
|
+
(None & None).should be_none
|
26
|
+
end
|
27
|
+
|
28
|
+
it "can be anded with a some, yielding none" do
|
29
|
+
(None & Some[cat]).should be_none
|
30
|
+
end
|
31
|
+
|
32
|
+
it "can be ored with another none, yielding none" do
|
33
|
+
(None | None).should be_none
|
34
|
+
end
|
35
|
+
|
36
|
+
it "can be ored with a some, yielding the some" do
|
37
|
+
(None | Some[cat]).should eq Some[cat]
|
38
|
+
end
|
39
|
+
|
40
|
+
it "prints as None" do
|
41
|
+
None.to_s.should eq "None"
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Option::Enumerable do
|
4
|
+
|
5
|
+
let (:cat) { Cat.new("MOGGIE!") }
|
6
|
+
|
7
|
+
describe "#map" do
|
8
|
+
|
9
|
+
it "maps a some to a some" do
|
10
|
+
Some[cat].map(&:name).should eq Some["MOGGIE!"]
|
11
|
+
end
|
12
|
+
|
13
|
+
it "also works for collect" do
|
14
|
+
Some[cat].collect(&:name).should eq Some["MOGGIE!"]
|
15
|
+
end
|
16
|
+
|
17
|
+
it "also works for flat_map" do
|
18
|
+
Some[cat].flat_map(&:name).should eq Some["MOGGIE!"]
|
19
|
+
end
|
20
|
+
|
21
|
+
it "maps none to none" do
|
22
|
+
None.map(&:name).should be_none
|
23
|
+
end
|
24
|
+
|
25
|
+
it "also works for collect" do
|
26
|
+
None.collect(&:name).should be_none
|
27
|
+
end
|
28
|
+
|
29
|
+
it "also works for flat_map" do
|
30
|
+
None.flat_map(&:name).should be_none
|
31
|
+
end
|
32
|
+
|
33
|
+
it "also works for collect_concat" do
|
34
|
+
None.collect_concat(&:name).should be_none
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#detect" do
|
39
|
+
it "returns none if called on a none" do
|
40
|
+
None.detect{ |pet| pet.name == "MOGGIE!" }.should be_none
|
41
|
+
end
|
42
|
+
|
43
|
+
it "returns a some if called on a some that matches" do
|
44
|
+
Some[cat].detect{ |pet| pet.name == "MOGGIE!" }.should eq Some[cat]
|
45
|
+
end
|
46
|
+
|
47
|
+
it "returns a none if called on a some that doesn't match" do
|
48
|
+
Some[cat].detect{ |pet| pet.name == "DOGGIE!" }.should be_none
|
49
|
+
end
|
50
|
+
|
51
|
+
it "also works for find" do
|
52
|
+
None.find{ |pet| pet.name == "MOGGIE!" }.should be_none
|
53
|
+
Some[cat].find{ |pet| pet.name == "MOGGIE!" }.should eq Some[cat]
|
54
|
+
Some[cat].find{ |pet| pet.name == "DOGGIE!" }.should be_none
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "#select" do
|
60
|
+
|
61
|
+
it "returns a none if called on a none" do
|
62
|
+
None.select(&:even?).should be_none
|
63
|
+
end
|
64
|
+
|
65
|
+
it "returns a none if called on a some that doesn't match" do
|
66
|
+
Some[3].select(&:even?).should be_none
|
67
|
+
end
|
68
|
+
|
69
|
+
it "returns a some if called on a some that matches" do
|
70
|
+
Some[3].select(&:odd?).should eq Some[3]
|
71
|
+
end
|
72
|
+
|
73
|
+
it "also works for find_all" do
|
74
|
+
None.find_all(&:even?).should be_none
|
75
|
+
Some[3].find_all(&:even?).should be_none
|
76
|
+
Some[3].find_all(&:odd?).should eq Some[3]
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "#grep" do
|
82
|
+
it "returns a none if no matching elements" do
|
83
|
+
None.grep(/ing/).should be_none
|
84
|
+
end
|
85
|
+
|
86
|
+
it "returns a some otherwise" do
|
87
|
+
Some["finger"].grep(/ing/).should eq Some["finger"]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "#reduce" do
|
92
|
+
it "raises an error if it is called on None" do
|
93
|
+
expect { None.reduce(:+) }.to raise_error Option::ValueOfNoneError
|
94
|
+
expect { None.reduce{ |acc, x| acc + x } }.to raise_error Option::ValueOfNoneError
|
95
|
+
end
|
96
|
+
|
97
|
+
it "works with a none as expected otherwise" do
|
98
|
+
None.reduce(5, :+).should eq 5
|
99
|
+
None.reduce(5) { |acc, x| acc + x }.should eq 5
|
100
|
+
end
|
101
|
+
|
102
|
+
it "works with a some as expected" do
|
103
|
+
Some[4].reduce(:+).should eq 4
|
104
|
+
Some[4].reduce(5, :+).should eq 9
|
105
|
+
Some[4].reduce(5) { |acc, x| acc + x }.should eq 9
|
106
|
+
end
|
107
|
+
|
108
|
+
it "also works as inject" do
|
109
|
+
Some[4].inject(:+).should eq 4
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "#none?" do
|
114
|
+
it "returns true if given a block the block evaluates to false for the value" do
|
115
|
+
Some[6].none?(&:odd?).should be_true
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "#reject" do
|
120
|
+
it "returns either some or none" do
|
121
|
+
None.reject(&:odd?).should be_none
|
122
|
+
Some[3].reject(&:odd?).should be_none
|
123
|
+
Some[4].reject(&:odd?).should eq Some[4]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Option do
|
4
|
+
|
5
|
+
let (:cat) { Cat.new("MOGGIE!") }
|
6
|
+
let (:dog) { Dog.new("DOGGIE!") }
|
7
|
+
|
8
|
+
context "== pattern matching ==" do
|
9
|
+
|
10
|
+
subject do
|
11
|
+
option.match do |m|
|
12
|
+
m.some { |cat| cat.name }
|
13
|
+
m.none { "no cat :-(" }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "a match can have one None branch, which matches only None" do
|
18
|
+
let (:option) { None }
|
19
|
+
it { should eq "no cat :-(" }
|
20
|
+
end
|
21
|
+
|
22
|
+
context "a match can have a catchall Some branch which matches any Some" do
|
23
|
+
let (:option) { Some[cat] }
|
24
|
+
it { should eq "MOGGIE!" }
|
25
|
+
end
|
26
|
+
|
27
|
+
it "can have multiple Some branches, each one matching a value" do
|
28
|
+
Some[cat].match do |m|
|
29
|
+
m.some(dog) { :canteloupe }
|
30
|
+
m.some(cat) { :fishsticks }
|
31
|
+
end.should eq :fishsticks
|
32
|
+
end
|
33
|
+
|
34
|
+
it "can take a lambda as a guard to match against" do
|
35
|
+
Some[cat].match do |m|
|
36
|
+
m.some ->(pet) { pet.name == "DOGGIE!" } { :fishsticks }
|
37
|
+
m.some ->(pet) { pet.name == "MOGGIE!" } { :canteloupe }
|
38
|
+
end.should eq :canteloupe
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Some do
|
4
|
+
|
5
|
+
let (:cat) { Cat.new("MOGGIE!") }
|
6
|
+
let (:dog) { Dog.new("DOGGIE!") }
|
7
|
+
|
8
|
+
subject { Some[cat] }
|
9
|
+
|
10
|
+
it { should be_some }
|
11
|
+
it { should be_some Cat }
|
12
|
+
it { should_not be_some Dog }
|
13
|
+
|
14
|
+
it { should_not be_none }
|
15
|
+
|
16
|
+
it { should eq Some[cat] }
|
17
|
+
it { should_not eq Some[dog] }
|
18
|
+
it { should_not eq None }
|
19
|
+
|
20
|
+
it "has a value" do
|
21
|
+
Some[cat].value.should eq cat
|
22
|
+
end
|
23
|
+
|
24
|
+
it "can be passed a default value but won't use it" do
|
25
|
+
Some[cat].value_or { dog }.should eq cat
|
26
|
+
end
|
27
|
+
|
28
|
+
it "can be anded with another some" do
|
29
|
+
(Some[cat] & Some[dog]).should eq Some[cat, dog]
|
30
|
+
end
|
31
|
+
|
32
|
+
it "can be anded with a none, resulting in none" do
|
33
|
+
(Some[cat] & None).should eq None
|
34
|
+
end
|
35
|
+
|
36
|
+
it "can be ored with another some" do
|
37
|
+
(Some[cat] | Some[dog]).should eq Some[cat]
|
38
|
+
end
|
39
|
+
|
40
|
+
it "can be ored with a none resulting in itself" do
|
41
|
+
(Some[cat] | None).should eq Some[cat]
|
42
|
+
end
|
43
|
+
|
44
|
+
it "prints as Some[value]" do
|
45
|
+
Some[4].to_s.should eq "Some[4]"
|
46
|
+
end
|
47
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start do
|
3
|
+
add_filter '/spec'
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'rspec'
|
7
|
+
require_relative 'support/cat'
|
8
|
+
require_relative '../lib/optional'
|
9
|
+
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
config.order = :rand
|
13
|
+
config.color_enabled = true
|
14
|
+
end
|
data/spec/support/cat.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
class Cat
|
2
|
+
attr_reader :name
|
3
|
+
def initialize(name)
|
4
|
+
@name = name
|
5
|
+
end
|
6
|
+
|
7
|
+
def == other
|
8
|
+
other.is_a?(Cat) && other.name == name
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Dog
|
13
|
+
attr_reader :name
|
14
|
+
def initialize(name)
|
15
|
+
@name = name
|
16
|
+
end
|
17
|
+
|
18
|
+
def == other
|
19
|
+
other.is_a?(Dog) && other.name == name
|
20
|
+
end
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: optional
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Russell Dunphy
|
9
|
+
- Radek Molenda
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2013-04-19 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '0'
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: simplecov
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
type: :development
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
description: Make nils go bye bye with Options!
|
48
|
+
email:
|
49
|
+
- russell@russelldunphy.com
|
50
|
+
- radek.molenda@gmail.com
|
51
|
+
executables: []
|
52
|
+
extensions: []
|
53
|
+
extra_rdoc_files: []
|
54
|
+
files:
|
55
|
+
- .gitignore
|
56
|
+
- Gemfile
|
57
|
+
- Gemfile.lock
|
58
|
+
- LICENSE.md
|
59
|
+
- README.md
|
60
|
+
- lib/optional.rb
|
61
|
+
- lib/optional/all.rb
|
62
|
+
- lib/optional/none.rb
|
63
|
+
- lib/optional/option.rb
|
64
|
+
- lib/optional/option/all.rb
|
65
|
+
- lib/optional/option/enumerable.rb
|
66
|
+
- lib/optional/option/errors.rb
|
67
|
+
- lib/optional/option/match.rb
|
68
|
+
- lib/optional/some.rb
|
69
|
+
- optional.gemspec
|
70
|
+
- spec/lib/optional/none_spec.rb
|
71
|
+
- spec/lib/optional/option/enumerable_spec.rb
|
72
|
+
- spec/lib/optional/option_spec.rb
|
73
|
+
- spec/lib/optional/some_spec.rb
|
74
|
+
- spec/spec_helper.rb
|
75
|
+
- spec/support/cat.rb
|
76
|
+
homepage: http://github.com/onthebeach/optional
|
77
|
+
licenses: []
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 1.8.25
|
97
|
+
signing_key:
|
98
|
+
specification_version: 3
|
99
|
+
summary: Optional values with pattern matching
|
100
|
+
test_files:
|
101
|
+
- spec/lib/optional/none_spec.rb
|
102
|
+
- spec/lib/optional/option/enumerable_spec.rb
|
103
|
+
- spec/lib/optional/option_spec.rb
|
104
|
+
- spec/lib/optional/some_spec.rb
|
105
|
+
- spec/spec_helper.rb
|
106
|
+
- spec/support/cat.rb
|
107
|
+
has_rdoc:
|