opera 0.6.0 → 0.7.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +1 -1
- data/README.md +32 -0
- data/lib/opera/operation/builder/options_builder.rb +29 -0
- data/lib/opera/operation/builder.rb +9 -15
- data/lib/opera/operation/executor.rb +5 -0
- data/lib/opera/operation.rb +1 -0
- data/lib/opera/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a69b196af65cb01e20f10d595444751505e0a241f6264e00c8de370c05def135
|
|
4
|
+
data.tar.gz: b17458445a2f96f47abb45064084498d8e4efb208a2dc04aa1e4abba1d2ab7ee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f4531f3b0cea01d642df058dc6a956de5e036b1259dd2dff659b4a67dc0956a34e965032442860b3b03af1997e1af9a41fe18845fbff4a13ef04a22f11adddc1
|
|
7
|
+
data.tar.gz: ea82fa32434682ec8f9dbbe3754204c3b9edcd586a64805debee5332ef05a69ef913523e309bf729bff626fa21eb79383058740bd3d9ee2cc5a7770f82c710d1
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Opera Changelog
|
|
2
2
|
|
|
3
|
+
### 0.7.0 - Apr 30, 2026
|
|
4
|
+
|
|
5
|
+
- Add `:if` / `:unless` options to all instructions except `always` (`validate`, `transaction`, `step`, `success`, `finish_if`, `operation`, `operations`, `within`) for declarative conditional execution. Conditions accept a Symbol (method name) or a Proc/Lambda (evaluated via `instance_exec` in the operation instance scope). Skipped instructions do not execute and are not recorded in `result.executions`; for block instructions (`transaction`, `within`) the whole block, including nested instructions, is skipped. For `operation` / `operations`, the conventional `<method>_output` slot in context is set to `nil` when skipped, matching the historical `return Opera::Operation::Result.new` early-exit behavior. Passing both `:if` and `:unless` on the same instruction raises `ArgumentError` at class load time.
|
|
6
|
+
|
|
3
7
|
### 0.6.0 - Apr 15, 2026
|
|
4
8
|
|
|
5
9
|
- Add `always` executor: runs its step unconditionally after all regular steps, regardless of failure or an early finish
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -124,6 +124,38 @@ end
|
|
|
124
124
|
| `within :method do ... end` | Wraps nested steps with a custom method that must `yield`. If it doesn't yield, nested steps are skipped. |
|
|
125
125
|
| `always :method` | Executes a step unconditionally after all regular steps, even after a failure or an early finish. Must appear at the end of the operation — only other `always` steps may follow. Cannot be used inside blocks. Use `result.success?` / `result.failure?` inside the method to branch on outcome. |
|
|
126
126
|
|
|
127
|
+
### Conditional execution (`:if` / `:unless`)
|
|
128
|
+
|
|
129
|
+
The `:if` and `:unless` keyword arguments provide declarative conditional
|
|
130
|
+
execution. They are supported on `validate`, `transaction`, `step`, `success`,
|
|
131
|
+
`finish_if`, `operation`, `operations`, and `within` (every instruction except
|
|
132
|
+
`always`). For block instructions (`transaction`, `within`), a falsy condition
|
|
133
|
+
skips the whole block, including its nested instructions.
|
|
134
|
+
|
|
135
|
+
The condition is evaluated **before** the instruction runs -- if the condition
|
|
136
|
+
is not met the instruction is skipped entirely (no method invocation, no side
|
|
137
|
+
effects, not recorded in `result.executions`).
|
|
138
|
+
|
|
139
|
+
The condition value can be a **Symbol** (method name on the operation) or a
|
|
140
|
+
**Proc/Lambda** (evaluated via `instance_exec` in the operation instance
|
|
141
|
+
scope).
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
# Symbol form
|
|
145
|
+
step :notify_user, if: :notifications_enabled?
|
|
146
|
+
|
|
147
|
+
# Lambda form
|
|
148
|
+
step :recalculate, unless: -> { params[:skip_recalculation] }
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
When an `operation` or `operations` instruction is skipped, its
|
|
152
|
+
`context[:<method>_output]` slot is set to `nil` (matching the historical
|
|
153
|
+
`return Opera::Operation::Result.new` early-exit behavior). For other
|
|
154
|
+
instructions no context output is set.
|
|
155
|
+
|
|
156
|
+
Passing both `:if` and `:unless` on the same instruction raises `ArgumentError`
|
|
157
|
+
at class load time.
|
|
158
|
+
|
|
127
159
|
### Combining instructions
|
|
128
160
|
|
|
129
161
|
```ruby
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Opera
|
|
4
|
+
module Operation
|
|
5
|
+
module Builder
|
|
6
|
+
class OptionsBuilder
|
|
7
|
+
ALLOWED_OPTIONS = %i[if unless].freeze
|
|
8
|
+
|
|
9
|
+
def self.build(opts)
|
|
10
|
+
return {} if opts.empty?
|
|
11
|
+
|
|
12
|
+
unknown = opts.keys - ALLOWED_OPTIONS
|
|
13
|
+
raise ArgumentError, "Unknown option(s): #{unknown.inspect}. Allowed: #{ALLOWED_OPTIONS}" if unknown.any?
|
|
14
|
+
|
|
15
|
+
{ predicate: build_predicate(opts) }.compact
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.build_predicate(opts)
|
|
19
|
+
return nil unless opts[:if] || opts[:unless]
|
|
20
|
+
raise ArgumentError, 'Cannot use :if and :unless together' if opts[:if] && opts[:unless]
|
|
21
|
+
|
|
22
|
+
cond = opts[:if] || opts[:unless]
|
|
23
|
+
condition_proc = cond.is_a?(Symbol) ? proc { send(cond) } : cond
|
|
24
|
+
opts.key?(:if) ? condition_proc : proc { !instance_exec(&condition_proc) }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -16,7 +16,7 @@ module Opera
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
INNER_INSTRUCTIONS.each do |instruction|
|
|
19
|
-
define_method instruction do |method = nil, &blk|
|
|
19
|
+
define_method instruction do |method = nil, **opts, &blk|
|
|
20
20
|
if instructions.any? { |i| i[:kind] == :always }
|
|
21
21
|
raise ArgumentError,
|
|
22
22
|
"`#{instruction}` cannot appear after `always`. " \
|
|
@@ -24,7 +24,7 @@ module Opera
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
check_method_availability!(method) if method
|
|
27
|
-
instructions.concat(InnerBuilder.new.send(instruction, method, &blk))
|
|
27
|
+
instructions.concat(InnerBuilder.new.send(instruction, method, **opts, &blk))
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
@@ -43,19 +43,13 @@ module Opera
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
INNER_INSTRUCTIONS.each do |instruction|
|
|
46
|
-
define_method instruction do |method = nil, &blk|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
else
|
|
54
|
-
{
|
|
55
|
-
kind: instruction,
|
|
56
|
-
method: method
|
|
57
|
-
}
|
|
58
|
-
end
|
|
46
|
+
define_method instruction do |method = nil, **opts, &blk|
|
|
47
|
+
entry = if blk
|
|
48
|
+
{ kind: instruction, label: method, instructions: InnerBuilder.new(&blk).instructions }
|
|
49
|
+
else
|
|
50
|
+
{ kind: instruction, method: method }
|
|
51
|
+
end
|
|
52
|
+
instructions << entry.merge(OptionsBuilder.build(opts))
|
|
59
53
|
end
|
|
60
54
|
end
|
|
61
55
|
|
|
@@ -41,6 +41,11 @@ module Opera
|
|
|
41
41
|
|
|
42
42
|
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
43
43
|
def evaluate_instruction(instruction)
|
|
44
|
+
if instruction[:predicate] && !operation.instance_exec(&instruction[:predicate])
|
|
45
|
+
add_instruction_output(instruction, nil) if %i[operation operations].include?(instruction[:kind])
|
|
46
|
+
return
|
|
47
|
+
end
|
|
48
|
+
|
|
44
49
|
case instruction[:kind]
|
|
45
50
|
when :step
|
|
46
51
|
Instructions::Executors::Step.new(operation).call(instruction)
|
data/lib/opera/operation.rb
CHANGED
data/lib/opera/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: opera
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ProFinda Development Team
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: dry-validation
|
|
@@ -92,6 +92,7 @@ files:
|
|
|
92
92
|
- lib/opera/operation/attributes_dsl.rb
|
|
93
93
|
- lib/opera/operation/base.rb
|
|
94
94
|
- lib/opera/operation/builder.rb
|
|
95
|
+
- lib/opera/operation/builder/options_builder.rb
|
|
95
96
|
- lib/opera/operation/config.rb
|
|
96
97
|
- lib/opera/operation/executor.rb
|
|
97
98
|
- lib/opera/operation/instructions/executors/always.rb
|