Skip to content

Commit 67269e0

Browse files
committed
Add context files
1 parent 4fbd297 commit 67269e0

File tree

5 files changed

+991
-0
lines changed

5 files changed

+991
-0
lines changed

context/1_overview.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# TypedOperation Architecture Overview
2+
3+
## Purpose
4+
5+
TypedOperation is a Ruby gem that implements the Command pattern with strong typing, partial application, and currying capabilities. It provides a structured way to encapsulate business logic into discrete, reusable, and testable operations.
6+
7+
## Core Architecture
8+
9+
### Inheritance Hierarchy
10+
11+
```
12+
Literal::Struct / Literal::Data
13+
└── TypedOperation::Base / TypedOperation::ImmutableBase
14+
```
15+
16+
### Key Components
17+
18+
1. **TypedOperation::Base** - Mutable operation base class built on `Literal::Struct`
19+
2. **TypedOperation::ImmutableBase** - Immutable operation base class built on `Literal::Data`
20+
3. **TypedOperation::PartiallyApplied** - Represents an operation with some parameters fixed
21+
4. **TypedOperation::Prepared** - A fully parameterized operation ready for execution
22+
5. **TypedOperation::Curried** - Enables functional-style currying of operations
23+
24+
## Design Principles
25+
26+
### 1. Type Safety
27+
- All parameters are typed using the `literal` gem
28+
- Type checking happens at instantiation time
29+
- Support for complex types (unions, arrays, custom types)
30+
31+
### 2. Functional Programming Support
32+
- Operations can be partially applied (`.with`)
33+
- Full currying support (`.curry`)
34+
- Operations are callable objects (`.call`)
35+
- Immutable operation support via `ImmutableBase`
36+
37+
### 3. Flexibility
38+
- Both positional and named parameters
39+
- Optional parameters with defaults
40+
- Parameter coercion via blocks
41+
- Pattern matching support
42+
43+
### 4. Integration Ready
44+
- Rails generator for `ApplicationOperation`
45+
- Action Policy authorization module
46+
- Dry::Monads integration examples
47+
- Clean extension points
48+
49+
## Key Features
50+
51+
### Parameter Definition DSL
52+
```ruby
53+
class MyOperation < TypedOperation::Base
54+
positional_param :name, String
55+
named_param :age, Integer, optional: true
56+
param :email, String, &:downcase
57+
end
58+
```
59+
60+
### Partial Application
61+
```ruby
62+
# Fix some parameters, apply others later
63+
op = MyOperation.with("John")
64+
op.call(age: 30, email: "[email protected]")
65+
```
66+
67+
### Currying
68+
```ruby
69+
# Transform into a series of single-argument functions
70+
curried = MyOperation.curry
71+
curried.("John").(30).("[email protected]")
72+
```
73+
74+
### Pattern Matching
75+
```ruby
76+
case MyOperation.new("John", age: 30)
77+
in MyOperation[name, age]
78+
puts "Name: #{name}, Age: #{age}"
79+
end
80+
```
81+
82+
## Extension Points
83+
84+
1. **Custom Base Classes** - Create domain-specific operation base classes
85+
2. **Authorization** - Via `ActionPolicyAuth` module
86+
3. **Result Types** - Integrate with Dry::Monads or similar
87+
4. **Lifecycle Hooks** - `prepare`, `before_execute_operation`, `after_execute_operation`
88+
89+
## Thread Safety
90+
91+
- `TypedOperation::Base` instances are not frozen, but have no writer methods
92+
- `TypedOperation::ImmutableBase` instances are frozen on initialization
93+
- All partial application creates new instances
94+
- No shared mutable state between operations
95+
96+
## Performance Considerations
97+
98+
- Type checking occurs at instantiation, not at definition
99+
- Partial application creates intermediate objects
100+
- Currying creates nested function objects
101+
- Pattern matching is optimized via `deconstruct` methods

context/2_core-concepts.md

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
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

Comments
 (0)