operation 0.0.3 → 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
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.