tron 1.0.0 → 1.0.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.
- checksums.yaml +4 -4
- data/README.md +67 -45
- data/lib/tron.rb +4 -4
- data/lib/tron/version.rb +1 -1
- metadata +1 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eea7d3927403faf13121f530c2a64d3ae05fa55b
|
4
|
+
data.tar.gz: 6d70cb35c23eeb4b8b49e9cd031f6fad1fdb77b4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a748c9ab621daf636f70d19f56ccc69f1b5f16881fd62a89e5389541175e6029db2aaec2125c2a61e858b51c4e77872a2e4e08d582a2bdce83b127a725aa0966
|
7
|
+
data.tar.gz: 2d2360d3a7ed669c84c01a45ebfc5826d30999ae8f083fd2f81b9ec9b033c3117cb6bc4a1f67dc14e205b6488a4e4bd5081e9463681cdeda067594d09b0675c3
|
data/README.md
CHANGED
@@ -2,7 +2,19 @@
|
|
2
2
|
[](https://travis-ci.org/halo/tron)
|
3
3
|
[](http://github.com/halo/tron/blob/master/LICENSE.md)
|
4
4
|
|
5
|
-
|
5
|
+
## TL;DR
|
6
|
+
|
7
|
+
Tron is a minimalistic combination of a [monad](https://www.morozov.is/2018/09/08/monad-laws-in-ruby.html) and [value object](https://madeintandem.com/blog/creating-value-objects-in-ruby/), implemented in [a few lines](https://github.com/halo/tron/blob/master/lib/tron.rb) of code.
|
8
|
+
|
9
|
+
Return `Tron.success(:it_worked)` or `Tron.failure(:aww_too_bad)` from a method to explain why and how it succeded/failed. That returns an immutable Struct (value object) that responds to `result.success?` and `result.failure?`.
|
10
|
+
|
11
|
+
The reason is accessible in `result.success #=> :it_worked`. You can add more metadata as a second argument: `Tron.failure(:nopes, error_code: 404)` which you can access like a Struct: `result.error_code #=> 404`.
|
12
|
+
|
13
|
+
Chaining can make your code cleaner: `result.on_success { download }.on_failure { show_message }`
|
14
|
+
|
15
|
+
## Introduction
|
16
|
+
|
17
|
+
|
6
18
|
|
7
19
|
Imagine you have a class like this:
|
8
20
|
|
@@ -14,7 +26,7 @@ class User
|
|
14
26
|
end
|
15
27
|
```
|
16
28
|
|
17
|
-
It's not clear from the code what this method returns
|
29
|
+
It's not clear from the code what this method returns. `true`?, a `User`?, a user ID?. What if a network error occurs, how does anyone calling `User.delete 42` know what happened?
|
18
30
|
|
19
31
|
Indeed, it is not even clear what "successful" means in the context of this message - if there is no user and you try to delete one, is that considered a "failure"?
|
20
32
|
|
@@ -22,21 +34,19 @@ Let's rewrite the method using Tron:
|
|
22
34
|
|
23
35
|
```ruby
|
24
36
|
class User
|
25
|
-
include Tron
|
26
|
-
|
27
37
|
def self.delete(id)
|
28
|
-
return
|
29
|
-
return
|
38
|
+
return Tron.failure(:id_missing) unless id
|
39
|
+
return Tron.failure(:invalid_id, id: id) unless id.match /[a-f]{8}/
|
30
40
|
|
31
41
|
user = @users[id]
|
32
42
|
if @users.delete id
|
33
|
-
|
43
|
+
Tron.success :user_deleted, user: user
|
34
44
|
else
|
35
|
-
|
45
|
+
Tron.success :deletion_failed, id: id
|
36
46
|
end
|
37
47
|
|
38
48
|
rescue ConnectionError
|
39
|
-
|
49
|
+
Tron.failure :deletion_failed_badly, id: id
|
40
50
|
end
|
41
51
|
end
|
42
52
|
```
|
@@ -45,39 +55,37 @@ One could even take it a step further and write it like this:
|
|
45
55
|
|
46
56
|
```ruby
|
47
57
|
class User
|
48
|
-
include Tron
|
49
|
-
|
50
58
|
def self.delete(id)
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
.on_success { send_sms }
|
55
|
-
.on_success { redirect }
|
59
|
+
check_id_syntax(id).on_success { delete_user(id) }
|
60
|
+
.on_success { send_sms }
|
61
|
+
.on_success { redirect }
|
56
62
|
end
|
57
63
|
|
58
64
|
def self.check_id_syntax(id)
|
59
|
-
return
|
60
|
-
return
|
61
|
-
|
65
|
+
return Tron.failure(:id_missing) unless id
|
66
|
+
return Tron.failure(:invalid_id, id: id) unless id.match /[a-f]{8}/
|
67
|
+
|
68
|
+
Tron.success :id_looks_good
|
62
69
|
end
|
63
70
|
|
64
71
|
def self.delete_user(id)
|
65
72
|
user = @users[id]
|
73
|
+
|
66
74
|
if @users.delete id
|
67
|
-
|
75
|
+
Tron.success :user_deleted, user: user
|
68
76
|
else
|
69
|
-
|
77
|
+
Tron.success :deletion_failed, id: id
|
70
78
|
end
|
71
79
|
|
72
|
-
rescue ConnectionError
|
73
|
-
|
80
|
+
rescue ConnectionError => ex
|
81
|
+
Tron.failure :deletion_failed_badly, id: id, message: ex.message
|
74
82
|
end
|
75
83
|
end
|
76
84
|
```
|
77
85
|
|
78
|
-
|
86
|
+
## So what are the benefits?
|
79
87
|
|
80
|
-
|
88
|
+
### 1. It will give you robust and predictable code
|
81
89
|
|
82
90
|
Tron will give you this consistent, implementation-unaware, programming convention:
|
83
91
|
|
@@ -85,28 +93,33 @@ Tron will give you this consistent, implementation-unaware, programming conventi
|
|
85
93
|
result = User.delete 42
|
86
94
|
|
87
95
|
if result.success?
|
88
|
-
puts "It worked! You deleted the user #{result.
|
96
|
+
puts "It worked! You deleted the user #{result.user.first_name}"
|
89
97
|
else
|
90
|
-
puts "Aw,
|
98
|
+
puts "Aw, couldn't delete User with ID #{result.id} because #{result.failure}"
|
91
99
|
end
|
92
100
|
```
|
93
101
|
|
102
|
+
The result is just a Struct:
|
103
|
+
|
94
104
|
```ruby
|
95
105
|
result = User.delete 42
|
96
106
|
|
97
|
-
|
98
|
-
result.
|
99
|
-
result.
|
100
|
-
|
101
|
-
|
107
|
+
# Query whether it worked
|
108
|
+
result.success? # => false
|
109
|
+
result.failure? # => true
|
110
|
+
|
111
|
+
# Query why and how
|
112
|
+
result.success # => nil
|
113
|
+
result.failure # => :deletion_failed_badly
|
102
114
|
|
103
|
-
#
|
104
|
-
|
105
|
-
result.
|
106
|
-
|
115
|
+
# Access immutable metadata
|
116
|
+
result.message # => "..."
|
117
|
+
result.inspect # => "#<struct failure=:alright, user_id=42, message='...'>"
|
118
|
+
|
119
|
+
result.message.upcase! # => modification raises an exception
|
107
120
|
```
|
108
121
|
|
109
|
-
|
122
|
+
### 2. If will give you better tests
|
110
123
|
|
111
124
|
How would you test this code?
|
112
125
|
|
@@ -127,20 +140,29 @@ end
|
|
127
140
|
|
128
141
|
You cannot simply test for the `false` as expected return value because it could mean anything. Tron helps you to check the response objects for every case.
|
129
142
|
|
130
|
-
|
143
|
+
### 3. It gives you documentation
|
131
144
|
|
132
145
|
While the code you're writing becomes slightly more verbose, that verbosity translates directly into documenation. You see immediately what each line is doing.
|
133
146
|
|
134
|
-
|
147
|
+
## Upgrading from 0.x.x to 1.x.x
|
148
|
+
|
149
|
+
* Don't use `include Tron`, it is not useful any more. There are no subclasses you might want to access.
|
150
|
+
* Replace `Tron::Success.call` with `Tron.success` (same for failure). The syntax is identical.
|
151
|
+
* The result object is now a Struct and has no `#meta` and `#metadata` methods anymore.
|
152
|
+
* The result object does not respond to `#code` any more. Instead use `#success` and `#failure` respectively. This is so that you can use `#code` as metadata, and also so that you can query the code via `#success` immediately, without first having to check `#success?`.
|
153
|
+
|
154
|
+
## Background
|
155
|
+
|
156
|
+
Tron is a complete rewrite of its predecessor [operation](https://github.com/halo/operation). I got inspired by the [deterministic](https://github.com/pzol/deterministic) gem, which is the follow-up of the [monadic](https://github.com/pzol/monadic) gem. There are some [complicated structs](https://github.com/dry-rb/dry-struct/blob/master/lib/dry/struct.rb) so I got inspired by [this robust implementation](https://github.com/iconara/immutable_struct) and simplified it even more.
|
135
157
|
|
136
|
-
|
158
|
+
## Requirements
|
137
159
|
|
138
|
-
|
160
|
+
* Ruby >= 2.3.0
|
139
161
|
|
140
|
-
|
162
|
+
## Copyright
|
141
163
|
|
142
|
-
|
164
|
+
MIT 2015-2019 halo. See [MIT-LICENSE](http://github.com/halo/tron/blob/master/LICENSE.md).
|
143
165
|
|
144
|
-
|
166
|
+
## Caveats
|
145
167
|
|
146
|
-
|
168
|
+
* There are no setter methods in the returned Struct, so you cannot overwrite the metadata. The values are also frozen, so you don't accidentally modify the attributes in-place. However, they are not deep-frozen, so an object may still be modified if you're ignorant.
|
data/lib/tron.rb
CHANGED
@@ -11,7 +11,7 @@ module Tron
|
|
11
11
|
code.respond_to?(:to_sym) ||
|
12
12
|
raise(ArgumentError, 'Tron.success must be called with a Symbol as first argument')
|
13
13
|
|
14
|
-
attributes.respond_to?(:keys)||
|
14
|
+
attributes.respond_to?(:keys) ||
|
15
15
|
raise(ArgumentError, 'The attributes Hash for Tron.success must respond to #keys')
|
16
16
|
|
17
17
|
attributes.respond_to?(:values) ||
|
@@ -34,7 +34,7 @@ module Tron
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def on_success(proc = nil, &block)
|
37
|
-
(proc || block).call
|
37
|
+
(proc || block).call self
|
38
38
|
end
|
39
39
|
|
40
40
|
def on_failure(_ = nil)
|
@@ -47,7 +47,7 @@ module Tron
|
|
47
47
|
code.respond_to?(:to_sym) ||
|
48
48
|
raise(ArgumentError, 'Tron.failure must be called with a Symbol as first argument')
|
49
49
|
|
50
|
-
attributes.respond_to?(:keys)||
|
50
|
+
attributes.respond_to?(:keys) ||
|
51
51
|
raise(ArgumentError, 'The attributes Hash for Tron.failure must respond to #keys')
|
52
52
|
|
53
53
|
attributes.respond_to?(:values) ||
|
@@ -74,7 +74,7 @@ module Tron
|
|
74
74
|
end
|
75
75
|
|
76
76
|
def on_failure(proc = nil, &block)
|
77
|
-
(proc || block).call
|
77
|
+
(proc || block).call self
|
78
78
|
end
|
79
79
|
end.new code.to_sym, *attributes.values.map(&:freeze)
|
80
80
|
end
|
data/lib/tron/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tron
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- halo
|
@@ -66,20 +66,6 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: rubocop
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
83
69
|
description: General-purpose method return objects that can be chained. Think minimalistic
|
84
70
|
value object monads. Heavily inspired by the `deterministic` gem, but much much
|
85
71
|
more light-weight.
|