Skip to content

Commit 2eda8fa

Browse files
authored
ensure we correctly wrap nested classes (#18)
1 parent c34cfa1 commit 2eda8fa

File tree

18 files changed

+470
-4
lines changed

18 files changed

+470
-4
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

+10-4
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,15 @@ 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!
4040
context.send(this.plural_resource_name).each_with_index do |resource, index|# context.packages.each_with_index do |package, index|
4141
context[this.singular_resource_name] = resource # context.package = package
4242
context[this.singular_resource_index_name] = index # context.package_index = index
4343

44-
klasses = Wrapper.wrap_many(self, this.each_loop_klasses)
45-
46-
klasses.each do |interactor| # [A, B, C].each do |interactor|
44+
self.class.klasses.each do |interactor| # [A, B, C].each do |interactor|
4745
interactor.call!(context) # interactor.call!(context)
4846
end # end
4947
end # end
@@ -54,6 +52,14 @@ def klass
5452
context # context
5553
end # end
5654

55+
define_singleton_method(:klasses) do # def self.klasses
56+
klasses = instance_variable_get(:@klasses) # @klasses ||= Wrapper.wrap_many(self, [A, B, C])
57+
return klasses if klasses
58+
59+
instance_variable_set(:@klasses, Wrapper.wrap_many(self, this.each_loop_klasses))
60+
end
61+
62+
# "<SomeNamespace::EachPackage iterates_over: [A, B, C]>"
5763
define_method(:inspect) do
5864
"<#{this.namespace}::#{this.iterator_klass_name} iterates_over: #{this.each_loop_klasses.inspect}>"
5965
end

spec/fixtures/asdf

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
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+
#
35+
class AllTheThings
36+
include Interactify
37+
38+
expect :things
39+
optional :optional_thing
40+
promise :a
41+
42+
organize \
43+
self.if(
44+
:things,
45+
then: [If::A, If::B],
46+
else: [If::C, If::D]
47+
),
48+
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+
),
58+
),
59+
60+
# test alternative if syntax
61+
self.if(
62+
-> (c) { c.a && c.b } ,
63+
then: -> (c) { c.both_a_and_b = true },
64+
else: -> (c) { c.both_a_and_b = false}
65+
),
66+
67+
# test setting a value to use later in the chain
68+
-> (c) { c.more_things = [1, 2, 3, 4] },
69+
70+
# test lambdas inside each
71+
self.each(
72+
:more_things,
73+
-> (c) {
74+
if c.more_thing_index.zero?
75+
c.first_more_thing = true
76+
end
77+
},
78+
-> (c) {
79+
if c.more_thing_index == 1
80+
c.next_more_thing = true
81+
end
82+
},
83+
# test nested if inside each
84+
{if: :not_set_thing, then: -> (c) { c.not_set_thing = true } },
85+
86+
# test setting a value after an else
87+
-> (c) { c.more_thing = true }
88+
),
89+
90+
self.if(
91+
:optional_thing,
92+
then: [
93+
-> (c) { c.optional_thing_was_set = true },
94+
-> (c) { c.and_then_another_thing = true },
95+
-> (c) { c.and_one_more_thing = true },
96+
# test nested each inside if
97+
self.each(:more_things,
98+
-> (c) { c.more_things[c.more_thing_index] = c.more_thing + 5 },
99+
-> (c) { c.more_things[c.more_thing_index] = c.more_thing + 5 }
100+
)
101+
],
102+
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+
)
138+
)
139+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module Each
2+
class A
3+
include Interactify
4+
5+
def call
6+
context.a = 'a'
7+
return unless context.thing
8+
9+
context.thing.a = 'a'
10+
context.thing.a_index = context.thing_index
11+
end
12+
end
13+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module Each
2+
class B
3+
include Interactify
4+
5+
def call
6+
context.b = 'b'
7+
return unless context.thing
8+
9+
context.thing.b = 'b'
10+
context.thing.b_index = context.thing_index
11+
end
12+
end
13+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module Each
2+
class C
3+
include Interactify
4+
5+
def call
6+
context.c = 'c'
7+
return unless context.thing
8+
9+
context.thing.c = 'c'
10+
context.thing.c_index = context.thing_index
11+
end
12+
end
13+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module Each
2+
class D
3+
include Interactify
4+
5+
def call
6+
context.d = 'd'
7+
return unless context.thing
8+
9+
context.thing.d = 'd'
10+
context.thing.d_index = context.thing_index
11+
end
12+
end
13+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module Each
2+
class Organizer
3+
include Interactify
4+
expect :things
5+
6+
organize \
7+
A, B, C, D,
8+
self.each(:things, A, B, C, D)
9+
end
10+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module If
2+
class A
3+
include Interactify
4+
5+
def call
6+
context.a = 'a'
7+
return unless context.thing
8+
9+
context.thing.a = 'a'
10+
context.thing.a_index = context.thing_index
11+
end
12+
end
13+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module If
2+
class B
3+
include Interactify
4+
5+
def call
6+
context.b = 'b'
7+
return unless context.thing
8+
9+
context.thing.b = 'b'
10+
context.thing.b_index = context.thing_index
11+
end
12+
end
13+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module If
2+
class C
3+
include Interactify
4+
5+
def call
6+
context.c = 'c'
7+
return unless context.thing
8+
9+
context.thing.c = 'c'
10+
context.thing.c_index = context.thing_index
11+
end
12+
end
13+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module If
2+
class D
3+
include Interactify
4+
5+
def call
6+
context.d = 'd'
7+
return unless context.thing
8+
9+
context.thing.d = 'd'
10+
context.thing.d_index = context.thing_index
11+
end
12+
end
13+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
module If
2+
class Anyways
3+
include Interactify
4+
5+
expect :blah, filled: false
6+
7+
def call
8+
context.anyways = blah
9+
end
10+
end
11+
12+
class MethodSyntaxOrganizer
13+
include Interactify
14+
expect :blah, filled: false
15+
16+
organize \
17+
self.if(:blah, [A, B], [C, D]),
18+
Anyways
19+
end
20+
21+
class AlternativeMethodSyntaxOrganizer
22+
include Interactify
23+
expect :blah, filled: false
24+
25+
organize(
26+
self.if(:blah, then: [A, B], else: [C, D]),
27+
Anyways
28+
)
29+
end
30+
31+
class HashSyntaxOrganizer
32+
include Interactify
33+
expect :blah, filled: false
34+
35+
organize(
36+
{if: :blah, then: [A, B], else: [C, D]},
37+
Anyways
38+
)
39+
end
40+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

0 commit comments

Comments
 (0)