tron 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +67 -45
  3. data/lib/tron.rb +4 -4
  4. data/lib/tron/version.rb +1 -1
  5. metadata +1 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 990e0bd04a0f3189d1483d9ebe010c740461aedc
4
- data.tar.gz: e11e18fc405897c6d3fc36fd14440efd4b5a2bb7
3
+ metadata.gz: eea7d3927403faf13121f530c2a64d3ae05fa55b
4
+ data.tar.gz: 6d70cb35c23eeb4b8b49e9cd031f6fad1fdb77b4
5
5
  SHA512:
6
- metadata.gz: 6651802dd7c1e274810caff0c0f0e924614da61c3861e66278264e24e91cc285c97d24c64c2d79528fcf7b4adfbbefe3eb0108559f1e7c31ec89be4e6fd9ff2f
7
- data.tar.gz: 4aa73296745a4415dd108627176530b320408cd96e6f639b462aa5610fa8e8889ef42f2c6023097b2c5d7f0f9a7d5f44c8efa32236a777a52b379f7d775cfb85
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
- # Tron
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 (`true`?, a `User`?, a user ID?). What if an error occured - how does anyone calling `User.delete 42` know what happened?
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 Failure.call(:id_missing) unless id
29
- return Failure.call(:invalid_id, id: id) unless id.match /[a-f]{8}/
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
- Success.call :user_deleted, user: user
43
+ Tron.success :user_deleted, user: user
34
44
  else
35
- Success.call :deletion_failed, id: id
45
+ Tron.success :deletion_failed, id: id
36
46
  end
37
47
 
38
48
  rescue ConnectionError
39
- Failure.call :deletion_failed_badly, id: id
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
- # If any one of these fail, the following blocks won't be executed
52
- check_id_syntax(id)
53
- .on_success { delete_user(id) }
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 Failure.call(:id_missing) unless id
60
- return Failure.call(:invalid_id, id: id) unless id.match /[a-f]{8}/
61
- Success.call(:id_looks_good)
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
- Success.call :user_deleted, user: user
75
+ Tron.success :user_deleted, user: user
68
76
  else
69
- Success.call :deletion_failed, id: id
77
+ Tron.success :deletion_failed, id: id
70
78
  end
71
79
 
72
- rescue ConnectionError
73
- Failure.call :deletion_failed_badly, id: id
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
- ### So what are the benefits?
86
+ ## So what are the benefits?
79
87
 
80
- #### 1. It will give you robust and predictable code
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.meta.user.first_name}"
96
+ puts "It worked! You deleted the user #{result.user.first_name}"
89
97
  else
90
- puts "Aw, could not delete User with ID #{result.meta.id} because #{result.code}"
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
- result.success? # => true
98
- result.failure? # => false
99
- result.metadata # => { object: <#User id=42> }
100
- result.meta # => { object: <#User id=42> }
101
- result.object # => <#User id=42> <- shortcut for meta[:object]
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
- # In case you use Hashie, you will get that via #meta
104
- require 'hashie/mash'
105
- result.meta # => <#Hashie::Mash object: <#User id=42>>
106
- result.object # => <#User id=42> <- shortcut for meta.object
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
- #### 2. If will give you better tests
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
- #### 3. It gives you documentation
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
- ### Background
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
- 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.
158
+ ## Requirements
137
159
 
138
- `operation` is very useful, but the API was always a bit cumbersome. Additionally, there was no paradigm of chaining trons, i.e. run multiple trons but bail out if one of them fails.
160
+ * Ruby >= 2.3.0
139
161
 
140
- ### Requirements
162
+ ## Copyright
141
163
 
142
- * Ruby >= 2.2.3
164
+ MIT 2015-2019 halo. See [MIT-LICENSE](http://github.com/halo/tron/blob/master/LICENSE.md).
143
165
 
144
- ### Copyright
166
+ ## Caveats
145
167
 
146
- MIT 2015 halo. See [MIT-LICENSE](http://github.com/halo/tron/blob/master/LICENSE.md).
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
@@ -2,7 +2,7 @@ module Tron
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 1
4
4
  MINOR = 0
5
- TINY = 0
5
+ TINY = 1
6
6
  PRE = nil
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join '.'
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.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.