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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.github/CODEOWNERS +5 -0
  3. data/.github/workflows/ci.yaml +52 -0
  4. data/.github/workflows/lint.yaml +38 -0
  5. data/.github/workflows/release.yml +43 -0
  6. data/.gitignore +14 -0
  7. data/.release-please-manifest.json +3 -0
  8. data/.rspec +1 -0
  9. data/.rubocop.yml +14 -0
  10. data/.rubocop_maintainer_style.yml +34 -0
  11. data/.rubocop_style.yml +142 -0
  12. data/.rubocop_todo.yml +453 -0
  13. data/.ruby-version +1 -0
  14. data/CHANGELOG.md +89 -0
  15. data/Gemfile +19 -0
  16. data/LICENSE.txt +22 -0
  17. data/README.md +736 -0
  18. data/easy_command.gemspec +26 -0
  19. data/lib/easy_command/chainable.rb +16 -0
  20. data/lib/easy_command/errors.rb +85 -0
  21. data/lib/easy_command/result.rb +53 -0
  22. data/lib/easy_command/ruby-2-7-specific.rb +49 -0
  23. data/lib/easy_command/ruby-2-specific.rb +53 -0
  24. data/lib/easy_command/ruby-3-specific.rb +49 -0
  25. data/lib/easy_command/spec_helpers/command_matchers.rb +89 -0
  26. data/lib/easy_command/spec_helpers/mock_command_helper.rb +89 -0
  27. data/lib/easy_command/spec_helpers.rb +2 -0
  28. data/lib/easy_command/version.rb +3 -0
  29. data/lib/easy_command.rb +94 -0
  30. data/locales/en.yml +2 -0
  31. data/release-please-config.json +11 -0
  32. data/spec/easy_command/errors_spec.rb +121 -0
  33. data/spec/easy_command/result_spec.rb +176 -0
  34. data/spec/easy_command_spec.rb +298 -0
  35. data/spec/factories/addition_command.rb +12 -0
  36. data/spec/factories/callback_command.rb +20 -0
  37. data/spec/factories/failure_command.rb +12 -0
  38. data/spec/factories/missed_call_command.rb +7 -0
  39. data/spec/factories/multiplication_command.rb +12 -0
  40. data/spec/factories/sub_command.rb +19 -0
  41. data/spec/factories/subcommand_command.rb +14 -0
  42. data/spec/factories/success_command.rb +11 -0
  43. data/spec/spec_helper.rb +16 -0
  44. 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,12 @@
1
+ class AdditionCommand
2
+ prepend EasyCommand
3
+
4
+ def call
5
+ @a + @b
6
+ end
7
+
8
+ def initialize(a,b)
9
+ @a = a
10
+ @b = b
11
+ end
12
+ 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,12 @@
1
+ class FailureCommand
2
+ prepend EasyCommand
3
+
4
+ def initialize(input)
5
+ @input = input
6
+ end
7
+
8
+ def call
9
+ errors.add(:base, :some_error, 'Error message')
10
+ @input * 2
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ class MissedCallCommand
2
+ prepend EasyCommand
3
+
4
+ def initialize(input)
5
+ @input = input
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ class MultiplicationCommand
2
+ prepend EasyCommand
3
+
4
+ def call
5
+ @a * @b
6
+ end
7
+
8
+ def initialize(a,b)
9
+ @a = a
10
+ @b = b
11
+ end
12
+ 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
@@ -0,0 +1,14 @@
1
+ class AddThenMultiplyCommand
2
+ prepend EasyCommand
3
+
4
+ def call
5
+ sum = assert_subcommand AdditionCommand, @a, @b
6
+ assert_subcommand MultiplicationCommand, sum, @c
7
+ end
8
+
9
+ def initialize(a,b,c)
10
+ @a = a
11
+ @b = b
12
+ @c = c
13
+ end
14
+ end