operation 0.0.3 → 1.0.0.beta

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7476c1c39664219019b09c247cc250dbebf0f345
4
- data.tar.gz: 40fa98cae6e39acfbf0dcbd53a7791b2d303ce1a
3
+ metadata.gz: e609f4ffd6fbf2a50ff0667fbf4bfdedd3a2f874
4
+ data.tar.gz: 28a06955ebe29eba6317917ae0be8dd3211a4711
5
5
  SHA512:
6
- metadata.gz: eac58c9b91409957e0b30b06d0a9ed8202184df68dccde38096876e1f678cdba1351e42fe257fb4482e21b89993d7d40eaa176cd5f590567c1fed9d4a8548c6c
7
- data.tar.gz: 791d169139a360edb60d6a5dcc2d8bdb683a344d5b1e5007099f9bd86a978416be1ba2116f731e9cbef841bd23a877e02bf3038d347e28ae557d7d66df4dfe81
6
+ metadata.gz: 8f8ee5b725295f6bd571e116775662374ae825f348752dd8284b244f45cb76cf160cbe50e16db8fbe1706bb3dd35e120caac1ad3da858492a0accae7473f835e
7
+ data.tar.gz: 6eb1d71f1794e0eebccb48f260922a0675de4f16f9d49f92e8cd15977f6bacd4b72390155bb114d7947a96df020f726e6807f4d66682b14f15e9c16c55942efe
data/README.md CHANGED
@@ -4,7 +4,7 @@ Imagine you have a class like this:
4
4
 
5
5
  ```ruby
6
6
  class User
7
- def delete(id)
7
+ def self.delete(id)
8
8
  @users.delete id
9
9
  end
10
10
  end
@@ -12,42 +12,32 @@ end
12
12
 
13
13
  What does it return? True? The User? an ID? What if an error occured? How does anyone calling `User.delete 42` know what happened?
14
14
 
15
- This is where `Operation` comes in. You would just **always** return an Operation. That Object holds information about what happened, like so:
15
+ This is where `Operation` comes in. You would simply return an operation object. That instance holds information about what happened, like so:
16
16
 
17
17
  ```ruby
18
18
  class User
19
- def delete(id)
19
+ def self.delete(id)
20
20
  return Operation.new(code: :id_missing) unless id
21
21
  return Operation.new(code: :invalid_id, id: id) unless id.match /[a-f]{8}/
22
22
 
23
23
  user = @users[id]
24
24
  if @users.delete id
25
- Operation.new success:true, code: :user_deleted, user: user
25
+ Operation.new success: true, code: :user_deleted, user: user
26
26
  else
27
- Operation.new code: :deletion_failed
27
+ Operation.new code: :deletion_failed, id: id
28
28
  end
29
+
29
30
  rescue ConnectionError
30
- Operation.new code: :deletion_failed_badly
31
+ Operation.new code: :deletion_failed_badly, id: id
31
32
  end
32
33
  end
33
34
  ```
34
35
 
35
- This will give you this joyful, consistent, conventional, implementation-unaware programming feeling:
36
-
37
- ```ruby
38
- operation = User.delete 42
39
- if operation.success?
40
- puts "It worked! You deleted the user #{operation.meta.user.first_name}"
41
- else
42
- puts "Oh, could not delete User with ID #{operation.object} because #{operation.code}"
43
- end
44
- ```
45
-
46
- For your convenience you can use `Operations#failure` and `Operations.success`:
36
+ There are some shortcuts too keep it less verbose:
47
37
 
48
38
  ```ruby
49
39
  class User
50
- def delete(id)
40
+ def self.delete(id)
51
41
  return Operations.failure(:id_missing) unless id
52
42
  return Operations.failure(:invalid_id, id: id) unless id.match /[a-f]{8}/
53
43
 
@@ -57,27 +47,73 @@ class User
57
47
  else
58
48
  Operations.failure :deletion_failed
59
49
  end
50
+
60
51
  rescue ConnectionError
61
52
  Operation.failure :deletion_failed_badly
62
53
  end
63
54
  end
64
55
  ```
65
56
 
66
- `#object` is just a shortcut to `#meta.object`:
57
+ ### So what are the benefits?
58
+
59
+ #### 1. Robust and predictable code
60
+
61
+ This will give you this joyful, consistent, conventional, implementation-unaware programming feeling:
62
+
63
+ ```ruby
64
+ operation = User.delete 42
65
+
66
+ if operation.success?
67
+ puts "It worked! You deleted the user #{operation.meta.user.first_name}"
68
+ else
69
+ puts "Aw, could not delete User with ID #{operation.meta.id} because #{operation.code}"
70
+ end
71
+ ```
72
+
73
+ ```ruby
74
+ operation = User.delete 42
75
+
76
+ operation.success? # => true
77
+ operation.failure? # => false
78
+ operation.metadata # => { object: <#User id=42> }
79
+ operation.meta # => { object: <#User id=42> }
80
+ operation.object # => <#User id=42> <- shortcut for meta[:object]
81
+
82
+ # In case you use Hashie, you will get that via #meta
83
+ require 'hashie/mash'
84
+ operation.meta # => <#Hashie::Mash object: <#User id=42>>
85
+ operation.object # => <#User id=42> <- shortcut for meta.object
86
+ ```
87
+
88
+ #### 2. Better tests
89
+
90
+ How would you test this code?
67
91
 
68
92
  ```ruby
69
- operation = User.delete 42
70
- operation.success? # => true
71
- operation.object # => <#User id=42>
72
- operation.meta # => <#Hashie::Mash object: <#User id=42>>
73
- operation.metadata # => { object: <#User id=42> }
74
- operation.failure? # => false
93
+ class Product
94
+ def self.delete(id)
95
+ return false if id.blank?
96
+ return false unless product = Products.find(id)
97
+ return false unless permission?
98
+ api.update(id, attributes)
99
+ end
100
+
101
+ def self.permission?
102
+ Date.today.sunday?
103
+ end
104
+ end
75
105
  ```
76
106
 
107
+ You cannot simply test for the `false` as expected return value because it could mean anything.
108
+
109
+ #### 3. Documentation
110
+
111
+ While the code becomes more verbose, that verbosity translates directly into documenation. You see immediately what each line is doing.
112
+
77
113
  ### Requirements
78
114
 
79
- * Ruby >= 1.9
115
+ * Ruby >= 2.1
80
116
 
81
117
  ### Copyright
82
118
 
83
- MIT 2014 halo. See [MIT-LICENSE](http://github.com/halo/operation/blob/master/MIT-LICENSE).
119
+ MIT 2015 halo. See [MIT-LICENSE](http://github.com/halo/operation/blob/master/LICENSE.md).
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,90 @@
1
+ class Operation
2
+ module Deferrable
3
+
4
+ attr_reader :arguments
5
+
6
+ def on_progress(&block)
7
+ return unless block
8
+ progressables.unshift block
9
+ end
10
+
11
+ def on_success(&block)
12
+ return unless block
13
+
14
+ if succeeded?
15
+ block.call(*arguments)
16
+ elsif !failed?
17
+ successables.unshift block
18
+ end
19
+ end
20
+
21
+ def on_failure(&block)
22
+ return unless block
23
+
24
+ if failed?
25
+ block.call(*arguments)
26
+ elsif !succeeded?
27
+ failables.unshift block
28
+ end
29
+ end
30
+
31
+ def succeeded?
32
+ status == :succeeded
33
+ end
34
+
35
+ def failed?
36
+ status == :failed
37
+ end
38
+
39
+ def status
40
+ @status ||= :unknown
41
+ end
42
+
43
+ def set_status(new_status, *args)
44
+ @arguments = args
45
+ @status = new_status
46
+
47
+ if succeeded? && !successables.empty?
48
+ while callback = successables.pop
49
+ callback.call(*arguments)
50
+ end
51
+ failables.clear if !failables.empty?
52
+
53
+ elsif failed? && !failables.empty?
54
+ while callback = failables.pop
55
+ callback.call(*arguments)
56
+ end
57
+ successables.clear if !failables.empty?
58
+ end
59
+ end
60
+
61
+ def progress(percent, code)
62
+ return if succeeded? || failed?
63
+
64
+ progressables.each do |callback|
65
+ callback.call ::Operation::Progress.new(percent, code)
66
+ end
67
+ end
68
+
69
+ def succeed(*args)
70
+ set_status :succeeded, *args
71
+ end
72
+
73
+ def fail(*args)
74
+ set_status :failed, *args
75
+ end
76
+
77
+ def successables
78
+ @successables ||= []
79
+ end
80
+
81
+ def failables
82
+ @failables ||= []
83
+ end
84
+
85
+ def progressables
86
+ @progressables ||= []
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,18 @@
1
+ class Operation
2
+ class Progress
3
+
4
+ def initialize(percent, code = nil)
5
+ @percent, @code = percent, code
6
+ end
7
+
8
+ def percent
9
+ @percent.to_i
10
+ end
11
+
12
+ def code
13
+ return if @code.to_s == ''
14
+ @code.to_s.to_sym
15
+ end
16
+
17
+ end
18
+ end
@@ -1,9 +1,10 @@
1
1
  class Operation
2
2
  module VERSION #:nodoc:
3
- MAJOR = 0
3
+ MAJOR = 1
4
4
  MINOR = 0
5
- TINY = 3
5
+ TINY = 0
6
+ PRE = 'beta'
6
7
 
7
- STRING = [MAJOR, MINOR, TINY].compact.join('.')
8
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join '.'
8
9
  end
9
10
  end
data/lib/operation.rb CHANGED
@@ -1,30 +1,48 @@
1
- require 'hashie/mash'
2
- require 'operations' # <- Not that we need them here, but the developer using this gem might want them.
1
+ require 'operation/deferrable'
2
+ require 'operation/progress'
3
+ require 'operations'
3
4
 
4
5
  class Operation
5
-
6
+ include ::Operation::Deferrable
6
7
  attr_reader :metadata, :code
7
8
 
8
9
  def initialize(options = {})
9
- @success = [true, 'true', 1, '1'].include? options[:success]
10
- @code = options[:code].to_s.to_sym unless options[:code].to_s == ''
11
- @metadata = options[:metadata]
10
+ @options = options
12
11
  end
13
12
 
14
13
  def success?
15
- !!@success
14
+ [true, 'true', 1, '1'].include? options[:success]
16
15
  end
17
16
 
18
17
  def failure?
19
18
  !success?
20
19
  end
21
20
 
21
+ def code
22
+ return if options[:code].to_s == ''
23
+ options[:code].to_s.to_sym
24
+ end
25
+
22
26
  # Convenience Wrapper
23
27
  def object
24
- meta.object
28
+ meta[:object] || meta['object'] || meta.object
29
+ rescue
30
+ nil
31
+ end
32
+
33
+ def metadata
34
+ options[:metadata]
25
35
  end
26
36
 
27
37
  def meta
38
+ if defined? Hashie::Mash
39
+ metamash
40
+ else
41
+ metadata
42
+ end
43
+ end
44
+
45
+ def metamash
28
46
  if metadata.respond_to? :each_pair
29
47
  Hashie::Mash.new metadata
30
48
  elsif metadata
@@ -33,4 +51,9 @@ class Operation
33
51
  Hashie::Mash.new
34
52
  end
35
53
  end
54
+
55
+ private
56
+
57
+ attr_reader :options
58
+
36
59
  end
data/lib/operations.rb CHANGED
@@ -1,11 +1,11 @@
1
1
  module Operations
2
2
 
3
3
  def self.success(code = :success, metadata = {})
4
- Operation.new success: true, code: code, metadata: metadata
4
+ ::Operation.new success: true, code: code, metadata: metadata
5
5
  end
6
6
 
7
7
  def self.failure(code, metadata = {})
8
- Operation.new success: false, code: code, metadata: metadata
8
+ ::Operation.new success: false, code: code, metadata: metadata
9
9
  end
10
10
 
11
11
  end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Operation do
4
+ let(:operation) { Operation.new }
5
+ let(:successables) { [] }
6
+ let(:progressables) { [] }
7
+ let(:failables) { [] }
8
+
9
+ describe '#on_progress' do
10
+ before do
11
+ operation.on_progress { |result| progressables << result }
12
+ operation.progress 42, :still_working
13
+ operation.progress 99, :almost_done
14
+ end
15
+
16
+ it 'calls all the successables' do
17
+ expect(progressables.size).to eq 2
18
+ expect(progressables.first.percent).to eq 42
19
+ expect(progressables.first.code).to eq :still_working
20
+ expect(progressables.last.percent).to eq 99
21
+ expect(progressables.last.code).to eq :almost_done
22
+ end
23
+
24
+ it 'does not call the successables' do
25
+ expect(successables).to be_empty
26
+ end
27
+
28
+ it 'does not call the failables' do
29
+ expect(failables).to be_empty
30
+ end
31
+
32
+ it 'does not alter the state' do
33
+ expect(operation.status).to eq :unknown
34
+ end
35
+ end
36
+
37
+ describe '#on_success' do
38
+ before do
39
+ operation.on_success { successables << 'called' }
40
+ operation.on_success { |result| successables << result }
41
+ operation.succeed :great
42
+ end
43
+
44
+ it 'calls all the successables' do
45
+ expect(successables).to eq ['called', :great]
46
+ end
47
+
48
+ it 'does not call the failables' do
49
+ expect(failables).to be_empty
50
+ end
51
+ end
52
+
53
+ describe '#on_failure' do
54
+ before do
55
+ operation.on_failure { failables << 'called' }
56
+ operation.on_failure { |result| failables << result }
57
+ operation.fail :bummer
58
+ end
59
+
60
+ it 'calls all the successables' do
61
+ expect(failables).to eq ['called', :bummer]
62
+ end
63
+
64
+ it 'does not call the failables' do
65
+ expect(successables).to be_empty
66
+ end
67
+ end
68
+
69
+ end
@@ -1,7 +1,6 @@
1
1
  require 'spec_helper'
2
- require 'operation'
3
2
 
4
- describe Operation do
3
+ RSpec.describe Operation do
5
4
 
6
5
  describe '#success' do
7
6
  it 'handles different types of success' do
@@ -18,6 +17,7 @@ describe Operation do
18
17
  expect( Operation.new(success: 'false') ).to be_failure
19
18
  expect( Operation.new(success: 0) ).to be_failure
20
19
  expect( Operation.new(success: '0') ).to be_failure
20
+ expect( Operation.new ).to be_failure
21
21
  end
22
22
  end
23
23
 
@@ -45,7 +45,29 @@ describe Operation do
45
45
  end
46
46
  end
47
47
 
48
+ describe 'test setup' do
49
+ it 'has no Hashie' do
50
+ expect { Hashie }.to raise_error NameError
51
+ end
52
+ end
53
+
48
54
  describe '#meta' do
55
+ it 'is nil for nil' do
56
+ expect( Operation.new.meta ).to be_nil
57
+ expect( Operation.new(metadata: nil).meta ).to be_nil
58
+ end
59
+
60
+ it 'is whatever was passed in' do
61
+ expect( Operation.new(metadata: {}).meta ).to be_instance_of Hash
62
+ expect( Operation.new(metadata: { one: { two: :three } }).meta[:one][:two] ).to eq :three
63
+ end
64
+ end
65
+
66
+ describe '#meta', :hashie do
67
+ before :all do
68
+ require 'hashie/mash'
69
+ end
70
+
49
71
  it 'is a Mash for nil' do
50
72
  expect( Operation.new.meta ).to be_instance_of Hashie::Mash
51
73
  expect( Operation.new(metadata: nil).meta ).to be_instance_of Hashie::Mash
data/spec/spec_helper.rb CHANGED
@@ -0,0 +1,10 @@
1
+ require 'operation'
2
+
3
+ RSpec.configure do |config|
4
+
5
+ config.raise_errors_for_deprecations!
6
+ config.disable_monkey_patching!
7
+ config.color = true
8
+ config.fail_fast = true
9
+
10
+ end
metadata CHANGED
@@ -1,69 +1,69 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: operation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 1.0.0.beta
5
5
  platform: ruby
6
6
  authors:
7
7
  - halo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-27 00:00:00.000000000 Z
11
+ date: 2015-03-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hashie
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
- type: :runtime
20
+ type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: guard-rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rb-fsevent
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - '>='
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  description: Implementation-agnostic method return status objects (say what?). Your
@@ -74,11 +74,14 @@ executables: []
74
74
  extensions: []
75
75
  extra_rdoc_files: []
76
76
  files:
77
- - MIT-LICENSE
78
77
  - README.md
78
+ - Rakefile
79
79
  - lib/operation.rb
80
+ - lib/operation/deferrable.rb
81
+ - lib/operation/progress.rb
80
82
  - lib/operation/version.rb
81
83
  - lib/operations.rb
84
+ - spec/lib/operation/deferrable_spec.rb
82
85
  - spec/lib/operation_spec.rb
83
86
  - spec/spec_helper.rb
84
87
  homepage: https://github.com/halo/operation
@@ -91,17 +94,17 @@ require_paths:
91
94
  - lib
92
95
  required_ruby_version: !ruby/object:Gem::Requirement
93
96
  requirements:
94
- - - '>='
97
+ - - ">="
95
98
  - !ruby/object:Gem::Version
96
- version: 1.9.3
99
+ version: 2.1.0
97
100
  required_rubygems_version: !ruby/object:Gem::Requirement
98
101
  requirements:
99
- - - '>='
102
+ - - ">"
100
103
  - !ruby/object:Gem::Version
101
- version: '0'
104
+ version: 1.3.1
102
105
  requirements: []
103
106
  rubyforge_project:
104
- rubygems_version: 2.2.2
107
+ rubygems_version: 2.4.5
105
108
  signing_key:
106
109
  specification_version: 4
107
110
  summary: Implementation-agnostic method return status objects
data/MIT-LICENSE DELETED
@@ -1,20 +0,0 @@
1
- Copyright 2014 halo
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.