treaty 0.19.0 → 0.20.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/README.md +1 -1
- data/lib/treaty/action/base.rb +11 -0
- data/lib/treaty/action/context/callable.rb +90 -0
- data/lib/treaty/action/context/dsl.rb +56 -0
- data/lib/treaty/action/context/workspace.rb +92 -0
- data/lib/treaty/action/executor/inventory.rb +136 -0
- data/lib/treaty/{info/rest → action/info}/builder.rb +2 -2
- data/lib/treaty/{info/rest → action/info}/dsl.rb +2 -2
- data/lib/treaty/{info/rest → action/info}/result.rb +2 -2
- data/lib/treaty/action/inventory/collection.rb +77 -0
- data/lib/treaty/action/inventory/factory.rb +108 -0
- data/lib/treaty/action/inventory/inventory.rb +146 -0
- data/lib/treaty/action/request/attribute/attribute.rb +76 -0
- data/lib/treaty/action/request/attribute/builder.rb +98 -0
- data/lib/treaty/action/request/entity.rb +78 -0
- data/lib/treaty/action/request/factory.rb +116 -0
- data/lib/treaty/action/request/validator.rb +120 -0
- data/lib/treaty/action/response/attribute/attribute.rb +79 -0
- data/lib/treaty/action/response/attribute/builder.rb +96 -0
- data/lib/treaty/action/response/entity.rb +79 -0
- data/lib/treaty/action/response/factory.rb +129 -0
- data/lib/treaty/action/response/validator.rb +111 -0
- data/lib/treaty/action/result.rb +81 -0
- data/lib/treaty/action/versions/collection.rb +47 -0
- data/lib/treaty/action/versions/dsl.rb +116 -0
- data/lib/treaty/action/versions/execution/request.rb +287 -0
- data/lib/treaty/action/versions/executor.rb +61 -0
- data/lib/treaty/action/versions/factory.rb +253 -0
- data/lib/treaty/action/versions/resolver.rb +150 -0
- data/lib/treaty/action/versions/semantic.rb +64 -0
- data/lib/treaty/action/versions/workspace.rb +106 -0
- data/lib/treaty/action.rb +31 -0
- data/lib/treaty/controller/dsl.rb +1 -1
- data/lib/treaty/entity/attribute/base.rb +1 -1
- data/lib/treaty/entity/attribute/builder/base.rb +1 -1
- data/lib/treaty/entity/attribute/dsl.rb +1 -1
- data/lib/treaty/entity/base.rb +1 -1
- data/lib/treaty/entity/builder.rb +62 -5
- data/lib/treaty/version.rb +1 -1
- metadata +32 -31
- data/lib/treaty/base.rb +0 -9
- data/lib/treaty/context/callable.rb +0 -26
- data/lib/treaty/context/dsl.rb +0 -12
- data/lib/treaty/context/workspace.rb +0 -32
- data/lib/treaty/executor/inventory.rb +0 -122
- data/lib/treaty/inventory/collection.rb +0 -71
- data/lib/treaty/inventory/factory.rb +0 -91
- data/lib/treaty/inventory/inventory.rb +0 -92
- data/lib/treaty/request/attribute/attribute.rb +0 -25
- data/lib/treaty/request/attribute/builder.rb +0 -46
- data/lib/treaty/request/entity.rb +0 -33
- data/lib/treaty/request/factory.rb +0 -81
- data/lib/treaty/request/validator.rb +0 -60
- data/lib/treaty/response/attribute/attribute.rb +0 -25
- data/lib/treaty/response/attribute/builder.rb +0 -46
- data/lib/treaty/response/entity.rb +0 -33
- data/lib/treaty/response/factory.rb +0 -87
- data/lib/treaty/response/validator.rb +0 -53
- data/lib/treaty/result.rb +0 -23
- data/lib/treaty/versions/collection.rb +0 -15
- data/lib/treaty/versions/dsl.rb +0 -42
- data/lib/treaty/versions/execution/request.rb +0 -177
- data/lib/treaty/versions/executor.rb +0 -14
- data/lib/treaty/versions/factory.rb +0 -112
- data/lib/treaty/versions/resolver.rb +0 -70
- data/lib/treaty/versions/semantic.rb +0 -22
- data/lib/treaty/versions/workspace.rb +0 -43
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 395c83deb4d618293b99588b6130e9529febdb028d2db017ee27181479371a80
|
|
4
|
+
data.tar.gz: c0b2b1e546542caeee840f105aa0752ae6d84067e78a494c5429204dd2ed3052
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d5b28e9d167fadd9cf465d9318920342f8be404ffb32ec17aa1f73e24c34cc8b1f3658273627086c4361d12e7fcd51ee33c96c17115f471c174bc0371122e41d
|
|
7
|
+
data.tar.gz: 5d5f5b386389546b413ee28988bff90386aef2aa60e5234ee660f9fcda01aedffd3dc7b3eab03b10b94a842170119cf89ed60b0cbb21bdbe15b69695d35096eb
|
data/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
</div>
|
|
14
14
|
|
|
15
15
|
> [!WARNING]
|
|
16
|
-
> **Development Status**: Treaty is currently under active development in the 0.x version series. Breaking changes may occur between minor versions (0.x) as we refine the API and add new features. The library will stabilize with the 1.0 release. We recommend pinning to specific patch versions in your Gemfile (e.g., `gem "treaty", "~> 0.
|
|
16
|
+
> **Development Status**: Treaty is currently under active development in the 0.x version series. Breaking changes may occur between minor versions (0.x) as we refine the API and add new features. The library will stabilize with the 1.0 release. We recommend pinning to specific patch versions in your Gemfile (e.g., `gem "treaty", "~> 0.20.0"`) until the 1.0 release.
|
|
17
17
|
|
|
18
18
|
## 📚 Documentation
|
|
19
19
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Action
|
|
5
|
+
module Context
|
|
6
|
+
# Class methods for calling treaty actions.
|
|
7
|
+
#
|
|
8
|
+
# ## Purpose
|
|
9
|
+
#
|
|
10
|
+
# Provides the public `call!` class method that serves as the entry
|
|
11
|
+
# point for treaty execution. Handles instance creation and delegates
|
|
12
|
+
# to instance methods.
|
|
13
|
+
#
|
|
14
|
+
# ## Usage
|
|
15
|
+
#
|
|
16
|
+
# Extended into:
|
|
17
|
+
# - Treaty classes (via Context::DSL)
|
|
18
|
+
#
|
|
19
|
+
# Called by:
|
|
20
|
+
# - Controller integration
|
|
21
|
+
# - Direct treaty invocation in tests
|
|
22
|
+
#
|
|
23
|
+
# ## Call Pattern
|
|
24
|
+
#
|
|
25
|
+
# ```ruby
|
|
26
|
+
# # Public API:
|
|
27
|
+
# result = Posts::CreateTreaty.call!(
|
|
28
|
+
# version: "1",
|
|
29
|
+
# params: { post: { title: "Hello" } },
|
|
30
|
+
# context: controller, # optional
|
|
31
|
+
# inventory: inventory # optional
|
|
32
|
+
# )
|
|
33
|
+
#
|
|
34
|
+
# result.data # => validated response hash
|
|
35
|
+
# result.status # => HTTP status code
|
|
36
|
+
# result.version # => resolved version
|
|
37
|
+
# ```
|
|
38
|
+
#
|
|
39
|
+
# ## Implementation
|
|
40
|
+
#
|
|
41
|
+
# Creates a new treaty instance and delegates to `_call!` which
|
|
42
|
+
# passes through to Workspace, then to Versions::Workspace for
|
|
43
|
+
# actual execution.
|
|
44
|
+
module Callable
|
|
45
|
+
# Executes treaty with given parameters
|
|
46
|
+
#
|
|
47
|
+
# Main entry point for treaty execution. Creates instance
|
|
48
|
+
# and delegates to instance methods for actual work.
|
|
49
|
+
#
|
|
50
|
+
# @param version [String, nil] Requested API version (nil uses default)
|
|
51
|
+
# @param params [Hash] Request parameters
|
|
52
|
+
# @param context [Object, nil] Controller context (for inventory evaluation)
|
|
53
|
+
# @param inventory [Treaty::Action::Inventory::Collection, nil] Inventory items
|
|
54
|
+
# @return [Treaty::Action::Result] Execution result
|
|
55
|
+
# @raise [Treaty::Exceptions::VersionNotFound] If version not found
|
|
56
|
+
# @raise [Treaty::Exceptions::Validation] If validation fails
|
|
57
|
+
# @raise [Treaty::Exceptions::Execution] If service execution fails
|
|
58
|
+
def call!(version:, params:, context: nil, inventory: nil)
|
|
59
|
+
treaty_instance = send(:new)
|
|
60
|
+
|
|
61
|
+
_call!(treaty_instance, context:, inventory:, version:, params:)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
# Internal call delegation to instance
|
|
67
|
+
#
|
|
68
|
+
# Passes all parameters plus class-level collection_of_versions
|
|
69
|
+
# to the instance's _call! method.
|
|
70
|
+
#
|
|
71
|
+
# @param treaty_instance [Object] Treaty instance
|
|
72
|
+
# @param context [Object, nil] Controller context
|
|
73
|
+
# @param inventory [Treaty::Action::Inventory::Collection, nil] Inventory items
|
|
74
|
+
# @param version [String, nil] Requested version
|
|
75
|
+
# @param params [Hash] Request parameters
|
|
76
|
+
# @return [Treaty::Action::Result] Execution result
|
|
77
|
+
def _call!(treaty_instance, context:, inventory:, version:, params:)
|
|
78
|
+
treaty_instance.send(
|
|
79
|
+
:_call!,
|
|
80
|
+
context:,
|
|
81
|
+
inventory:,
|
|
82
|
+
version:,
|
|
83
|
+
params:,
|
|
84
|
+
collection_of_versions:
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Action
|
|
5
|
+
module Context
|
|
6
|
+
# DSL module that wires up callable and workspace functionality.
|
|
7
|
+
#
|
|
8
|
+
# ## Purpose
|
|
9
|
+
#
|
|
10
|
+
# Acts as a composition root that includes both Callable (class methods)
|
|
11
|
+
# and Workspace (instance methods) when included in a treaty class.
|
|
12
|
+
# This enables the `call!` API pattern.
|
|
13
|
+
#
|
|
14
|
+
# ## Usage
|
|
15
|
+
#
|
|
16
|
+
# Included in:
|
|
17
|
+
# - Treaty::Action::Base (as part of core DSL)
|
|
18
|
+
#
|
|
19
|
+
# ## What it provides
|
|
20
|
+
#
|
|
21
|
+
# When included, adds:
|
|
22
|
+
# - Class method: `call!` (from Callable)
|
|
23
|
+
# - Instance methods: `_call!`, `call!` (from Workspace)
|
|
24
|
+
#
|
|
25
|
+
# ## Architecture
|
|
26
|
+
#
|
|
27
|
+
# The call chain works as follows:
|
|
28
|
+
#
|
|
29
|
+
# ```
|
|
30
|
+
# MyTreaty.call!(version:, params:, ...)
|
|
31
|
+
# │
|
|
32
|
+
# └─► Callable.call! (class method)
|
|
33
|
+
# │
|
|
34
|
+
# ├─► Creates treaty instance
|
|
35
|
+
# │
|
|
36
|
+
# └─► Workspace._call! (instance method)
|
|
37
|
+
# │
|
|
38
|
+
# └─► Workspace.call! (stores @collection_of_versions)
|
|
39
|
+
# │
|
|
40
|
+
# └─► super → Versions::Workspace.call!
|
|
41
|
+
# ```
|
|
42
|
+
module DSL
|
|
43
|
+
# Hook called when module is included
|
|
44
|
+
#
|
|
45
|
+
# Extends the including class with Callable (class methods)
|
|
46
|
+
# and includes Workspace (instance methods).
|
|
47
|
+
#
|
|
48
|
+
# @param base [Class] The class including this module
|
|
49
|
+
def self.included(base)
|
|
50
|
+
base.extend(Callable)
|
|
51
|
+
base.include(Workspace)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Action
|
|
5
|
+
module Context
|
|
6
|
+
# Instance methods for treaty execution context.
|
|
7
|
+
#
|
|
8
|
+
# ## Purpose
|
|
9
|
+
#
|
|
10
|
+
# Provides instance-level methods that bridge between Callable
|
|
11
|
+
# (class methods) and Versions::Workspace (actual execution).
|
|
12
|
+
# Stores the collection_of_versions in instance variable for
|
|
13
|
+
# use by Versions::Workspace.
|
|
14
|
+
#
|
|
15
|
+
# ## Usage
|
|
16
|
+
#
|
|
17
|
+
# Included via:
|
|
18
|
+
# - Context::DSL (when DSL is included)
|
|
19
|
+
#
|
|
20
|
+
# ## Method Chain
|
|
21
|
+
#
|
|
22
|
+
# ```
|
|
23
|
+
# Callable.call! (class)
|
|
24
|
+
# │
|
|
25
|
+
# └─► _call! (receives all params from class)
|
|
26
|
+
# │
|
|
27
|
+
# └─► call! (stores @collection_of_versions)
|
|
28
|
+
# │
|
|
29
|
+
# └─► super → Versions::Workspace.call!
|
|
30
|
+
# ```
|
|
31
|
+
#
|
|
32
|
+
# ## Why Two Methods?
|
|
33
|
+
#
|
|
34
|
+
# - `_call!` - Entry point from Callable, receives raw parameters
|
|
35
|
+
# - `call!` - Stores collection_of_versions, then calls super
|
|
36
|
+
#
|
|
37
|
+
# The separation allows Versions::Workspace to override `call!`
|
|
38
|
+
# while keeping the parameter passing clean.
|
|
39
|
+
#
|
|
40
|
+
# ## Instance Variable
|
|
41
|
+
#
|
|
42
|
+
# Stores `@collection_of_versions` which is used by
|
|
43
|
+
# Versions::Workspace.call! for version resolution.
|
|
44
|
+
module Workspace
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
# Entry point for instance execution
|
|
48
|
+
#
|
|
49
|
+
# Receives all parameters from Callable and forwards to call!.
|
|
50
|
+
# This method exists to provide a clean interface between
|
|
51
|
+
# class methods and instance methods.
|
|
52
|
+
#
|
|
53
|
+
# @param context [Object, nil] Controller context
|
|
54
|
+
# @param inventory [Treaty::Action::Inventory::Collection, nil] Inventory items
|
|
55
|
+
# @param version [String, nil] Requested version
|
|
56
|
+
# @param params [Hash] Request parameters
|
|
57
|
+
# @param collection_of_versions [Treaty::Action::Versions::Collection] Version factories
|
|
58
|
+
# @return [Treaty::Action::Result] Execution result
|
|
59
|
+
def _call!(
|
|
60
|
+
context:,
|
|
61
|
+
inventory:,
|
|
62
|
+
version:,
|
|
63
|
+
params:,
|
|
64
|
+
collection_of_versions:
|
|
65
|
+
)
|
|
66
|
+
call!(
|
|
67
|
+
context:,
|
|
68
|
+
inventory:,
|
|
69
|
+
version:,
|
|
70
|
+
params:,
|
|
71
|
+
collection_of_versions:
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Stores collection and delegates to Versions::Workspace
|
|
76
|
+
#
|
|
77
|
+
# Captures collection_of_versions in instance variable,
|
|
78
|
+
# then calls super which invokes Versions::Workspace.call!
|
|
79
|
+
# for actual treaty execution.
|
|
80
|
+
#
|
|
81
|
+
# @param collection_of_versions [Treaty::Action::Versions::Collection] Version factories
|
|
82
|
+
# @return [Treaty::Action::Result] Execution result (from super)
|
|
83
|
+
def call!(
|
|
84
|
+
collection_of_versions:,
|
|
85
|
+
**
|
|
86
|
+
)
|
|
87
|
+
@collection_of_versions = collection_of_versions
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Action
|
|
5
|
+
module Executor
|
|
6
|
+
# Lazy-evaluating proxy for accessing inventory items.
|
|
7
|
+
#
|
|
8
|
+
# ## Purpose
|
|
9
|
+
#
|
|
10
|
+
# Provides lazy evaluation and caching of inventory items defined in controllers.
|
|
11
|
+
# Acts as a proxy that evaluates inventory items only when accessed, avoiding
|
|
12
|
+
# unnecessary computation for unused items.
|
|
13
|
+
#
|
|
14
|
+
# ## Usage
|
|
15
|
+
#
|
|
16
|
+
# Created internally by:
|
|
17
|
+
# - Versions::Execution::Request (when executing treaty versions)
|
|
18
|
+
#
|
|
19
|
+
# Passed to:
|
|
20
|
+
# - Service classes (as `inventory` parameter)
|
|
21
|
+
# - Proc executors (as `inventory:` keyword argument)
|
|
22
|
+
#
|
|
23
|
+
# ## Access Patterns
|
|
24
|
+
#
|
|
25
|
+
# Method-based access (lazy, cached):
|
|
26
|
+
# inventory.current_user # Evaluates and caches on first call
|
|
27
|
+
# inventory.current_user # Returns cached value
|
|
28
|
+
#
|
|
29
|
+
# Hash-based access (evaluates all items):
|
|
30
|
+
# inventory.to_h # => { current_user: <User>, posts: [...] }
|
|
31
|
+
#
|
|
32
|
+
# ## Caching
|
|
33
|
+
#
|
|
34
|
+
# Once an item is evaluated via method access, the result is cached
|
|
35
|
+
# in `@evaluated_cache`. Subsequent calls return the cached value
|
|
36
|
+
# without re-evaluation.
|
|
37
|
+
#
|
|
38
|
+
# ## Example
|
|
39
|
+
#
|
|
40
|
+
# # In controller:
|
|
41
|
+
# treaty :index do
|
|
42
|
+
# provide :current_user
|
|
43
|
+
# provide :posts, from: :load_posts
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# # In service:
|
|
47
|
+
# class Posts::IndexService
|
|
48
|
+
# def call(inventory:, params:)
|
|
49
|
+
# user = inventory.current_user # Lazy evaluation
|
|
50
|
+
# posts = inventory.posts # Lazy evaluation
|
|
51
|
+
# # ...
|
|
52
|
+
# end
|
|
53
|
+
# end
|
|
54
|
+
class Inventory
|
|
55
|
+
# Creates a new inventory executor instance
|
|
56
|
+
#
|
|
57
|
+
# @param inventory [Treaty::Action::Inventory::Collection, nil] Collection of inventory items
|
|
58
|
+
# @param context [Object] Controller context for evaluating items (typically ActionController instance)
|
|
59
|
+
def initialize(inventory, context)
|
|
60
|
+
@inventory = inventory
|
|
61
|
+
@context = context
|
|
62
|
+
@evaluated_cache = {}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Handles dynamic method calls to access inventory items
|
|
66
|
+
#
|
|
67
|
+
# Looks up the inventory item by method name, evaluates it with
|
|
68
|
+
# the controller context, and caches the result.
|
|
69
|
+
#
|
|
70
|
+
# @param method_name [Symbol] Name of the inventory item to access
|
|
71
|
+
# @return [Object] Evaluated value from the inventory item
|
|
72
|
+
# @raise [Treaty::Exceptions::Inventory] If item with given name not found
|
|
73
|
+
def method_missing(method_name, *_args)
|
|
74
|
+
return @evaluated_cache[method_name] if @evaluated_cache.key?(method_name)
|
|
75
|
+
|
|
76
|
+
item = find_inventory_item(method_name)
|
|
77
|
+
|
|
78
|
+
@evaluated_cache[method_name] = item.evaluate(@context)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Checks if method corresponds to an inventory item
|
|
82
|
+
#
|
|
83
|
+
# @param method_name [Symbol] Method name to check
|
|
84
|
+
# @param include_private [Boolean] Whether to include private methods
|
|
85
|
+
# @return [Boolean] True if inventory contains item with given name
|
|
86
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
87
|
+
return false if @inventory.nil?
|
|
88
|
+
|
|
89
|
+
@inventory.names.include?(method_name) || super
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Evaluates all inventory items and returns as hash
|
|
93
|
+
#
|
|
94
|
+
# Unlike method-based access, this evaluates ALL items at once.
|
|
95
|
+
# Useful when you need all inventory data as a hash.
|
|
96
|
+
#
|
|
97
|
+
# @return [Hash{Symbol => Object}] Hash of all evaluated inventory values
|
|
98
|
+
def to_h
|
|
99
|
+
return {} if @inventory.nil?
|
|
100
|
+
|
|
101
|
+
@inventory.evaluate(@context)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Returns human-readable representation for debugging
|
|
105
|
+
#
|
|
106
|
+
# @return [String] Inspection string with available item names
|
|
107
|
+
def inspect
|
|
108
|
+
items = @inventory&.names || []
|
|
109
|
+
"#<Treaty::Action::Executor::Inventory items=#{items.inspect}>"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
# Finds inventory item by name or raises error
|
|
115
|
+
#
|
|
116
|
+
# @param name [Symbol] Name of the inventory item
|
|
117
|
+
# @return [Treaty::Action::Inventory::Inventory] Found inventory item
|
|
118
|
+
# @raise [Treaty::Exceptions::Inventory] If item not found
|
|
119
|
+
def find_inventory_item(name)
|
|
120
|
+
item = @inventory&.find { |item| item.name == name }
|
|
121
|
+
|
|
122
|
+
return item if item
|
|
123
|
+
|
|
124
|
+
available = @inventory&.names || []
|
|
125
|
+
|
|
126
|
+
raise Treaty::Exceptions::Inventory,
|
|
127
|
+
I18n.t(
|
|
128
|
+
"treaty.executor.inventory.item_not_found",
|
|
129
|
+
name:,
|
|
130
|
+
available: available.join(", ")
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Action
|
|
5
|
+
module Inventory
|
|
6
|
+
# Collection wrapper for sets of inventory items.
|
|
7
|
+
#
|
|
8
|
+
# ## Purpose
|
|
9
|
+
#
|
|
10
|
+
# Provides a unified interface for working with collections of inventory items.
|
|
11
|
+
# Uses Ruby Set internally for uniqueness but exposes Array-like interface.
|
|
12
|
+
#
|
|
13
|
+
# ## Usage
|
|
14
|
+
#
|
|
15
|
+
# Used internally by:
|
|
16
|
+
# - Inventory::Factory (to store inventory items)
|
|
17
|
+
# - Executor::Inventory (to evaluate and access items)
|
|
18
|
+
#
|
|
19
|
+
# ## Methods
|
|
20
|
+
#
|
|
21
|
+
# Delegates common collection methods to internal Set:
|
|
22
|
+
# - `<<` - Add inventory item
|
|
23
|
+
# - `each_with_object` - Iteration with accumulator
|
|
24
|
+
# - `find` - Access by condition
|
|
25
|
+
# - `empty?` - Size check
|
|
26
|
+
#
|
|
27
|
+
# Custom methods:
|
|
28
|
+
# - `exists?` - Returns true if collection is not empty
|
|
29
|
+
# - `names` - Returns array of inventory item names
|
|
30
|
+
# - `evaluate` - Evaluates all items with controller context
|
|
31
|
+
#
|
|
32
|
+
# ## Example
|
|
33
|
+
#
|
|
34
|
+
# collection = Collection.new
|
|
35
|
+
# collection << Inventory.new(name: :current_user, source: :current_user)
|
|
36
|
+
# collection << Inventory.new(name: :posts, source: :load_posts)
|
|
37
|
+
# collection.exists? # => true
|
|
38
|
+
# collection.names # => [:current_user, :posts]
|
|
39
|
+
class Collection
|
|
40
|
+
extend Forwardable
|
|
41
|
+
|
|
42
|
+
def_delegators :@collection, :<<, :each_with_object, :find, :empty?
|
|
43
|
+
|
|
44
|
+
# Creates a new collection instance
|
|
45
|
+
#
|
|
46
|
+
# @param collection [Set] Initial collection (default: empty Set)
|
|
47
|
+
def initialize(collection = Set.new)
|
|
48
|
+
@collection = collection
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Checks if collection has any elements
|
|
52
|
+
#
|
|
53
|
+
# @return [Boolean] True if collection is not empty
|
|
54
|
+
def exists?
|
|
55
|
+
!empty?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns array of all inventory item names
|
|
59
|
+
#
|
|
60
|
+
# @return [Array<Symbol>] Array of inventory item names
|
|
61
|
+
def names
|
|
62
|
+
@collection.each_with_object([]) { |item, names| names << item.name }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Evaluates all inventory items with controller context
|
|
66
|
+
#
|
|
67
|
+
# @param context [Object] Controller context for evaluation
|
|
68
|
+
# @return [Hash{Symbol => Object}] Hash of evaluated inventory values
|
|
69
|
+
def evaluate(context)
|
|
70
|
+
@collection.each_with_object({}) do |inventory_item, hash|
|
|
71
|
+
hash[inventory_item.name] = inventory_item.evaluate(context)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Action
|
|
5
|
+
module Inventory
|
|
6
|
+
# Factory for building inventory collections from controller DSL.
|
|
7
|
+
#
|
|
8
|
+
# ## Purpose
|
|
9
|
+
#
|
|
10
|
+
# Provides the `provide` DSL method used in controller treaty blocks
|
|
11
|
+
# to define inventory items. Inventory allows controllers to pass
|
|
12
|
+
# data (current_user, loaded records, etc.) to services.
|
|
13
|
+
#
|
|
14
|
+
# ## Usage
|
|
15
|
+
#
|
|
16
|
+
# Created by:
|
|
17
|
+
# - Controller::DSL (when treaty block is evaluated)
|
|
18
|
+
#
|
|
19
|
+
# ## DSL Method
|
|
20
|
+
#
|
|
21
|
+
# The only supported method is `provide`:
|
|
22
|
+
#
|
|
23
|
+
# treaty :index do
|
|
24
|
+
# provide :current_user # Shorthand: same name as method
|
|
25
|
+
# provide :posts, from: :load_posts # Symbol source
|
|
26
|
+
# provide :meta, from: -> { build_meta } # Proc source
|
|
27
|
+
# provide :limit, from: 10 # Direct value
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# ## Source Types
|
|
31
|
+
#
|
|
32
|
+
# | Type | Description | Evaluation |
|
|
33
|
+
# |------|-------------|------------|
|
|
34
|
+
# | Symbol | Controller method name | `context.send(source)` |
|
|
35
|
+
# | Proc | Lambda/block | `context.instance_exec(&source)` |
|
|
36
|
+
# | Other | Direct value | Returned as-is |
|
|
37
|
+
#
|
|
38
|
+
# ## Example
|
|
39
|
+
#
|
|
40
|
+
# factory = Factory.new(:index)
|
|
41
|
+
# factory.provide :current_user
|
|
42
|
+
# factory.provide :posts, from: :load_posts
|
|
43
|
+
# factory.collection # => Collection with 2 items
|
|
44
|
+
class Factory
|
|
45
|
+
# @return [Treaty::Action::Inventory::Collection] Collection of inventory items
|
|
46
|
+
attr_reader :collection
|
|
47
|
+
|
|
48
|
+
# Creates a new factory instance
|
|
49
|
+
#
|
|
50
|
+
# @param action_name [Symbol] Controller action name (for error messages)
|
|
51
|
+
def initialize(action_name)
|
|
52
|
+
@action_name = action_name
|
|
53
|
+
@collection = Collection.new
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Handles DSL method calls (only `provide` is supported)
|
|
57
|
+
#
|
|
58
|
+
# Creates an Inventory item and adds it to the collection.
|
|
59
|
+
# Validates that only `provide` method is called and name is a Symbol.
|
|
60
|
+
#
|
|
61
|
+
# @param method_name [Symbol] Method name (must be :provide)
|
|
62
|
+
# @param args [Array] Arguments (first must be inventory name as Symbol)
|
|
63
|
+
# @param options [Hash] Options (:from for source)
|
|
64
|
+
# @raise [Treaty::Exceptions::Inventory] If method is not `provide`
|
|
65
|
+
# @raise [Treaty::Exceptions::Inventory] If name is not a Symbol
|
|
66
|
+
# @return [Treaty::Action::Inventory::Collection] Updated collection
|
|
67
|
+
def method_missing(method_name, *args, **options, &_block) # rubocop:disable Metrics/MethodLength
|
|
68
|
+
unless method_name == :provide
|
|
69
|
+
raise Treaty::Exceptions::Inventory,
|
|
70
|
+
I18n.t(
|
|
71
|
+
"treaty.inventory.unknown_method",
|
|
72
|
+
method: method_name,
|
|
73
|
+
action: @action_name
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
inventory_name = args.first
|
|
78
|
+
|
|
79
|
+
unless inventory_name.is_a?(Symbol)
|
|
80
|
+
raise Treaty::Exceptions::Inventory,
|
|
81
|
+
I18n.t(
|
|
82
|
+
"treaty.inventory.name_must_be_symbol",
|
|
83
|
+
name: inventory_name.inspect
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
source = if options.key?(:from)
|
|
88
|
+
options.fetch(:from)
|
|
89
|
+
else
|
|
90
|
+
inventory_name
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
@collection << Inventory.new(name: inventory_name, source:)
|
|
94
|
+
|
|
95
|
+
@collection
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Checks if method should be handled by method_missing
|
|
99
|
+
#
|
|
100
|
+
# @param method_name [Symbol] Method name
|
|
101
|
+
# @return [Boolean] True only for :provide
|
|
102
|
+
def respond_to_missing?(method_name, *)
|
|
103
|
+
method_name == :provide || super
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|