|
| 1 | +# TypedOperation Core Concepts |
| 2 | + |
| 3 | +## Parameter System |
| 4 | + |
| 5 | +### Parameter Definition Methods |
| 6 | + |
| 7 | +TypedOperation provides three ways to define parameters: |
| 8 | + |
| 9 | +1. **`param`** - Base method, creates named parameters by default |
| 10 | +2. **`positional_param`** - Explicitly creates positional parameters |
| 11 | +3. **`named_param`** - Explicitly creates named parameters (same as `param`) |
| 12 | + |
| 13 | +### Parameter Options |
| 14 | + |
| 15 | +```ruby |
| 16 | +param :name, Type, <options> |
| 17 | +``` |
| 18 | + |
| 19 | +Options: |
| 20 | +- `positional: true/false` - Makes parameter positional |
| 21 | +- `optional: true/false` - Makes parameter optional |
| 22 | +- `default: value/proc` - Sets default value |
| 23 | +- `reader: :public/:private` - Controls accessor visibility |
| 24 | +- Block for coercion |
| 25 | + |
| 26 | +### Type System (via Literal gem) |
| 27 | + |
| 28 | +#### Basic Types |
| 29 | +```ruby |
| 30 | +param :name, String |
| 31 | +param :age, Integer |
| 32 | +param :active, _Boolean |
| 33 | +``` |
| 34 | + |
| 35 | +#### Complex Types |
| 36 | +```ruby |
| 37 | +include Literal::Types |
| 38 | + |
| 39 | +param :tags, _Array(String) |
| 40 | +param :status, _Union(String, Symbol) |
| 41 | +param :user, _Nilable(User) # Same as optional(User) |
| 42 | +param :data, _Hash(String, Integer) |
| 43 | +``` |
| 44 | + |
| 45 | +#### Optional Types |
| 46 | +```ruby |
| 47 | +# Three ways to make optional: |
| 48 | +param :name, String, optional: true |
| 49 | +param :name, optional(String) |
| 50 | +param :name, _Nilable(String) # Using Literal::Types |
| 51 | +``` |
| 52 | + |
| 53 | +### Parameter Coercion |
| 54 | + |
| 55 | +Coercion blocks transform input values: |
| 56 | + |
| 57 | +```ruby |
| 58 | +param :email, String, &:downcase |
| 59 | +param :age, Integer, &:to_i |
| 60 | +param :tags, _Array(String) do |value| |
| 61 | + value.is_a?(String) ? value.split(',') : Array(value) |
| 62 | +end |
| 63 | +``` |
| 64 | + |
| 65 | +**Important**: For nilable types, coercion blocks don't run on nil values. |
| 66 | + |
| 67 | +### Parameter Order Rules |
| 68 | + |
| 69 | +1. Required positional parameters must come before optional ones |
| 70 | +2. Named parameters can be defined in any order |
| 71 | +3. Mixing positional and named parameters is allowed |
| 72 | + |
| 73 | +```ruby |
| 74 | +class ValidOrder < TypedOperation::Base |
| 75 | + positional_param :required1, String |
| 76 | + positional_param :required2, String |
| 77 | + positional_param :optional1, String, optional: true |
| 78 | + named_param :name, String |
| 79 | + named_param :age, Integer, optional: true |
| 80 | +end |
| 81 | +``` |
| 82 | + |
| 83 | +## Operation Lifecycle |
| 84 | + |
| 85 | +### 1. Instantiation Phase |
| 86 | + |
| 87 | +```ruby |
| 88 | +# Parameters are validated and coerced |
| 89 | +operation = MyOperation.new("value", key: "value") |
| 90 | +``` |
| 91 | + |
| 92 | +- Type checking occurs here |
| 93 | +- Coercion blocks are applied |
| 94 | +- `after_initialize` is called (Literal hook) |
| 95 | +- `prepare` method is called if defined |
| 96 | + |
| 97 | +### 2. Execution Phase |
| 98 | + |
| 99 | +```ruby |
| 100 | +result = operation.call |
| 101 | +``` |
| 102 | + |
| 103 | +Execution flow: |
| 104 | +1. `call` → `execute_operation` |
| 105 | +2. `before_execute_operation` (hook) |
| 106 | +3. `perform` (main logic - must be implemented) |
| 107 | +4. `after_execute_operation(result)` (hook) |
| 108 | + |
| 109 | +### Lifecycle Hooks |
| 110 | + |
| 111 | +```ruby |
| 112 | +class MyOperation < TypedOperation::Base |
| 113 | + param :data, String |
| 114 | + |
| 115 | + # Called after parameters are set |
| 116 | + def prepare |
| 117 | + validate_data! |
| 118 | + end |
| 119 | + |
| 120 | + # Called before perform |
| 121 | + def before_execute_operation |
| 122 | + setup_resources |
| 123 | + super # Important for subclass chains |
| 124 | + end |
| 125 | + |
| 126 | + # Main operation logic |
| 127 | + def perform |
| 128 | + process_data |
| 129 | + end |
| 130 | + |
| 131 | + # Called after perform |
| 132 | + def after_execute_operation(result) |
| 133 | + cleanup_resources |
| 134 | + super(result) # Pass result through |
| 135 | + end |
| 136 | +end |
| 137 | +``` |
| 138 | + |
| 139 | +## Base Class Differences |
| 140 | + |
| 141 | +### TypedOperation::Base |
| 142 | +- Built on `Literal::Struct` |
| 143 | +- Mutable internals (but no setters) |
| 144 | +- Compatible with all features |
| 145 | +- Use for most operations |
| 146 | + |
| 147 | +### TypedOperation::ImmutableBase |
| 148 | +- Built on `Literal::Data` |
| 149 | +- Frozen after initialization |
| 150 | +- **Cannot** use `ActionPolicyAuth` |
| 151 | +- Use when immutability is critical |
| 152 | + |
| 153 | +## Parameter Introspection |
| 154 | + |
| 155 | +Operations provide class methods for parameter inspection: |
| 156 | + |
| 157 | +```ruby |
| 158 | +MyOperation.positional_parameters # [:name, :age] |
| 159 | +MyOperation.keyword_parameters # [:email, :active] |
| 160 | +MyOperation.required_positional_parameters # [:name] |
| 161 | +MyOperation.required_keyword_parameters # [:email] |
| 162 | +MyOperation.optional_positional_parameters # [:age] |
| 163 | +MyOperation.optional_keyword_parameters # [:active] |
| 164 | +``` |
| 165 | + |
| 166 | +## Error Handling |
| 167 | + |
| 168 | +TypedOperation defines three main error types: |
| 169 | + |
| 170 | +1. **`Literal::TypeError`** - Parameter type mismatch |
| 171 | +2. **`TypedOperation::MissingParameterError`** - Required parameter missing |
| 172 | +3. **`TypedOperation::InvalidOperationError`** - Operation misconfiguration |
| 173 | + |
| 174 | +```ruby |
| 175 | +# Type error |
| 176 | +MyOperation.new(123) # Expected String, got Integer |
| 177 | + |
| 178 | +# Missing parameter |
| 179 | +MyOperation.with("name").call # Missing required :email |
| 180 | + |
| 181 | +# Invalid operation |
| 182 | +class BadOp < TypedOperation::Base |
| 183 | + # No perform method defined |
| 184 | +end |
| 185 | +BadOp.new.call # InvalidOperationError |
| 186 | +``` |
| 187 | + |
| 188 | +## Internal Implementation Details |
| 189 | + |
| 190 | +### PropertyBuilder |
| 191 | + |
| 192 | +The `PropertyBuilder` class handles parameter definition: |
| 193 | +1. Wraps type in `NilableType` if optional |
| 194 | +2. Adds nil to union type if default is nil |
| 195 | +3. Validates positional parameter order |
| 196 | +4. Sets up coercion wrappers for nilable types |
| 197 | + |
| 198 | +### Parameter Storage |
| 199 | + |
| 200 | +Parameters are stored as Literal properties: |
| 201 | +- Positional: `prop :name, Type, :positional` |
| 202 | +- Named: `prop :name, Type, :keyword` |
| 203 | +- No writers are created (writer: false) |
| 204 | + |
| 205 | +### Execution Context |
| 206 | + |
| 207 | +- Operations maintain no internal state beyond parameters |
| 208 | +- Each execution is independent |
| 209 | +- Thread-safe by design (no shared mutable state) |
0 commit comments