Skip to content

Commit 927cb23

Browse files
committed
feat(steps): allow to redefine original methods by outputs
1 parent be610b6 commit 927cb23

File tree

14 files changed

+191
-95
lines changed

14 files changed

+191
-95
lines changed

lib/convenient_service/service/plugins/can_have_steps/entities/method/commands/define_method_in_container.rb

+42-5
Original file line numberDiff line numberDiff line change
@@ -51,22 +51,59 @@ def initialize(method:, container:, index:)
5151
end
5252

5353
##
54-
# @return [Boolean]
54+
# @return [Boolean] true if method is just defined, false if already defined.
5555
#
5656
def call
57+
container.mutex.synchronize do
58+
return false if has_defined_method?
59+
60+
define_method
61+
62+
true
63+
end
64+
end
65+
66+
private
67+
68+
##
69+
# @return [void]
70+
#
71+
def define_method
72+
container.klass.alias_method name_before_out_redefinition.to_s, name.to_s if container.has_defined_method?(name)
73+
5774
<<~RUBY.tap { |code| container.klass.class_eval(code, __FILE__, __LINE__ + 1) }
5875
##
5976
# @return [Object] Can be any type.
6077
# @raise [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Method::Exceptions::OutMethodStepIsNotCompleted]
6178
#
79+
# @internal
80+
# TODO: Direct specs.
81+
#
6282
def #{name}
63-
internals.cache.scope(:step_output_values).fetch(__method__) do
64-
::ConvenientService.raise ::ConvenientService::Service::Plugins::CanHaveSteps::Entities::Method::Exceptions::OutMethodStepIsNotCompleted.new(method_name: __method__)
65-
end
83+
return internals.cache.scope(:step_output_values).read(__method__) if internals.cache.scope(:step_output_values).exist?(__method__)
84+
85+
return #{name}_before_out_redefinition if respond_to?(:#{name}_before_out_redefinition)
86+
87+
::ConvenientService.raise ::ConvenientService::Service::Plugins::CanHaveSteps::Entities::Method::Exceptions::OutMethodStepIsNotCompleted.new(method_name: __method__)
6688
end
6789
RUBY
90+
end
91+
92+
##
93+
# @return [String]
94+
#
95+
def name_before_out_redefinition
96+
"#{name}_before_out_redefinition"
97+
end
98+
99+
##
100+
# @return [Boolean]
101+
#
102+
def has_defined_method?
103+
return false unless container.has_defined_method?(name)
104+
return true if container.has_defined_method?(name_before_out_redefinition)
68105

69-
true
106+
container.klass.instance_method(name.to_s).source_location.first.include?(__FILE__)
70107
end
71108
end
72109
end

lib/convenient_service/service/plugins/can_have_steps/entities/method/concern/instance_methods.rb

+4-3
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,15 @@ def has_organizer?
8383
##
8484
# @param container [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Service]
8585
# @param index [Integer]
86-
# @return [void]
86+
# @return [Boolean] true if method is just defined, false if already defined.
87+
#
88+
# @internal
89+
# TODO: Split input and output methods into separate classes.
8790
#
8891
def define_output_in_container!(container, index:)
8992
direction.define_output_in_container!(container, index: index, method: self)
9093

9194
caller.define_output_in_container!(container, index: index, method: self)
92-
93-
true
9495
end
9596

9697
##

lib/convenient_service/service/plugins/can_have_steps/entities/method/entities/callers/proc.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def calculate_value(method)
1414
end
1515

1616
def define_output_in_container!(container, index:, method:)
17-
true
17+
false
1818
end
1919

2020
private

lib/convenient_service/service/plugins/can_have_steps/entities/method/entities/callers/raw.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def calculate_value(method)
1414
end
1515

1616
def define_output_in_container!(container, index:, method:)
17-
true
17+
false
1818
end
1919

2020
private

lib/convenient_service/service/plugins/can_have_steps/entities/method/entities/directions/output.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ module Entities
1010
module Directions
1111
class Output < Base
1212
def define_output_in_container!(container, index:, method:)
13-
true
13+
false
1414
end
1515
end
1616
end

lib/convenient_service/service/plugins/can_have_steps/entities/service/concern/instance_methods.rb

+7
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ def initialize(klass)
3333
@klass = klass
3434
end
3535

36+
##
37+
# @return [Mutex]
38+
#
39+
def mutex
40+
klass.__convenient_service_config__.mutex
41+
end
42+
3643
##
3744
# @param method [String, Symbol]
3845
# @return [Boolean]

spec/lib/convenient_service/service/plugins/can_have_steps/entities/method/commands/define_method_in_container_spec.rb

+103-75
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,6 @@
1010

1111
include ConvenientService::RSpec::Matchers::DelegateTo
1212

13-
let(:container) do
14-
Class.new.tap do |klass|
15-
klass.class_exec(first_step_service) do |first_step_service|
16-
include ConvenientService::Standard::Config
17-
18-
step first_step_service, out: :foo
19-
end
20-
end
21-
end
22-
2313
let(:organizer) { container.new }
2414

2515
let(:first_step_service) do
@@ -39,110 +29,148 @@ def result
3929
describe ".call" do
4030
subject(:command_result) { described_class.call(method: method, container: step.container, index: step.index) }
4131

42-
it "returns `true`" do
43-
expect(command_result).to eq(true)
44-
end
32+
context "when `method` is NOT defined in container" do
33+
let(:container) do
34+
Class.new.tap do |klass|
35+
klass.class_exec(first_step_service) do |first_step_service|
36+
include ConvenientService::Standard::Config
4537

46-
it "defines `method` in `container`" do
47-
expect { command_result }.to change { ConvenientService::Utils::Method.defined?(method.name.to_s, step.container.klass, private: true) }.from(false).to(true)
48-
end
38+
step first_step_service, out: :foo
39+
end
40+
end
41+
end
4942

50-
example_group "generated method" do
51-
before do
52-
command_result
43+
it "returns `true`" do
44+
expect(command_result).to eq(true)
5345
end
5446

55-
context "when corresponding step is NOT completed" do
56-
let(:exception_message) do
57-
<<~TEXT
58-
`out` method `#{method}` is called before its corresponding step is completed.
47+
it "defines `method` in `container`" do
48+
expect { command_result }.to change { ConvenientService::Utils::Method.defined?(method.name.to_s, step.container.klass, private: true) }.from(false).to(true)
49+
end
5950

60-
Maybe it makes sense to change steps order?
61-
TEXT
51+
example_group "generated method" do
52+
before do
53+
command_result
6254
end
6355

64-
it "raises `ConvenientService::Service::Plugins::CanHaveSteps::Entities::Method::Exceptions::OutMethodStepIsNotCompleted`" do
65-
expect { organizer.foo }
66-
.to raise_error(ConvenientService::Service::Plugins::CanHaveSteps::Entities::Method::Exceptions::OutMethodStepIsNotCompleted)
67-
.with_message(exception_message)
68-
end
56+
context "when corresponding step is NOT completed" do
57+
let(:exception_message) do
58+
<<~TEXT
59+
`out` method `#{method}` is called before its corresponding step is completed.
6960
70-
specify do
71-
expect { ignoring_exception(ConvenientService::Service::Plugins::CanHaveSteps::Entities::Method::Exceptions::OutMethodStepIsNotCompleted) { organizer.foo } }
72-
.to delegate_to(ConvenientService, :raise)
73-
end
74-
end
61+
Maybe it makes sense to change steps order?
62+
TEXT
63+
end
7564

76-
context "when corresponding step is completed" do
77-
before do
78-
##
79-
# NOTE: Completes the step.
80-
#
81-
organizer.steps[0].save_outputs_in_organizer!
82-
end
65+
it "raises `ConvenientService::Service::Plugins::CanHaveSteps::Entities::Method::Exceptions::OutMethodStepIsNotCompleted`" do
66+
expect { organizer.foo }
67+
.to raise_error(ConvenientService::Service::Plugins::CanHaveSteps::Entities::Method::Exceptions::OutMethodStepIsNotCompleted)
68+
.with_message(exception_message)
69+
end
8370

84-
it "returns corresponding step service result data attribute by key" do
85-
expect(organizer.foo).to eq(organizer.steps[0].result.unsafe_data[:foo])
71+
specify do
72+
expect { ignoring_exception(ConvenientService::Service::Plugins::CanHaveSteps::Entities::Method::Exceptions::OutMethodStepIsNotCompleted) { organizer.foo } }
73+
.to delegate_to(ConvenientService, :raise)
74+
end
8675
end
8776

88-
context "when multiple corresponding steps are completed" do
89-
let(:container) do
90-
Class.new.tap do |klass|
91-
klass.class_exec(first_step_service, second_step_service) do |first_step_service, second_step_service|
92-
include ConvenientService::Standard::Config
77+
context "when corresponding step is completed" do
78+
before do
79+
##
80+
# NOTE: Completes the step.
81+
#
82+
organizer.steps[0].save_outputs_in_organizer!
83+
end
84+
85+
it "returns corresponding step service result data attribute by key" do
86+
expect(organizer.foo).to eq(organizer.steps[0].result.unsafe_data[:foo])
87+
end
9388

94-
step first_step_service, out: :foo
89+
context "when multiple corresponding steps are completed" do
90+
let(:container) do
91+
Class.new.tap do |klass|
92+
klass.class_exec(first_step_service, second_step_service) do |first_step_service, second_step_service|
93+
include ConvenientService::Standard::Config
9594

96-
step second_step_service, out: :foo
95+
step first_step_service, out: :foo
96+
97+
step second_step_service, out: :foo
98+
end
9799
end
98100
end
101+
102+
let(:second_step_service) do
103+
Class.new do
104+
include ConvenientService::Standard::Config
105+
106+
def result
107+
success(data: {foo: "foo from second step"})
108+
end
109+
end
110+
end
111+
112+
before do
113+
##
114+
# NOTE: Completes the steps.
115+
#
116+
organizer.steps[0].save_outputs_in_organizer!
117+
organizer.steps[1].save_outputs_in_organizer!
118+
end
119+
120+
it "returns last corresponding step service result data attribute by key" do
121+
expect(organizer.foo).to eq(organizer.steps[1].result.unsafe_data[:foo])
122+
end
99123
end
124+
end
100125

101-
let(:second_step_service) do
102-
Class.new do
103-
include ConvenientService::Standard::Config
126+
context "when method has alias" do
127+
let(:container) do
128+
Class.new.tap do |klass|
129+
klass.class_exec(first_step_service) do |first_step_service|
130+
include ConvenientService::Standard::Config
104131

105-
def result
106-
success(data: {foo: "foo from second step"})
132+
step first_step_service, out: {foo: :bar}
107133
end
108134
end
109135
end
110136

111137
before do
112138
##
113-
# NOTE: Completes the steps.
139+
# NOTE: Completes the step.
114140
#
115141
organizer.steps[0].save_outputs_in_organizer!
116-
organizer.steps[1].save_outputs_in_organizer!
117142
end
118143

119-
it "returns last corresponding step service result data attribute by key" do
120-
expect(organizer.foo).to eq(organizer.steps[1].result.unsafe_data[:foo])
144+
it "returns corresponding step service result data attribute by key" do
145+
expect(organizer.bar).to eq(organizer.steps[0].result.unsafe_data[:bar])
121146
end
122147
end
123148
end
149+
end
150+
151+
context "when `method` is defined in container" do
152+
let(:container) do
153+
Class.new.tap do |klass|
154+
klass.class_exec(first_step_service) do |first_step_service|
155+
include ConvenientService::Standard::Config
124156

125-
context "when method has alias" do
126-
let(:container) do
127-
Class.new.tap do |klass|
128-
klass.class_exec(first_step_service) do |first_step_service|
129-
include ConvenientService::Standard::Config
157+
step first_step_service, out: :foo
130158

131-
step first_step_service, out: {foo: :bar}
159+
def foo
160+
:foo
132161
end
133162
end
134163
end
164+
end
135165

136-
before do
137-
##
138-
# NOTE: Completes the step.
139-
#
140-
organizer.steps[0].save_outputs_in_organizer!
141-
end
166+
it "returns `false`" do
167+
described_class.call(method: method, container: step.container, index: step.index)
142168

143-
it "returns corresponding step service result data attribute by key" do
144-
expect(organizer.bar).to eq(organizer.steps[0].result.unsafe_data[:bar])
145-
end
169+
expect(command_result).to eq(false)
170+
end
171+
172+
it "does NOT define `method` in `container`" do
173+
expect { command_result }.not_to change { ConvenientService::Utils::Method.defined?(method.name.to_s, step.container.klass, private: true) }
146174
end
147175
end
148176
end

spec/lib/convenient_service/service/plugins/can_have_steps/entities/method/concern/instance_methods_spec.rb

+9-3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
let(:service_class) do
2424
Class.new.tap do |klass|
2525
klass.class_exec(method_other, method_return_value) do |method_other, method_return_value|
26+
include ConvenientService::Core
27+
2628
define_method(method_other) { method_return_value }
2729
end
2830
end
@@ -159,7 +161,13 @@
159161

160162
describe "#define_output_in_container!" do
161163
let(:method_options) { {direction: :output} }
162-
let(:service_class) { Class.new }
164+
165+
let(:service_class) do
166+
Class.new do
167+
include ConvenientService::Core
168+
end
169+
end
170+
163171
let(:index) { 0 }
164172

165173
it "returns `true`" do
@@ -170,14 +178,12 @@
170178
expect { method.define_output_in_container!(container, index: index) }
171179
.to delegate_to(direction, :define_output_in_container!)
172180
.with_arguments(container, index: index, method: method)
173-
.and_return_its_value
174181
end
175182

176183
specify do
177184
expect { method.define_output_in_container!(container, index: index) }
178185
.to delegate_to(caller, :define_output_in_container!)
179186
.with_arguments(container, index: index, method: method)
180-
.and_return_its_value
181187
end
182188
end
183189

0 commit comments

Comments
 (0)