easy_command 1.0.0.pre.rc1
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 +7 -0
- data/.github/CODEOWNERS +5 -0
- data/.github/workflows/ci.yaml +52 -0
- data/.github/workflows/lint.yaml +38 -0
- data/.github/workflows/release.yml +43 -0
- data/.gitignore +14 -0
- data/.release-please-manifest.json +3 -0
- data/.rspec +1 -0
- data/.rubocop.yml +14 -0
- data/.rubocop_maintainer_style.yml +34 -0
- data/.rubocop_style.yml +142 -0
- data/.rubocop_todo.yml +453 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +89 -0
- data/Gemfile +19 -0
- data/LICENSE.txt +22 -0
- data/README.md +736 -0
- data/easy_command.gemspec +26 -0
- data/lib/easy_command/chainable.rb +16 -0
- data/lib/easy_command/errors.rb +85 -0
- data/lib/easy_command/result.rb +53 -0
- data/lib/easy_command/ruby-2-7-specific.rb +49 -0
- data/lib/easy_command/ruby-2-specific.rb +53 -0
- data/lib/easy_command/ruby-3-specific.rb +49 -0
- data/lib/easy_command/spec_helpers/command_matchers.rb +89 -0
- data/lib/easy_command/spec_helpers/mock_command_helper.rb +89 -0
- data/lib/easy_command/spec_helpers.rb +2 -0
- data/lib/easy_command/version.rb +3 -0
- data/lib/easy_command.rb +94 -0
- data/locales/en.yml +2 -0
- data/release-please-config.json +11 -0
- data/spec/easy_command/errors_spec.rb +121 -0
- data/spec/easy_command/result_spec.rb +176 -0
- data/spec/easy_command_spec.rb +298 -0
- data/spec/factories/addition_command.rb +12 -0
- data/spec/factories/callback_command.rb +20 -0
- data/spec/factories/failure_command.rb +12 -0
- data/spec/factories/missed_call_command.rb +7 -0
- data/spec/factories/multiplication_command.rb +12 -0
- data/spec/factories/sub_command.rb +19 -0
- data/spec/factories/subcommand_command.rb +14 -0
- data/spec/factories/success_command.rb +11 -0
- data/spec/spec_helper.rb +16 -0
- metadata +102 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
describe EasyCommand::Errors do
|
6
|
+
let(:errors) { described_class.new }
|
7
|
+
|
8
|
+
describe '#add' do
|
9
|
+
it 'adds the error' do
|
10
|
+
errors.add :attribute, :some_error, 'some error description'
|
11
|
+
expect(errors[:attribute]).to eq([{ code: :some_error, message: 'some error description' }])
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'adds the same error only once' do
|
15
|
+
errors.add :attribute, :some_error, 'some error description'
|
16
|
+
errors.add :attribute, :some_error, 'some error description'
|
17
|
+
|
18
|
+
expect(errors[:attribute]).to eq([{ code: :some_error, message: 'some error description' }])
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'tries to localize the error message if possible' do
|
22
|
+
expect(I18n).to receive(:t!).with(:bad_post_code, anything).and_return("Very bad post code")
|
23
|
+
|
24
|
+
errors.add :address, :invalid, :bad_post_code
|
25
|
+
expect(errors[:address]).to eq([{ code: :invalid, message: "Very bad post code" }])
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'symbolizes the code' do
|
29
|
+
errors.add :attribute, 'some_error', 'some error description'
|
30
|
+
expect(errors[:attribute]).to eq([{ code: :some_error, message: 'some error description' }])
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when the errors are for a i18n-scoped class' do
|
34
|
+
let(:scoped_klass) { Class.new { prepend EasyCommand }.tap { |c| c.i18n_scope = 'my.custom.scope' } }
|
35
|
+
let(:errors) { described_class.new(source: scoped_klass) }
|
36
|
+
|
37
|
+
it "takes the scope into account for localization" do
|
38
|
+
allow(I18n).to receive(:t!).with(:bad_post_code, anything).
|
39
|
+
and_return("Bad error message")
|
40
|
+
expect(I18n).to receive(:t!).with(:bad_post_code, hash_including(scope: 'my.custom.scope')).
|
41
|
+
and_return("Correct error message")
|
42
|
+
|
43
|
+
errors.add :address, :invalid, :bad_post_code
|
44
|
+
expect(errors[:address]).to eq([{ code: :invalid, message: "Correct error message" }])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#exists?' do
|
50
|
+
it "indicates if the attribute has a specific error", :aggregate_failures do
|
51
|
+
expect(errors.exists?(:attribute, :some_error)).to eq(false)
|
52
|
+
errors.add(:attribute, :some_error, 'some message')
|
53
|
+
expect(errors.exists?(:attribute, :some_error)).to eq(true)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe '#merge_from' do
|
58
|
+
# rubocop:disable Style/OpenStructUse
|
59
|
+
# We use it to quickly-mock objects, it's specs-only, that's fine
|
60
|
+
|
61
|
+
it 'can import errors from object with similar error sets' do
|
62
|
+
commandlike_object = OpenStruct.new(errors: described_class.new)
|
63
|
+
commandlike_object.errors.add(:name, :bad_name, "Bad name!")
|
64
|
+
|
65
|
+
errors.merge_from(commandlike_object)
|
66
|
+
|
67
|
+
expect(errors).to have_key(:name)
|
68
|
+
expect(errors[:name]).to include(code: :bad_name, message: "Bad name!")
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'can import errors from any object responding to errors.details and errors.messages' do
|
72
|
+
recordlike_object = OpenStruct.new(errors: OpenStruct.new(messages: {}, details: {}))
|
73
|
+
recordlike_object.errors.messages[:name] = ["Bad name!"]
|
74
|
+
recordlike_object.errors.details[:name] = [{ error: :bad_name }]
|
75
|
+
|
76
|
+
errors.merge_from(recordlike_object)
|
77
|
+
|
78
|
+
expect(errors).to have_key(:name)
|
79
|
+
expect(errors[:name]).to include(code: :bad_name, message: "Bad name!")
|
80
|
+
end
|
81
|
+
# rubocop:enable Style/OpenStructUse
|
82
|
+
end
|
83
|
+
|
84
|
+
describe '#add_multiple_errors' do
|
85
|
+
let(:errors_list) do
|
86
|
+
{
|
87
|
+
attribute_a: [{ code: :some_error, message: 'some error description' }],
|
88
|
+
attribute_b: [{ code: :another_error, message: 'another error description' }],
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
before do
|
93
|
+
errors.add_multiple_errors errors_list
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'populates itself with the added errors' do
|
97
|
+
expect(errors[:attribute_a]).to eq(errors_list[:attribute_a])
|
98
|
+
expect(errors[:attribute_b]).to eq(errors_list[:attribute_b])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe '#full_messages' do
|
103
|
+
let(:errors_list) do
|
104
|
+
{
|
105
|
+
attribute_a: [
|
106
|
+
{ code: :some_error, message: 'some error description' },
|
107
|
+
{ code: :another_error, message: 'another error description' },
|
108
|
+
],
|
109
|
+
attribute_b: [{ code: :another_error, message: 'another error description' }],
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
before do
|
114
|
+
errors.add_multiple_errors errors_list
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'returns a messages array' do
|
118
|
+
expect(errors.full_messages).to eq(["Attribute_a some error description", "Attribute_a another error description", "Attribute_b another error description"])
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe EasyCommand::Result do
|
4
|
+
let(:monad) { described_class.new(5) }
|
5
|
+
|
6
|
+
it "carries a result" do
|
7
|
+
expect(monad.result).to eq(5)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "default" do
|
11
|
+
it "is a success" do
|
12
|
+
expect(monad.success?).to eq(true)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "isn't a failure" do
|
16
|
+
expect(monad.failure?).to eq(false)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "has no errors" do
|
20
|
+
expect(monad.errors).to be_empty
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe EasyCommand::Success do
|
25
|
+
let(:monad) { described_class.new(5) }
|
26
|
+
|
27
|
+
it "is a success" do
|
28
|
+
expect(monad.success?).to eq(true)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "isn't a failure" do
|
32
|
+
expect(monad.failure?).to eq(false)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "has no errors" do
|
36
|
+
expect(monad.errors).to be_empty
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe EasyCommand::Params do
|
41
|
+
let(:monad) { described_class.new(5) }
|
42
|
+
|
43
|
+
it "is a success" do
|
44
|
+
expect(monad.success?).to eq(true)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "isn't a failure" do
|
48
|
+
expect(monad.failure?).to eq(false)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "has no errors" do
|
52
|
+
expect(monad.errors).to be_empty
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe EasyCommand::Failure do
|
57
|
+
let(:monad) { described_class.new(5) }
|
58
|
+
|
59
|
+
it "isn't a success" do
|
60
|
+
expect(monad.success?).to eq(false)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "is a failure" do
|
64
|
+
expect(monad.failure?).to eq(true)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "can be instantiated with errors" do
|
68
|
+
errors = EasyCommand::Errors.new
|
69
|
+
errors.add(:attribute, :error)
|
70
|
+
monad = described_class.new(nil).with_errors(errors)
|
71
|
+
expect(monad.errors).to have_error(:attribute, :error)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "callbacks" do
|
76
|
+
describe "#on_success" do
|
77
|
+
context "when calld on a Success monads" do
|
78
|
+
let(:monad) { EasyCommand::Success.new(5) }
|
79
|
+
|
80
|
+
it "executes the block" do
|
81
|
+
block_executed = false
|
82
|
+
monad.on_success do
|
83
|
+
block_executed = true
|
84
|
+
end
|
85
|
+
expect(block_executed).to eq true
|
86
|
+
end
|
87
|
+
|
88
|
+
it "passes the content of the monad to the block" do
|
89
|
+
monad.on_success do |arg|
|
90
|
+
expect(arg).to eq(5)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "when called on a Failure monads" do
|
96
|
+
let(:monad) { EasyCommand::Failure.new(5) }
|
97
|
+
|
98
|
+
it "doesn't execute the block" do
|
99
|
+
block_executed = false
|
100
|
+
monad.on_success do
|
101
|
+
block_executed = true
|
102
|
+
end
|
103
|
+
expect(block_executed).to eq false
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "#on_failure" do
|
109
|
+
context "when called on a Success monads" do
|
110
|
+
let(:monad) { EasyCommand::Success.new(5) }
|
111
|
+
|
112
|
+
it "doesn't execute the block" do
|
113
|
+
block_executed = false
|
114
|
+
monad.on_failure do
|
115
|
+
block_executed = true
|
116
|
+
end
|
117
|
+
expect(block_executed).to eq false
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context "when called on a Failure monads" do
|
122
|
+
let(:errors) { EasyCommand::Errors.new.tap { |err| err.add :attribute, :code } }
|
123
|
+
let(:monad) { EasyCommand::Failure.new(5).with_errors(errors) }
|
124
|
+
|
125
|
+
it "executes the block" do
|
126
|
+
block_executed = false
|
127
|
+
monad.on_failure do
|
128
|
+
block_executed = true
|
129
|
+
end
|
130
|
+
expect(block_executed).to eq true
|
131
|
+
end
|
132
|
+
|
133
|
+
it "passes the content of the monad to the block" do
|
134
|
+
monad.on_failure do |arg|
|
135
|
+
expect(arg).to eq(errors)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "chaining mechanism" do
|
143
|
+
let(:callable_object) do
|
144
|
+
Class.new { def self.call(*); :toto end }
|
145
|
+
end
|
146
|
+
|
147
|
+
it "calls the chained object" do
|
148
|
+
expect(callable_object).to receive(:call)
|
149
|
+
monad.then(callable_object)
|
150
|
+
end
|
151
|
+
|
152
|
+
it "passes its own result to the call" do
|
153
|
+
allow(callable_object).to receive(:call) do |arg|
|
154
|
+
expect(arg).to eq 5
|
155
|
+
end
|
156
|
+
monad.then(callable_object)
|
157
|
+
end
|
158
|
+
|
159
|
+
it "returns the result of the call" do
|
160
|
+
expect(monad.then(callable_object)).to eq(:toto)
|
161
|
+
end
|
162
|
+
|
163
|
+
context "when it's a failure" do
|
164
|
+
let(:monad) { EasyCommand::Failure.new(nil) }
|
165
|
+
|
166
|
+
it "doesn't call the chained object" do
|
167
|
+
expect(callable_object).not_to receive(:call)
|
168
|
+
monad.then(callable_object)
|
169
|
+
end
|
170
|
+
|
171
|
+
it "returns itself" do
|
172
|
+
expect(monad.then(callable_object)).to eq(monad)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,298 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EasyCommand do
|
4
|
+
let(:command) { SuccessCommand.new(2) }
|
5
|
+
|
6
|
+
describe ".call" do
|
7
|
+
before do
|
8
|
+
allow(SuccessCommand).to receive(:new).and_return(command)
|
9
|
+
allow(command).to receive(:call)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "initializes the command" do
|
13
|
+
expect(SuccessCommand).to receive(:new)
|
14
|
+
|
15
|
+
SuccessCommand.call 2
|
16
|
+
end
|
17
|
+
|
18
|
+
it "calls #call method" do
|
19
|
+
expect(command).to receive(:call)
|
20
|
+
|
21
|
+
SuccessCommand.call 2
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#call" do
|
26
|
+
let(:missed_call_command) { MissedCallCommand.new(2) }
|
27
|
+
|
28
|
+
it "returns a Success" do
|
29
|
+
expect(command.call).to be_a(EasyCommand::Success)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "raises an exception if the method is not defined in the command" do
|
33
|
+
expect { missed_call_command.call }.to raise_error(EasyCommand::NotImplementedError)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "returns a Failure if something went wrong" do
|
37
|
+
command.errors.add(:some_error, 'some message')
|
38
|
+
expect(command.call).to be_a(EasyCommand::Failure)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#abort' do
|
43
|
+
let(:aborting_command) {
|
44
|
+
Class.new do
|
45
|
+
prepend EasyCommand
|
46
|
+
|
47
|
+
def call
|
48
|
+
abort :base, :some_error, 'Error message', result: 3
|
49
|
+
raise "We shouldn't reach this" # rubocop:disable Lint/UnreachableCode
|
50
|
+
end
|
51
|
+
end
|
52
|
+
}
|
53
|
+
|
54
|
+
it "stops the execution as soon as it's called" do
|
55
|
+
expect { aborting_command.call }.not_to raise_error
|
56
|
+
end
|
57
|
+
|
58
|
+
it "stops add the error passed as args" do
|
59
|
+
expect(aborting_command.call.errors).to have_error(:base, :some_error)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "stops let Failure carry the result" do
|
63
|
+
expect(aborting_command.call.result).to eq(3)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '#assert' do
|
68
|
+
let(:asserting_command) {
|
69
|
+
Class.new do
|
70
|
+
prepend EasyCommand
|
71
|
+
|
72
|
+
def initialize(should_error, with_result: false)
|
73
|
+
@should_error = should_error
|
74
|
+
@with_result = with_result
|
75
|
+
end
|
76
|
+
|
77
|
+
def call
|
78
|
+
if @with_result
|
79
|
+
assert potential_error, result: 4
|
80
|
+
else
|
81
|
+
assert potential_error
|
82
|
+
end
|
83
|
+
raise "We shouldn't reach this"
|
84
|
+
end
|
85
|
+
|
86
|
+
def potential_error
|
87
|
+
if @should_error
|
88
|
+
errors.add :base, :error1
|
89
|
+
errors.add :base, :error2
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
}
|
94
|
+
|
95
|
+
it "stops the execution as soon as it's called" do
|
96
|
+
expect { asserting_command.call(true) }.not_to raise_error
|
97
|
+
end
|
98
|
+
|
99
|
+
it "adds the error passed as args" do
|
100
|
+
expect(asserting_command.call(true).errors).
|
101
|
+
to have_error(:base, :error1).
|
102
|
+
and have_error(:base, :error2)
|
103
|
+
end
|
104
|
+
|
105
|
+
context "with a result along side the error" do
|
106
|
+
it "lets Failure carry the result" do
|
107
|
+
expect(asserting_command.call(true, with_result: true).result).to eq(4)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "#success?" do
|
113
|
+
it "is true by default" do
|
114
|
+
expect(command.call).to be_success
|
115
|
+
end
|
116
|
+
|
117
|
+
it "is false if something went wrong" do
|
118
|
+
command.errors.add(:some_error, 'some message')
|
119
|
+
expect(command.call).not_to be_success
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "#result" do
|
124
|
+
it "returns the result of command execution" do
|
125
|
+
expect(command.call.result).to eq(4)
|
126
|
+
end
|
127
|
+
|
128
|
+
context "when call is not called yet" do
|
129
|
+
it "returns nil" do
|
130
|
+
expect(command.result).to be_nil
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context "when command fails" do
|
135
|
+
let(:command) { FailureCommand.new(2) }
|
136
|
+
|
137
|
+
it "still returns the result" do
|
138
|
+
expect(command.call.result).to eq(4)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "#failure?" do
|
144
|
+
it "is false by default" do
|
145
|
+
expect(command.call).not_to be_failure
|
146
|
+
end
|
147
|
+
|
148
|
+
it "is true if something went wrong" do
|
149
|
+
command.errors.add(:some_error, 'some message')
|
150
|
+
expect(command.call).to be_failure
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe "#errors" do
|
155
|
+
it "returns an EasyCommand::Errors" do
|
156
|
+
expect(command.errors).to be_a(EasyCommand::Errors)
|
157
|
+
end
|
158
|
+
|
159
|
+
context "with no errors" do
|
160
|
+
it "is empty" do
|
161
|
+
expect(command.errors).to be_empty
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
context "with errors" do
|
166
|
+
before do
|
167
|
+
command.errors.add(:attribute, :some_error, 'some message')
|
168
|
+
end
|
169
|
+
|
170
|
+
it "has a key with error message" do
|
171
|
+
expect(command.errors[:attribute]).to eq([{ code: :some_error, message: 'some message' }])
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
describe "#has_error?" do
|
177
|
+
it "indicates if the command has an error", :aggregate_failures do
|
178
|
+
expect(command.has_error?(:attribute, :some_error)).to eq(false)
|
179
|
+
command.errors.add(:attribute, :some_error, 'some message')
|
180
|
+
expect(command.has_error?(:attribute, :some_error)).to eq(true)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe ".i18n_scope" do
|
185
|
+
after do
|
186
|
+
# Resetting to prevent leaks across specs
|
187
|
+
command.class.i18n_scope = 'errors.messages'
|
188
|
+
end
|
189
|
+
|
190
|
+
it "has a default value of 'errors.messages'" do
|
191
|
+
expect(command.class.i18n_scope).to eq('errors.messages')
|
192
|
+
end
|
193
|
+
|
194
|
+
it "can be overriden" do
|
195
|
+
command.class.i18n_scope = 'errors.new_scope'
|
196
|
+
expect(command.class.i18n_scope).to eq('errors.new_scope')
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
describe "#on_success" do
|
201
|
+
let(:command) { CallbackCommand.new(add_error: false) }
|
202
|
+
|
203
|
+
before do
|
204
|
+
allow(command).to receive(:call).and_call_original
|
205
|
+
allow(command).to receive(:on_success).and_call_original
|
206
|
+
end
|
207
|
+
|
208
|
+
it "is executed after #call" do
|
209
|
+
expect(command).to receive(:call).ordered
|
210
|
+
expect(command).to receive(:on_success).ordered
|
211
|
+
command.call
|
212
|
+
end
|
213
|
+
|
214
|
+
context "when there are errors" do
|
215
|
+
let(:command) { CallbackCommand.new(add_error: true) }
|
216
|
+
|
217
|
+
it "does not call #on_success" do
|
218
|
+
expect(command).to receive(:call).ordered
|
219
|
+
expect(command).not_to receive(:on_success)
|
220
|
+
command.call
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
context "when using sub command" do
|
225
|
+
let(:command) { CallbackCommand.new(add_error: false, with_subcommand: true) }
|
226
|
+
let(:subcommand) { SubCommand.new(add_error: false) }
|
227
|
+
|
228
|
+
before do
|
229
|
+
allow(SubCommand).to receive(:new).and_return(subcommand)
|
230
|
+
allow(subcommand).to receive(:on_success).and_call_original
|
231
|
+
allow(subcommand).to receive(:code_execution).and_call_original
|
232
|
+
|
233
|
+
allow(command).to receive(:code_execution).and_call_original
|
234
|
+
end
|
235
|
+
|
236
|
+
specify "#on_success are called in order" do
|
237
|
+
expect(command).to receive(:on_success).ordered
|
238
|
+
expect(subcommand).to receive(:on_success).ordered
|
239
|
+
command.call
|
240
|
+
end
|
241
|
+
|
242
|
+
specify "Sub command #on_success code are executed before parent" do
|
243
|
+
expect(subcommand).to receive(:code_execution).ordered
|
244
|
+
expect(command).to receive(:code_execution).ordered
|
245
|
+
command.call
|
246
|
+
end
|
247
|
+
|
248
|
+
context "when parent has errors" do
|
249
|
+
let(:command) { CallbackCommand.new(add_error: true) }
|
250
|
+
|
251
|
+
it "does not call #on_success neither for parent nor for children" do
|
252
|
+
expect(command).not_to receive(:on_success)
|
253
|
+
expect(subcommand).not_to receive(:on_success)
|
254
|
+
command.call
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
context "when subcommand has error" do
|
259
|
+
let(:subcommand) { SubCommand.new(add_error: true) }
|
260
|
+
|
261
|
+
it "does not call #on_success neither for parents nor for children" do
|
262
|
+
command.call
|
263
|
+
expect(command).not_to receive(:on_success)
|
264
|
+
expect(subcommand).not_to receive(:on_success)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
describe "Subcommmand mechanism" do
|
271
|
+
it "can call other commands", :aggregate_failures do
|
272
|
+
expect(AdditionCommand).to receive(:new).and_call_original
|
273
|
+
expect(MultiplicationCommand).to receive(:new).and_call_original
|
274
|
+
expect(AddThenMultiplyCommand.call(2, 3, 4).result).to eq(20)
|
275
|
+
end
|
276
|
+
|
277
|
+
context "when the first command fails" do
|
278
|
+
before do
|
279
|
+
double_addition = AdditionCommand.new(2, 3)
|
280
|
+
allow(AdditionCommand).to receive(:new).and_return(double_addition)
|
281
|
+
allow(double_addition).to receive(:call).and_wrap_original do |m, *|
|
282
|
+
double_addition.errors.add(:addition, :failed)
|
283
|
+
m.call
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
it "merges the errors from the failing command in the calling command" do
|
288
|
+
result = AddThenMultiplyCommand.call(2, 3, 4)
|
289
|
+
expect(result.errors).to have_error(:addition, :failed)
|
290
|
+
end
|
291
|
+
|
292
|
+
it "stops the flow" do
|
293
|
+
expect(MultiplicationCommand).not_to receive(:new)
|
294
|
+
AddThenMultiplyCommand.call(2, 3, 4)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class CallbackCommand
|
2
|
+
prepend EasyCommand
|
3
|
+
|
4
|
+
def initialize(add_error: false, with_subcommand: false)
|
5
|
+
@add_error = add_error
|
6
|
+
@with_subcommand = with_subcommand
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
assert_subcommand(SubCommand) if @with_subcommand
|
11
|
+
errors.add(:something, :forbidden) if @add_error
|
12
|
+
end
|
13
|
+
|
14
|
+
def on_success
|
15
|
+
code_execution __method__
|
16
|
+
end
|
17
|
+
|
18
|
+
def code_execution(method)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class SubCommand
|
2
|
+
prepend EasyCommand
|
3
|
+
|
4
|
+
def initialize(add_error: false)
|
5
|
+
@add_error = add_error
|
6
|
+
@execution_orders = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
errors.add(:something, :forbidden) if @add_error
|
11
|
+
end
|
12
|
+
|
13
|
+
def on_success
|
14
|
+
code_execution __method__
|
15
|
+
end
|
16
|
+
|
17
|
+
def code_execution(method)
|
18
|
+
end
|
19
|
+
end
|