Skip to content

Commit a78ac9c

Browse files
committed
fix bug triggered by nesting ifs and eaches
1 parent 55ac5e0 commit a78ac9c

File tree

4 files changed

+99
-18
lines changed

4 files changed

+99
-18
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@
2525
- All internal restructuring/refactoring into domains.
2626
- Add support for organize `self.if(:condition, then: A, else: B)` syntax
2727
- change location of matchers to `require 'interactify/rspec_matchers/matchers'`
28+
29+
## [0.4.1] - 2023-12-29
30+
- Fix bug triggered when nesting each and if

lib/interactify/dsl/each_chain.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def klass
3333
end # end
3434

3535
define_singleton_method(:source_location) do # def self.source_location
36-
const_source_location this.evaluating_receiver.to_s # [file, line]
36+
const_source_location this.evaluating_receiver.to_s # [file, line]
3737
end # end
3838

3939
define_method(:run!) do # def run!

spec/fixtures/integration_app/app/interactors/all_the_things.rb

+87-7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,37 @@
1+
# This is test code.
2+
#
3+
# Avoid using organizers predominantly composed of lambdas. In complex organizers,
4+
# it's common to have many isolated interactor classes, but maintain some orchestration
5+
# code for readability and cohesion.
6+
#
7+
# For instance, rather than creating an EachProduct interactor or a separate interactor chain
8+
# for a single boolean flag, it's more efficient to manage the high-level business process
9+
# in one location. This approach reduces the need to switch between two mindsets:
10+
#
11+
# Mode 1 (High-Level Overview): Understanding the business process and identifying where changes fit.
12+
# Mode 2 (Detailed Implementation): Focusing on the specific logic of a process step.
13+
#
14+
# Initially, in Mode 1, a bird's-eye view is essential for grasping the process architecture.
15+
# Once this overview is clear, you pinpoint the relevant interactor for modifications or additions,
16+
# transitioning to Mode 2 for implementation.
17+
#
18+
# This distinction between modes is crucial. Mode 1 involves more reading, conceptualizing,
19+
# and questioning, often requiring navigation through multiple files and note-taking.
20+
# Mode 2 is about active coding and editing. Minimizing context switching between these modes
21+
# saves time and mental energy. It's also easier to maintain a high-level overview of the process
22+
# when the code is in one place.
23+
#
24+
# Typically this will allow us to move closer towards a one organizer per interaction model.
25+
# E.g. one Rails action == one organizer.
26+
# Even the background job firing can be handled by the organizer via YourClass::Async.
27+
#
28+
# An improvement we aim for is automatic diagram generation from the organizers, utilizing
29+
# contracts for documentation.
30+
#
31+
# Note: The conditional structures (`if`, `each`) here are not executed at runtime in the usual
32+
# sense. They are evaluated when defining the organizer, leading to the creation of discrete
33+
# classes that handle the respective logic.
34+
#
135
class AllTheThings
236
include Interactify
337

@@ -12,34 +46,44 @@ class AllTheThings
1246
else: [If::C, If::D]
1347
),
1448

15-
self.each(
16-
:things,
17-
If::A,
18-
If::B,
19-
-> (c) { c.lambda_set = true }
49+
# test each with lambda
50+
self.if(
51+
-> (c) { c.things },
52+
self.each(
53+
:things,
54+
If::A,
55+
If::B,
56+
-> (c) { c.lambda_set = true }
57+
),
2058
),
2159

60+
# test alternative if syntax
2261
self.if(
2362
-> (c) { c.a && c.b } ,
2463
then: -> (c) { c.both_a_and_b = true },
2564
else: -> (c) { c.both_a_and_b = false}
2665
),
2766

67+
# test setting a value to use later in the chain
2868
-> (c) { c.more_things = [1, 2, 3, 4] },
2969

70+
# test lambdas inside each
3071
self.each(
3172
:more_things,
3273
-> (c) {
3374
if c.more_thing_index.zero?
3475
c.first_more_thing = true
3576
end
3677
},
37-
-> (c) {
78+
-> (c) {
3879
if c.more_thing_index == 1
3980
c.next_more_thing = true
4081
end
4182
},
83+
# test nested if inside each
4284
{if: :not_set_thing, then: -> (c) { c.not_set_thing = true } },
85+
86+
# test setting a value after an else
4387
-> (c) { c.more_thing = true }
4488
),
4589

@@ -49,11 +93,47 @@ class AllTheThings
4993
-> (c) { c.optional_thing_was_set = true },
5094
-> (c) { c.and_then_another_thing = true },
5195
-> (c) { c.and_one_more_thing = true },
96+
# test nested each inside if
5297
self.each(:more_things,
5398
-> (c) { c.more_things[c.more_thing_index] = c.more_thing + 5 },
5499
-> (c) { c.more_things[c.more_thing_index] = c.more_thing + 5 }
55-
)
100+
)
56101
],
57102
else: -> (c) { c.optional_thing_was_set = false }
103+
),
104+
105+
# if -> each -> if -> each
106+
self.if(:condition, then: [-> {}], else: [
107+
self.each(
108+
:things,
109+
self.if(
110+
:thing,
111+
then: self.each(
112+
:more_things,
113+
-> (c) {
114+
c.counter ||= 0
115+
c.counter += 1
116+
}
117+
)
118+
)
119+
)
120+
]),
121+
122+
# each -> each -> each
123+
self.each(
124+
:more_things,
125+
self.each(
126+
:more_things,
127+
self.each(
128+
:more_things,
129+
self.each(
130+
:more_things,
131+
-> (c) {
132+
c.heavily_nested_counter ||= 0
133+
c.heavily_nested_counter += 1
134+
}
135+
)
136+
)
137+
)
58138
)
59139
end

spec/integration/all_the_things_integration_spec.rb

+8-10
Original file line numberDiff line numberDiff line change
@@ -27,29 +27,27 @@
2727
expect(result.b).to eq("b")
2828
expect(result.c).to eq(nil)
2929
expect(result.d).to eq(nil)
30+
3031
expect(result.lambda_set).to eq(true)
3132
expect(result.both_a_and_b).to eq(true)
3233
expect(result.more_things).to eq([1, 2, 3, 4])
3334
expect(result.first_more_thing).to eq(true)
3435
expect(result.next_more_thing).to eq(true)
3536
expect(result.optional_thing_was_set).to eq(false)
37+
38+
expect(result.counter).to eq 8
39+
expect(result.heavily_nested_counter).to eq 256
3640
end
3741
end
3842

3943
context "with an optional thing" do
4044
let(:result) { AllTheThings.promising(:a).call!(things:, optional_thing: true) }
4145

42-
it "sets A and B, then lambda_set, then both_a_and_b, then first_more_thing, next_more_thing" do
43-
expect(result.a).to eq("a")
44-
expect(result.b).to eq("b")
45-
expect(result.c).to eq(nil)
46-
expect(result.d).to eq(nil)
47-
expect(result.lambda_set).to eq(true)
48-
expect(result.both_a_and_b).to eq(true)
49-
expect(result.more_things).to eq([6, 7, 8, 9])
50-
expect(result.first_more_thing).to eq(true)
51-
expect(result.next_more_thing).to eq(true)
46+
it "sets the optional thing" do
5247
expect(result.optional_thing_was_set).to eq(true)
48+
49+
expect(result.counter).to eq 8
50+
expect(result.heavily_nested_counter).to eq 256
5351
end
5452
end
5553

0 commit comments

Comments
 (0)