tron 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,146 @@
1
+ [![Gem Version](https://img.shields.io/gem/v/tron.svg)](https://rubygems.org/gems/tron)
2
+ [![Build Status](https://travis-ci.org/halo/tron.svg?branch=master)](https://travis-ci.org/halo/tron)
3
+ [![License](http://img.shields.io/badge/license-MIT-blue.svg)](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).
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,8 @@
1
+ require 'tron/version'
2
+
3
+ require 'tron/resultable'
4
+ require 'tron/success'
5
+ require 'tron/failure'
6
+
7
+ module Tron
8
+ end
@@ -0,0 +1,16 @@
1
+ module Tron
2
+ class Failure
3
+ include Resultable
4
+
5
+ def on_success(_ = nil)
6
+ self
7
+ end
8
+ alias_method :>>, :on_success
9
+
10
+ def on_failure(proc = nil, &block)
11
+ (proc || block).call
12
+ end
13
+ alias_method :>>, :on_failure
14
+
15
+ end
16
+ end
@@ -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
@@ -0,0 +1,16 @@
1
+ module Tron
2
+ class Success
3
+ include Resultable
4
+
5
+ def on_success(proc = nil, &block)
6
+ (proc || block).call
7
+ end
8
+ alias_method :>>, :on_success
9
+
10
+ def on_failure(_ = nil)
11
+ self
12
+ end
13
+ alias_method :>>, :on_failure
14
+
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ module Tron
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 7
5
+ TINY = 0
6
+ PRE = nil
7
+
8
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join '.'
9
+ end
10
+ end
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: