1# frozen_string_literal: true
2
3require "delegate"
4
5class AdminAction
6 class NoOp
7 def to_s
8 "NoOp"
9 end
10 end
11
12 module Direction
13 class InvalidDirection < StandardError; end
14
15 def self.for(direction)
16 {
17 forward: Forward,
18 reverse: Reverse,
19 reforward: Reforward
20 }.fetch(direction.to_sym) { raise InvalidDirection }
21 end
22
23 class Forward < SimpleDelegator
24 def with(**kwargs)
25 self.class.new(__getobj__.with(**kwargs))
26 end
27
28 def perform
29 check_forward.then { forward }.then { |x| self.class.new(x) }
30 end
31
32 def to_h
33 super.merge(direction: :forward)
34 end
35
36 def undo
37 Reverse.new(__getobj__.with(parent_id: id))
38 end
39 end
40
41 class Reverse < SimpleDelegator
42 def with(**kwargs)
43 self.class.new(__getobj__.with(**kwargs))
44 end
45
46 def perform
47 check_reverse.then { reverse }.then { |x| self.class.new(x) }
48 end
49
50 def to_s
51 "UNDO(#{parent_id}) #{super}"
52 end
53
54 def to_h
55 super.merge(direction: :reverse)
56 end
57
58 def undo
59 Reforward.new(__getobj__)
60 end
61 end
62
63 class Reforward < Forward
64 def with(**kwargs)
65 self.class.new(__getobj__.with(**kwargs))
66 end
67
68 def to_s
69 "REDO(#{parent_id}) #{super}"
70 end
71
72 def to_h
73 super.merge(direction: :reforward)
74 end
75
76 def undo
77 Reverse.new(__getobj__)
78 end
79 end
80 end
81
82 def self.for(**kwargs)
83 Direction::Forward.new(new(**kwargs))
84 end
85
86 def initialize(**kwargs)
87 @attributes = kwargs
88 end
89
90 def with(**kwargs)
91 self.class.new(**@attributes.merge(kwargs))
92 end
93
94 def id
95 @attributes[:id]
96 end
97
98 def parent_id
99 @attributes[:parent_id]
100 end
101
102 # This tells us if this is the first time we're running this command.
103 # This can be used by actions which take destructive or annoying actions
104 # and don't want to redo those parts on an undo or redo.
105 def first_time?
106 !parent_id
107 end
108
109 def actor_id
110 @attributes[:actor_id]
111 end
112
113 def check_forward
114 EMPromise.resolve(nil)
115 end
116
117 def forward
118 EMPromise.resolve(self)
119 end
120
121 def check_reverse
122 EMPromise.resolve(nil)
123 end
124
125 def reverse
126 EMPromise.resolve(self)
127 end
128
129 def to_h
130 @attributes.merge({
131 class: self.class.to_s.delete_prefix("AdminAction::")
132 }.compact)
133 end
134
135 module Isomorphic
136 def check_reverse
137 to_reverse.check_forward
138 end
139
140 def reverse
141 # We don't want it to return the reversed one
142 # We want it to return itself but with the reverse state
143 to_reverse.forward.then { self }
144 end
145 end
146end