actioninteractor 0.0.17.3 → 0.1.0

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
  SHA256:
3
- metadata.gz: 2b81aa835686296262195e571d9fb8d1236843347c3ca5122aab1e0811cb6e91
4
- data.tar.gz: adcd0dd458f3060e867f551f91675486f9cdb7d45da1542b7b0cce70d6dc2efa
3
+ metadata.gz: fbd574e879d9fab318f41cc0f07eebdaaefc8e05327c7bb2372a85d34764d685
4
+ data.tar.gz: 1f7db0bf258fa20186e9191e4935c500c0c99fd9de319393b1d2d3651e12c0ad
5
5
  SHA512:
6
- metadata.gz: 5e9fb543029fe26df807969abfc770b5b5f7488fe36c92bb097e85dbfbfca75ca0b87016197327fcb05c652a411ff4311bff4962ca3ea2410dc0b63632a13efe
7
- data.tar.gz: 99367f92a0eef0f35e47c3f12dfd1fc1ea32ca0e03d0d80df1eb4656c6f2f214677b5d14b1f0cdda5d16663571279fc6c1842a74c47d8eca76e1329686e52137
6
+ metadata.gz: 51f6b4f5e7740e49083f9895c9c799486a88aad1dbaeb310573ba3a82e22aa25cfc2e5e9dc9112bc9214f87f200a3ea885a4f0b895330a3aa424688fc79812b8
7
+ data.tar.gz: 5f43085d1442d8ad88f365746c276e907fa90b6b645c47df9fe851507fe3778c776c73a10c558f703dd87c3941c843ffc906c214493c0379353dd8fae2e64e7f
@@ -25,4 +25,5 @@ Gem::Specification.new do |s|
25
25
 
26
26
  s.add_dependency "bundler", ">= 1.3"
27
27
  s.add_development_dependency "rake", "~> 0"
28
+ s.add_development_dependency "test-unit", ">= 3.3"
28
29
  end
@@ -1,77 +1,118 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionInteractor
4
+ # == Action \Interactor \Base
5
+ #
6
+ # This is a base class for an interactor (data processing unit).
7
+ # It gets a payload (input) as an initialization parameter and
8
+ # execute some methods which is described in `execute` method.
9
+ # After that, the results can be obtained by `results` method.
10
+ # In Ruby on Rails, it can be used for doing some business logic
11
+ # like new user registration process. For example inserting user data
12
+ # in the database and creating a notification message, registering a
13
+ # job for sending the message.
14
+ #
15
+ # class RegistrationInteractor < ActionInteractor::Base
16
+ # def execute
17
+ # return fail! unless payload[:name]
18
+ # user = User.create!(name: payload[:name])
19
+ # notiticaion = user.notifications.create!(name: 'Welcome')
20
+ # RegistrationNotificationJob.perform_later!
21
+ # results.add(:user, user)
22
+ # success!
23
+ # end
24
+ # end
4
25
  class Base
5
- attr_reader :params, :results
26
+ attr_reader :payload, :errors, :results
6
27
 
7
- def initialize(params)
8
- @params = params
28
+ # Initialize with payload
29
+ # Errors and Results data and initial state will be set.
30
+ def initialize(payload)
31
+ @payload = payload
32
+ @errors = Errors.new
33
+ @results = Results.new
9
34
  reset!
10
35
  end
11
36
 
37
+ # Execute the operation with given payload.
38
+ # (Should be overridden.)
12
39
  def execute
13
40
  # if the interactor already finished execution, do nothing.
14
41
  return if finished?
15
- # if contract is not satisfied= (ex: params is empty), mark as failed.
16
- return fail! if params.nil?
17
- # implement some codes
42
+ # if contract is not satisfied= (ex: payload is empty), mark as failed.
43
+ return fail! if payload.nil?
44
+ # (Implement some codes for the operation.)
45
+
18
46
  # if finished execution, mark as success.
19
47
  success!
20
48
  end
21
49
 
50
+ # Execute the operation with given payload.
51
+ # If there are some errors, ActionInteractor::ExeuctionError will be raised.
22
52
  def execute!
23
53
  execute
24
54
  success? || raise(ExecutionError.new("Failed to execute the interactor"))
25
55
  end
26
56
 
57
+ # Returns `true` if marked as finished otherwise `false`.
27
58
  def finished?
28
59
  @_finished
29
60
  end
30
61
 
62
+ # Returns `true` if not marked as finished otherwise `false`.
31
63
  def unfinished?
32
64
  !finished?
33
65
  end
34
66
 
67
+ # Returns `true` if marked as success and there are no errors otherwise `false`.
35
68
  def success?
36
- @_success
69
+ @_success && @errors.empty?
37
70
  end
38
71
 
72
+ # Returns `true` if not marked as success or there are some errors otherwise `false`.
39
73
  def failure?
40
74
  !success?
41
75
  end
42
76
 
77
+ # Returns `true` if the operation was not successful and not finished otherwise `false`.
43
78
  def aborted?
44
79
  failure? && unfinished?
45
80
  end
46
81
 
82
+ # Reset statuses.
47
83
  def reset!
48
- @results = Results.new
49
84
  @_success = false
50
85
  @_finished = false
51
86
  end
52
87
 
88
+ # Mark the operation as failed and unfinished.
53
89
  def abort!
54
90
  @_success = false
55
91
  @_finished = false
56
92
  end
57
93
 
94
+ # Mark the operation as success and finished.
58
95
  def success!
59
96
  @_success = true
60
97
  @_finished = true
61
98
  end
62
99
 
100
+ # Mask the operation as failed and finished.
63
101
  def fail!
64
102
  @_success = false
65
103
  @_finished = true
66
104
  end
67
105
 
68
106
  class << self
69
- def execute(params)
70
- new(params).tap(&:execute)
107
+ # Execute the operation with given payload.
108
+ def execute(payload)
109
+ new(payload).tap(&:execute)
71
110
  end
72
111
 
73
- def execute!(params)
74
- new(params).tap(&:execute!)
112
+ # Execute the operation with given payload.
113
+ # If there are some errors, ActionInteractor::ExeuctionError will be raised.
114
+ def execute!(payload)
115
+ new(payload).tap(&:execute!)
75
116
  end
76
117
  end
77
118
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module ActionInteractor
6
+ # == Action \Interactor \Errors
7
+ # Provides a +Hash+ like object to Action Interactors execution errors.
8
+ class Errors
9
+ include Enumerable
10
+ extend Forwardable
11
+
12
+ attr_reader :errors
13
+
14
+ def_delegators :@errors, :clear, :keys, :values, :[], :empty?, :any?
15
+
16
+ def initialize(*)
17
+ @errors = {}
18
+ end
19
+
20
+ # Add +error+ to the errors hash.
21
+ def add(attribute, error)
22
+ errors[attribute.to_sym] = error
23
+ end
24
+
25
+ # Delete a error for +key+.
26
+ def delete(key)
27
+ attribute = key.to_sym
28
+ errors.delete(attribute)
29
+ end
30
+
31
+ # Iterates through each error key, value pair in the errors hash.
32
+ def each
33
+ errors.each_key do |attribute|
34
+ yield attribute, errors[attribute]
35
+ end
36
+ end
37
+
38
+ # Convert errors to hash.
39
+ def to_hash
40
+ errors
41
+ end
42
+
43
+ # Returns array containing error messages.
44
+ def messages
45
+ errors.map do |attribute, error|
46
+ "#{attribute} #{error}"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -3,20 +3,46 @@
3
3
  require "forwardable"
4
4
 
5
5
  module ActionInteractor
6
+ # == Action \Interactor \Results
7
+ # Provides a +Hash+ like object to Action Interactors execution results.
6
8
  class Results
7
9
  include Enumerable
8
10
  extend Forwardable
9
11
 
10
- attr_reader :_results
12
+ attr_reader :results
11
13
 
12
- def_delegators :@_results, :clear, :keys, :values, :[], :delete
14
+ def_delegators :@results, :clear, :keys, :values, :[]
13
15
 
14
16
  def initialize(*)
15
- @_results = {}
17
+ @results = {}
16
18
  end
17
19
 
18
- def add(attribute, detail)
19
- _results[attribute.to_sym] = detail
20
+ # Add +result+ to the results hash.
21
+ def add(attribute, result)
22
+ results[attribute.to_sym] = result
23
+ end
24
+
25
+ # Delete a result for +key+.
26
+ def delete(key)
27
+ attribute = key.to_sym
28
+ results.delete(attribute)
29
+ end
30
+
31
+ # Iterates through each result key, value pair in the results hash.
32
+ def each
33
+ results.each_key do |attribute|
34
+ yield attribute, results[attribute]
35
+ end
36
+ end
37
+
38
+ def method_missing(attribute, *)
39
+ # Define shortcut methods for each result key.
40
+ # It returns the result for the key
41
+ if results.has_key?(attribute)
42
+ results[attribute]
43
+ else
44
+ super
45
+ end
20
46
  end
21
47
  end
22
48
  end
@@ -4,5 +4,6 @@ $:.unshift File.dirname(__FILE__)
4
4
 
5
5
  module ActionInteractor
6
6
  autoload :Base, "action_interactor/base"
7
+ autoload :Errors, "action_interactor/errors"
7
8
  autoload :Results, "action_interactor/results"
8
9
  end
@@ -2,44 +2,52 @@ require "test/unit"
2
2
  require_relative "../lib/actioninteractor"
3
3
 
4
4
  class BaseTest < Test::Unit::TestCase
5
+ test "initialized successfully" do
6
+ payload = {}
7
+ interactor = ActionInteractor::Base.new(payload)
8
+ assert_equal(interactor.success?, false)
9
+ assert_equal(interactor.finished?, false)
10
+ assert_equal(interactor.errors.empty?, true)
11
+ end
12
+
5
13
  test ".execute does not raise error" do
6
- params = {}
7
- assert_nothing_raised { ActionInteractor::Base.execute(params) }
14
+ payload = {}
15
+ assert_nothing_raised { ActionInteractor::Base.execute(payload) }
8
16
  end
9
17
 
10
18
  test ".execute returns an ActionInteractor::Base instance" do
11
- params = {}
12
- interactor = ActionInteractor::Base.execute(params)
19
+ payload = {}
20
+ interactor = ActionInteractor::Base.execute(payload)
13
21
  assert_instance_of(ActionInteractor::Base, interactor)
14
22
  end
15
23
 
16
24
  test ".execute! also returns an ActionInteractor::Base instance" do
17
- params = {}
18
- interactor = ActionInteractor::Base.execute!(params)
25
+ payload = {}
26
+ interactor = ActionInteractor::Base.execute!(payload)
19
27
  assert_instance_of(ActionInteractor::Base, interactor)
20
28
  end
21
29
 
22
30
  test "the result is an instance of ActionInteractor::Results" do
23
- params = {}
24
- interactor = ActionInteractor::Base.execute(params)
31
+ payload = {}
32
+ interactor = ActionInteractor::Base.execute(payload)
25
33
  assert_instance_of(ActionInteractor::Results, interactor.results)
26
34
  end
27
35
 
28
36
  test "#success? is true" do
29
- params = {}
30
- interactor = ActionInteractor::Base.execute(params)
37
+ payload = {}
38
+ interactor = ActionInteractor::Base.execute(payload)
31
39
  assert interactor.success?
32
40
  end
33
41
 
34
42
  test "#finished? is true" do
35
- params = {}
36
- interactor = ActionInteractor::Base.execute(params)
43
+ payload = {}
44
+ interactor = ActionInteractor::Base.execute(payload)
37
45
  assert interactor.finished?
38
46
  end
39
47
 
40
48
  test "#aborted? is true after #abort!" do
41
- params = {}
42
- interactor = ActionInteractor::Base.execute(params)
49
+ payload = {}
50
+ interactor = ActionInteractor::Base.execute(payload)
43
51
  interactor.abort!
44
52
  assert interactor.aborted?
45
53
  end
@@ -0,0 +1,35 @@
1
+ require "test/unit"
2
+ require_relative "../lib/actioninteractor"
3
+
4
+ class ErrorsTest < Test::Unit::TestCase
5
+ test "initialized correctly" do
6
+ assert_nothing_raised { ActionInteractor::Errors.new }
7
+ end
8
+
9
+ test "add result for the key" do
10
+ errors = ActionInteractor::Errors.new
11
+ errors.add(:foo, "bar")
12
+ assert_equal(errors[:foo], "bar")
13
+ end
14
+
15
+ test "delete the result for the key" do
16
+ errors = ActionInteractor::Errors.new
17
+ errors.add(:foo, "bar")
18
+ errors.delete(:foo)
19
+ assert_equal(errors[:foo], nil)
20
+ end
21
+
22
+ test "iterate through the errors" do
23
+ errors = ActionInteractor::Errors.new
24
+ errors.add(:foo, "foo")
25
+ errors.add(:bar, "bar")
26
+ errors.add(:baz, "baz")
27
+ info = []
28
+ errors.each do |attribute, result|
29
+ info << [attribute, result]
30
+ end
31
+ assert_equal(info[0], [:foo, "foo"])
32
+ assert_equal(info[1], [:bar, "bar"])
33
+ assert_equal(info[2], [:baz, "baz"])
34
+ end
35
+ end
@@ -4,24 +4,29 @@ require_relative "../lib/actioninteractor"
4
4
  class User
5
5
  attr_accessor :name
6
6
 
7
- def initialize(params)
8
- @name = params[:name]
7
+ def initialize(payload)
8
+ @name = payload[:name]
9
9
  end
10
10
  end
11
11
 
12
12
  class RegistrationInteractor < ActionInteractor::Base
13
13
  def execute
14
- return fail! unless params[:name]
15
- results.add(:user, User.new(name: params[:name]))
14
+ unless payload[:name]
15
+ errors.add(:name, "can't be blank.")
16
+ return fail!
17
+ end
18
+ results.add(:user, User.new(name: payload[:name]))
16
19
  success!
17
20
  end
18
21
  end
19
22
 
20
23
  class NotificationInteractor < ActionInteractor::Base
21
24
  def execute
22
- return fail! unless params[:name] || params[:email]
23
- results.add(:name, params[:name])
24
- results.add(:email, params[:email])
25
+ errors.add(:name, "can't be blank.") unless payload[:name]
26
+ errors.add(:email, "can't be blank.") unless payload[:email]
27
+ return fail! if errors.any?
28
+ results.add(:name, payload[:name])
29
+ results.add(:email, payload[:email])
25
30
  success!
26
31
  end
27
32
  end
@@ -29,82 +34,100 @@ end
29
34
 
30
35
  class InheritanceTest < Test::Unit::TestCase
31
36
  test ".execute does not raise error" do
32
- params = { name: 'John'}
33
- assert_nothing_raised { RegistrationInteractor.execute(params) }
37
+ payload = { name: 'John'}
38
+ assert_nothing_raised { RegistrationInteractor.execute(payload) }
34
39
  end
35
40
 
36
41
  test ".execute returns an RegistrationInteractor instance" do
37
- params = { name: 'John'}
38
- interactor = RegistrationInteractor.execute(params)
42
+ payload = { name: 'John'}
43
+ interactor = RegistrationInteractor.execute(payload)
39
44
  assert_instance_of(RegistrationInteractor, interactor)
40
45
  end
41
46
 
42
47
  test "the result contains a user" do
43
- params = { name: 'John'}
44
- interactor = RegistrationInteractor.execute(params)
48
+ payload = { name: 'John'}
49
+ interactor = RegistrationInteractor.execute(payload)
45
50
  assert_instance_of(User, interactor.results[:user])
46
51
  end
47
52
 
48
53
  test "the result user name is John" do
49
- params = { name: 'John'}
50
- interactor = RegistrationInteractor.execute(params)
54
+ payload = { name: 'John'}
55
+ interactor = RegistrationInteractor.execute(payload)
51
56
  user = interactor.results[:user]
52
57
  assert_equal(user.name, 'John')
53
58
  end
54
59
 
55
60
  test "#success? is true" do
56
- params = { name: 'John'}
57
- interactor = RegistrationInteractor.execute(params)
61
+ payload = { name: 'John'}
62
+ interactor = RegistrationInteractor.execute(payload)
58
63
  assert interactor.success?
59
64
  end
60
65
 
61
66
  test "#finished? is true" do
62
- params = { name: 'John'}
63
- interactor = RegistrationInteractor.execute(params)
67
+ payload = { name: 'John'}
68
+ interactor = RegistrationInteractor.execute(payload)
64
69
  assert interactor.finished?
65
70
  end
66
71
 
67
- test "if params is empty, .execute returns instance of RegistrationInteractor" do
68
- params = {}
69
- interactor = RegistrationInteractor.execute(params)
72
+ test "if payload is empty, .execute returns instance of RegistrationInteractor" do
73
+ payload = {}
74
+ interactor = RegistrationInteractor.execute(payload)
70
75
  assert_instance_of(RegistrationInteractor, interactor)
71
76
  end
72
77
 
73
- test "if params is empty, #execute! raises a ExecutionError" do
74
- params = {}
75
- interactor = RegistrationInteractor.new(params)
78
+ test "if payload is empty, #execute! raises a ExecutionError" do
79
+ payload = {}
80
+ interactor = RegistrationInteractor.new(payload)
76
81
  error = assert_raises ActionInteractor::ExecutionError do
77
82
  interactor.execute!
78
83
  end
79
84
  assert_equal "Failed to execute the interactor", error.message
80
85
  end
81
86
 
82
- test "if params is empty, #execute! raises a ExecutionError" do
83
- params = {}
84
- error = assert_raises ActionInteractor::ExecutionError do
85
- RegistrationInteractor.execute!(params)
86
- end
87
- assert_equal "Failed to execute the interactor", error.message
88
- end
89
-
90
- test "if params is empty, #failure? is true" do
91
- params = {}
92
- interactor = RegistrationInteractor.execute(params)
87
+ test "if payload is empty, #failure? is true" do
88
+ payload = {}
89
+ interactor = RegistrationInteractor.execute(payload)
93
90
  assert interactor.failure?
94
91
  end
95
92
 
96
- test "if params is empty, #finished? is true" do
97
- params = {}
98
- interactor = RegistrationInteractor.execute(params)
93
+ test "if payload is empty, #finished? is true" do
94
+ payload = {}
95
+ interactor = RegistrationInteractor.execute(payload)
99
96
  assert interactor.finished?
100
97
  end
101
98
 
102
99
  test "the result user name is Taro and email is taro@example.com" do
103
- params = { name: 'Taro', email: 'taro@example.com'}
104
- interactor = NotificationInteractor.execute(params)
100
+ payload = { name: 'Taro', email: 'taro@example.com'}
101
+ interactor = NotificationInteractor.execute(payload)
105
102
  name = interactor.results[:name]
106
103
  assert_equal(name, 'Taro')
107
104
  email = interactor.results[:email]
108
105
  assert_equal(email, 'taro@example.com')
109
106
  end
107
+
108
+ test "if none is given, the registration and notification will fail." do
109
+ payload = {}
110
+ registration = RegistrationInteractor.execute(payload)
111
+ assert registration.failure?
112
+ assert registration.errors.any?
113
+ assert_equal(registration.errors.to_hash, { name: "can't be blank." })
114
+ assert_equal(registration.errors.messages, ["name can't be blank."])
115
+ notification = NotificationInteractor.execute(payload)
116
+ assert notification.failure?
117
+ assert notification.errors.any?
118
+ assert_equal(notification.errors.to_hash, { name: "can't be blank.", email: "can't be blank." })
119
+ assert_equal(notification.errors.messages, ["name can't be blank.", "email can't be blank."])
120
+ end
121
+
122
+ test "if only the name is given, the registration is successful but the notification is not." do
123
+ payload = { name: 'Taro' }
124
+ registration = RegistrationInteractor.execute(payload)
125
+ assert registration.success?
126
+ assert registration.errors.empty?
127
+ notification = NotificationInteractor.execute(payload)
128
+ assert notification.failure?
129
+ assert notification.errors.any?
130
+ assert_equal(notification.errors.to_hash, { email: "can't be blank." })
131
+ assert_equal(notification.errors.messages, ["email can't be blank."])
132
+ end
110
133
  end
@@ -11,4 +11,34 @@ class ResultsTest < Test::Unit::TestCase
11
11
  results.add(:foo, "bar")
12
12
  assert_equal(results[:foo], "bar")
13
13
  end
14
+
15
+ test "delete the result for the key" do
16
+ results = ActionInteractor::Results.new
17
+ results.add(:foo, "bar")
18
+ results.delete(:foo)
19
+ assert_equal(results[:foo], nil)
20
+ end
21
+
22
+ test "iterate through the results" do
23
+ results = ActionInteractor::Results.new
24
+ results.add(:foo, "foo")
25
+ results.add(:bar, "bar")
26
+ results.add(:baz, "baz")
27
+ info = []
28
+ results.each do |attribute, result|
29
+ info << [attribute, result]
30
+ end
31
+ assert_equal(info[0], [:foo, "foo"])
32
+ assert_equal(info[1], [:bar, "bar"])
33
+ assert_equal(info[2], [:baz, "baz"])
34
+ end
35
+
36
+ test "get result by the key name's method" do
37
+ results = ActionInteractor::Results.new
38
+ assert_raises ::NoMethodError do
39
+ results.foo
40
+ end
41
+ results.add(:foo, "bar")
42
+ assert_equal(results.foo, "bar")
43
+ end
14
44
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actioninteractor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.17.3
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryo Hashimoto
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-25 00:00:00.000000000 Z
11
+ date: 2020-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: test-unit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '3.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '3.3'
41
55
  description: Action Interactor provides a simple interface for performing operations
42
56
  like Service Object / Command pattern.
43
57
  email: ryohashimoto@gmail.com
@@ -48,9 +62,11 @@ files:
48
62
  - Rakefile
49
63
  - actioninteractor.gemspec
50
64
  - lib/action_interactor/base.rb
65
+ - lib/action_interactor/errors.rb
51
66
  - lib/action_interactor/results.rb
52
67
  - lib/actioninteractor.rb
53
68
  - test/base_test.rb
69
+ - test/errors_test.rb
54
70
  - test/inheritance_test.rb
55
71
  - test/results_test.rb
56
72
  homepage: https://github.com/ryohashimoto/lightrails
@@ -79,5 +95,6 @@ summary: Action Interactor provides a simple interface for performing operations
79
95
  of Lightrails).
80
96
  test_files:
81
97
  - test/base_test.rb
98
+ - test/errors_test.rb
82
99
  - test/inheritance_test.rb
83
100
  - test/results_test.rb