tron 0.7.0
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 +7 -0
- data/README.md +146 -0
- data/Rakefile +1 -0
- data/lib/tron.rb +8 -0
- data/lib/tron/failure.rb +16 -0
- data/lib/tron/resultable.rb +62 -0
- data/lib/tron/success.rb +16 -0
- data/lib/tron/version.rb +10 -0
- metadata +122 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e0ecb0f09d903e55c0725c96d38b913ba42ce1a5
|
4
|
+
data.tar.gz: 86b2a7e89a06393f26a69208ae775b5e4a0bdc34
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4861aa9046fc9dfc70e003d48ac729f7f25406538e034a4601e45252731682a281a475c71d5257244d355830abe3a0401a90a6b09cdd9278182a5d63ff1309f8
|
7
|
+
data.tar.gz: a308d078748e1d704b7fc54657f58f7011ca079ec977c7996517578ff867504997fa9a3cc3dc27c178bb301c7ecc110c3f3ba2499ee039e773882e2f0b7f2188
|
data/README.md
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
[](https://rubygems.org/gems/tron)
|
2
|
+
[](https://travis-ci.org/halo/tron)
|
3
|
+
[](http://github.com/halo/tron/blob/master/LICENSE.md)
|
4
|
+
|
5
|
+
# Tron
|
6
|
+
|
7
|
+
Imagine you have a class like this:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class User
|
11
|
+
def self.delete(id)
|
12
|
+
@users.delete id
|
13
|
+
end
|
14
|
+
end
|
15
|
+
```
|
16
|
+
|
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?
|
18
|
+
|
19
|
+
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
|
+
|
21
|
+
Let's rewrite the method using Tron:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
class User
|
25
|
+
include Tron
|
26
|
+
|
27
|
+
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}/
|
30
|
+
|
31
|
+
user = @users[id]
|
32
|
+
if @users.delete id
|
33
|
+
Success.call :user_deleted, user: user
|
34
|
+
else
|
35
|
+
Success.call :deletion_failed, id: id
|
36
|
+
end
|
37
|
+
|
38
|
+
rescue ConnectionError
|
39
|
+
Failure.call :deletion_failed_badly, id: id
|
40
|
+
end
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
One could even take it a step further and write it like this:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
class User
|
48
|
+
include Tron
|
49
|
+
|
50
|
+
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 }
|
56
|
+
end
|
57
|
+
|
58
|
+
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)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.delete_user(id)
|
65
|
+
user = @users[id]
|
66
|
+
if @users.delete id
|
67
|
+
Success.call :user_deleted, user: user
|
68
|
+
else
|
69
|
+
Success.call :deletion_failed, id: id
|
70
|
+
end
|
71
|
+
|
72
|
+
rescue ConnectionError
|
73
|
+
Failure.call :deletion_failed_badly, id: id
|
74
|
+
end
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
### So what are the benefits?
|
79
|
+
|
80
|
+
#### 1. It will give you robust and predictable code
|
81
|
+
|
82
|
+
Tron will give you this consistent, implementation-unaware, programming convention:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
result = User.delete 42
|
86
|
+
|
87
|
+
if result.success?
|
88
|
+
puts "It worked! You deleted the user #{result.meta.user.first_name}"
|
89
|
+
else
|
90
|
+
puts "Aw, could not delete User with ID #{result.meta.id} because #{result.code}"
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
result = User.delete 42
|
96
|
+
|
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]
|
102
|
+
|
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
|
107
|
+
```
|
108
|
+
|
109
|
+
#### 2. If will give you better tests
|
110
|
+
|
111
|
+
How would you test this code?
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
class Product
|
115
|
+
def self.delete(id)
|
116
|
+
return false if id.blank?
|
117
|
+
return false unless product = Products.find(id)
|
118
|
+
return false unless permission?
|
119
|
+
api.update(id, attributes)
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.permission?
|
123
|
+
Date.today.sunday?
|
124
|
+
end
|
125
|
+
end
|
126
|
+
```
|
127
|
+
|
128
|
+
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
|
+
|
130
|
+
#### 3. It gives you documentation
|
131
|
+
|
132
|
+
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
|
+
|
134
|
+
### Background
|
135
|
+
|
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.
|
137
|
+
|
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.
|
139
|
+
|
140
|
+
### Requirements
|
141
|
+
|
142
|
+
* Ruby >= 2.2.3
|
143
|
+
|
144
|
+
### Copyright
|
145
|
+
|
146
|
+
MIT 2015 halo. See [MIT-LICENSE](http://github.com/halo/tron/blob/master/LICENSE.md).
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/lib/tron.rb
ADDED
data/lib/tron/failure.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
module Tron
|
2
|
+
module Resultable
|
3
|
+
attr_reader :metadata
|
4
|
+
|
5
|
+
def self.included(receiver)
|
6
|
+
receiver.extend ::Tron::Resultable::ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
# Convenience wrapper
|
11
|
+
def call(code, metadata = nil)
|
12
|
+
new code: code, metadata: metadata
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(code: nil, metadata: nil)
|
17
|
+
@code = code
|
18
|
+
@metadata = metadata
|
19
|
+
end
|
20
|
+
|
21
|
+
def success?
|
22
|
+
is_a? ::Tron::Success
|
23
|
+
end
|
24
|
+
|
25
|
+
def failure?
|
26
|
+
is_a? ::Tron::Failure
|
27
|
+
end
|
28
|
+
|
29
|
+
def code
|
30
|
+
return if @code.to_s == ''
|
31
|
+
@code.to_s.to_sym
|
32
|
+
end
|
33
|
+
|
34
|
+
# Convenience Wrapper
|
35
|
+
def object
|
36
|
+
metadata[:object] || metadata['object']
|
37
|
+
rescue
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def meta
|
42
|
+
if defined? ::Hashie::Mash
|
43
|
+
metamash
|
44
|
+
else
|
45
|
+
metadata
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def metamash
|
52
|
+
if metadata.respond_to? :each_pair
|
53
|
+
::Hashie::Mash.new metadata
|
54
|
+
elsif metadata
|
55
|
+
metadata
|
56
|
+
else
|
57
|
+
::Hashie::Mash.new
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
data/lib/tron/success.rb
ADDED
data/lib/tron/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tron
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.7.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- halo
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: hashie
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: guard-rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rb-fsevent
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
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
|
+
description: General-purpose method return objects that can be chained. Heavily inspired
|
84
|
+
by the `deterministic` gem, but much much more light-weight.
|
85
|
+
email:
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- README.md
|
91
|
+
- Rakefile
|
92
|
+
- lib/tron.rb
|
93
|
+
- lib/tron/failure.rb
|
94
|
+
- lib/tron/resultable.rb
|
95
|
+
- lib/tron/success.rb
|
96
|
+
- lib/tron/version.rb
|
97
|
+
homepage: https://github.com/halo/tron
|
98
|
+
licenses:
|
99
|
+
- MIT
|
100
|
+
metadata: {}
|
101
|
+
post_install_message:
|
102
|
+
rdoc_options: []
|
103
|
+
require_paths:
|
104
|
+
- lib
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 2.2.3
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
requirements: []
|
116
|
+
rubyforge_project:
|
117
|
+
rubygems_version: 2.4.5.1
|
118
|
+
signing_key:
|
119
|
+
specification_version: 4
|
120
|
+
summary: General-purpose method return objects that can be chained.
|
121
|
+
test_files: []
|
122
|
+
has_rdoc:
|