actioninteractor 0.0.17.3 → 0.1.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 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