unexceptional 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/unexceptional.rb +201 -0
  3. metadata +102 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 843955cd99c06a932f0594cc96ffa64488cd596a
4
+ data.tar.gz: 38523fd3cd5282fa9bcd44ad8dd6048ed4f94089
5
+ SHA512:
6
+ metadata.gz: 4188134219b235eba0f0dc541adc67b360ac723f31d8be8e945202cb1afa9d4f088f3e2a760272da71be0b624fae767a7704995d580dc578441feb6865e085b1
7
+ data.tar.gz: fd32c477e1a02bb7317fb51e87d1b9cc00cf956ab4cc3479bac8a93689968d07bf7ab75c351d65033e86a4534ab37b1eea97973276eff4d773eed19b69ac8c8c
@@ -0,0 +1,201 @@
1
+ module Unexceptional
2
+ class Result
3
+ # Pass true or false and an error value. If the first argument is true, returns an ok
4
+ # Result. If the first argument is false, returns an err Result wrapping the error
5
+ # value.
6
+ def self.check(condition, error)
7
+ condition ? ok : err(error)
8
+ end
9
+
10
+ # Returns a new Result respresenting failure. Optionally pass a wrapped result value.
11
+ def self.err(err)
12
+ new false, nil, err
13
+ end
14
+
15
+ # Pass a block and a collection. The block must accept a member of the collection and
16
+ # return a Result.
17
+ #
18
+ # If all members succeed, returns a Result wrapping all the mapped members:
19
+ #
20
+ # Result.map([1, 2]) do |i|
21
+ # Result.ok i * 2
22
+ # end
23
+ # # => Result.ok([1, 2])
24
+ #
25
+ # Aborts on the first failure:
26
+ #
27
+ # Result.map([1, 2, 3]) do |i|
28
+ # if i == 2
29
+ # Result.err '2 is invalid'
30
+ # elsif i == 3
31
+ # raise 'This is never executed because Result.map aborts on the previous element.'
32
+ # else
33
+ # Result.ok i * 2
34
+ # end
35
+ # end
36
+ # # => Result.err('2 is invalid')
37
+ def self.map_while(collection)
38
+ Result.ok(
39
+ collection.map do |member|
40
+ result = yield member
41
+ if result.err?
42
+ return result
43
+ else
44
+ result.unwrap
45
+ end
46
+ end
47
+ )
48
+ end
49
+
50
+ # Returns a new Result respresenting success. Optionally pass a wrapped result value.
51
+ def self.ok(val = nil)
52
+ new true, val, nil
53
+ end
54
+
55
+ # Given a block, runs an ActiveRecord transaction. The block must return a Result. If
56
+ # the Result is an error, rolls back the transaction. Either way, returns the Result.
57
+ # You must call `require 'active_record` before you call this method.
58
+ def self.transaction
59
+ unless defined?(ActiveRecord)
60
+ raise 'ActiveRecord is not defined'
61
+ end
62
+ result = nil
63
+ ActiveRecord::Base.transaction do
64
+ result = yield
65
+ if result.err?
66
+ raise ActiveRecord::Rollback
67
+ end
68
+ end
69
+ result
70
+ end
71
+
72
+ # Tries to run a list of procs, aborting on the first failure, if any. Each proc must
73
+ # return a Result--either ok or err. Aborts on the first err, if any, returning the
74
+ # failed Result. If all procs return ok, returns the last Result.
75
+ #
76
+ # Result.try(
77
+ # -> { Result.ok 2 },
78
+ # ->(i) { Result.ok 3 * i }
79
+ # )
80
+ # # => Result.ok(6)
81
+ #
82
+ # Result.try(
83
+ # -> { Result.ok 2 },
84
+ # ->(_) { Result.err :uh_oh },
85
+ # ->(i) { Result.ok 3 * i }
86
+ # )
87
+ # # => Result.err(:uh_oh)
88
+ #
89
+ # You can also pass tuples through and pattern-match:
90
+ #
91
+ # Result.try(
92
+ # -> { Result.ok [1, 2] },
93
+ # ->((a, b)) { Result.ok a + b }
94
+ # )
95
+ # # => Result.ok(3)
96
+ def self.try(*procs)
97
+ if procs.empty?
98
+ raise 'Must past at least one proc to Result.try'
99
+ end
100
+ procs.inject(nil) do |last_result, proc|
101
+ if last_result.nil?
102
+ proc.call
103
+ elsif last_result.ok?
104
+ if proc.parameters.length == 0
105
+ proc.call
106
+ else
107
+ proc.call last_result.unwrap
108
+ end
109
+ else
110
+ last_result
111
+ end
112
+ end
113
+ end
114
+
115
+ # If this Result is an err, returns self:
116
+ #
117
+ # Result
118
+ # .err(:uh_oh)
119
+ # .and_then { 'This block never executes' }
120
+ # # => Result.err(:uh_oh)
121
+ #
122
+ # If this Result is ok, then the behavior depends on what you passed to `and_then`:
123
+ #
124
+ # # Passing a single argument:
125
+ # Result
126
+ # .ok('This value gets dropped')
127
+ # .and_then(Result.ok('This is the final value'))
128
+ # # => Result.ok('This is the final value')
129
+ #
130
+ # # Passing a block:
131
+ # Result
132
+ # .ok(3)
133
+ # .and_then { |v| v * 2 }
134
+ # # => Result.ok(6)
135
+ #
136
+ # # Passing nothing:
137
+ # Result
138
+ # .ok('This value gets dropped')
139
+ # .and_then
140
+ # # => Result.ok
141
+ def and_then(next_result = nil)
142
+ if @ok
143
+ if block_given?
144
+ yield
145
+ elsif next_result
146
+ next_result
147
+ else
148
+ Result.ok
149
+ end
150
+ else
151
+ self
152
+ end
153
+ end
154
+
155
+ # Yields this Result if this Result is an err.
156
+ def if_err
157
+ yield self.err if !@ok
158
+ self
159
+ end
160
+
161
+ # Yields this Result if this Result is ok.
162
+ def if_ok
163
+ yield self.val if @ok
164
+ self
165
+ end
166
+
167
+ # Returns the wrapped err value. Raises if this Result is ok.
168
+ def err
169
+ if !@ok
170
+ @err
171
+ else
172
+ raise "Called #err, but Result was ok."
173
+ end
174
+ end
175
+
176
+ # Returns true if this Result is an err, false if this Result is ok.
177
+ def err?
178
+ !@ok
179
+ end
180
+
181
+ def initialize(ok, val, err) # :nodoc:
182
+ @ok = ok
183
+ @val = val
184
+ @err = err
185
+ end
186
+
187
+ # Returns true if this Result is ok, false if this Result is an err.
188
+ def ok?
189
+ @ok
190
+ end
191
+
192
+ # Returns the wrapped success value. Raises if this Result is an err.
193
+ def unwrap
194
+ if @ok
195
+ @val
196
+ else
197
+ raise "Called #unwrap on error: #{@err.inspect}"
198
+ end
199
+ end
200
+ end
201
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: unexceptional
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jarrett Colby
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-03-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest-reporters
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activerecord
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sqlite3
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1'
69
+ description: Provides a Result class for more elegant, exception-free error handling.
70
+ Especially useful for processing input that could be invalid for many different
71
+ reasons.
72
+ email: jarrett@madebyhq.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - lib/unexceptional.rb
78
+ homepage: https://github.com/jarrett/unexceptional
79
+ licenses:
80
+ - MIT
81
+ metadata: {}
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubyforge_project:
98
+ rubygems_version: 2.2.2
99
+ signing_key:
100
+ specification_version: 4
101
+ summary: Unexceptional
102
+ test_files: []