tron 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/halo/tron.svg?branch=master)](https://travis-ci.org/halo/tron)
|
3
3
|
[![License](http://img.shields.io/badge/license-MIT-blue.svg)](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.
|