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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/treaty/action/base.rb +11 -0
  4. data/lib/treaty/action/context/callable.rb +90 -0
  5. data/lib/treaty/action/context/dsl.rb +56 -0
  6. data/lib/treaty/action/context/workspace.rb +92 -0
  7. data/lib/treaty/action/executor/inventory.rb +136 -0
  8. data/lib/treaty/{info/rest → action/info}/builder.rb +2 -2
  9. data/lib/treaty/{info/rest → action/info}/dsl.rb +2 -2
  10. data/lib/treaty/{info/rest → action/info}/result.rb +2 -2
  11. data/lib/treaty/action/inventory/collection.rb +77 -0
  12. data/lib/treaty/action/inventory/factory.rb +108 -0
  13. data/lib/treaty/action/inventory/inventory.rb +146 -0
  14. data/lib/treaty/action/request/attribute/attribute.rb +76 -0
  15. data/lib/treaty/action/request/attribute/builder.rb +98 -0
  16. data/lib/treaty/action/request/entity.rb +78 -0
  17. data/lib/treaty/action/request/factory.rb +116 -0
  18. data/lib/treaty/action/request/validator.rb +120 -0
  19. data/lib/treaty/action/response/attribute/attribute.rb +79 -0
  20. data/lib/treaty/action/response/attribute/builder.rb +96 -0
  21. data/lib/treaty/action/response/entity.rb +79 -0
  22. data/lib/treaty/action/response/factory.rb +129 -0
  23. data/lib/treaty/action/response/validator.rb +111 -0
  24. data/lib/treaty/action/result.rb +81 -0
  25. data/lib/treaty/action/versions/collection.rb +47 -0
  26. data/lib/treaty/action/versions/dsl.rb +116 -0
  27. data/lib/treaty/action/versions/execution/request.rb +287 -0
  28. data/lib/treaty/action/versions/executor.rb +61 -0
  29. data/lib/treaty/action/versions/factory.rb +253 -0
  30. data/lib/treaty/action/versions/resolver.rb +150 -0
  31. data/lib/treaty/action/versions/semantic.rb +64 -0
  32. data/lib/treaty/action/versions/workspace.rb +106 -0
  33. data/lib/treaty/action.rb +31 -0
  34. data/lib/treaty/controller/dsl.rb +1 -1
  35. data/lib/treaty/entity/attribute/base.rb +1 -1
  36. data/lib/treaty/entity/attribute/builder/base.rb +1 -1
  37. data/lib/treaty/entity/attribute/dsl.rb +1 -1
  38. data/lib/treaty/entity/base.rb +1 -1
  39. data/lib/treaty/entity/builder.rb +62 -5
  40. data/lib/treaty/version.rb +1 -1
  41. metadata +32 -31
  42. data/lib/treaty/base.rb +0 -9
  43. data/lib/treaty/context/callable.rb +0 -26
  44. data/lib/treaty/context/dsl.rb +0 -12
  45. data/lib/treaty/context/workspace.rb +0 -32
  46. data/lib/treaty/executor/inventory.rb +0 -122
  47. data/lib/treaty/inventory/collection.rb +0 -71
  48. data/lib/treaty/inventory/factory.rb +0 -91
  49. data/lib/treaty/inventory/inventory.rb +0 -92
  50. data/lib/treaty/request/attribute/attribute.rb +0 -25
  51. data/lib/treaty/request/attribute/builder.rb +0 -46
  52. data/lib/treaty/request/entity.rb +0 -33
  53. data/lib/treaty/request/factory.rb +0 -81
  54. data/lib/treaty/request/validator.rb +0 -60
  55. data/lib/treaty/response/attribute/attribute.rb +0 -25
  56. data/lib/treaty/response/attribute/builder.rb +0 -46
  57. data/lib/treaty/response/entity.rb +0 -33
  58. data/lib/treaty/response/factory.rb +0 -87
  59. data/lib/treaty/response/validator.rb +0 -53
  60. data/lib/treaty/result.rb +0 -23
  61. data/lib/treaty/versions/collection.rb +0 -15
  62. data/lib/treaty/versions/dsl.rb +0 -42
  63. data/lib/treaty/versions/execution/request.rb +0 -177
  64. data/lib/treaty/versions/executor.rb +0 -14
  65. data/lib/treaty/versions/factory.rb +0 -112
  66. data/lib/treaty/versions/resolver.rb +0 -70
  67. data/lib/treaty/versions/semantic.rb +0 -22
  68. data/lib/treaty/versions/workspace.rb +0 -43
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c7b03c7d0eed4f560087ed9662f711e50ec947a78357d687ea2be29ddd9b7872
4
- data.tar.gz: c9b3b121e50d75518e4db1a0b3c388f36334cce4b9995aefb5b24e3101e5d984
3
+ metadata.gz: 395c83deb4d618293b99588b6130e9529febdb028d2db017ee27181479371a80
4
+ data.tar.gz: c0b2b1e546542caeee840f105aa0752ae6d84067e78a494c5429204dd2ed3052
5
5
  SHA512:
6
- metadata.gz: 13ca45c37ea6988a5b046a8c7825cf11acfd62fbc5c32d120888c66623da24f52dbea656120a71e815018765573aa3b1c05370bba356fc4913dd02ec342022c6
7
- data.tar.gz: 431831fb76b2a44997aef884aa3662d7cff2a474f13a308472af78cd298471776f001454b98d02ed38a11e3d9f9f84ff2e5034f77004533d092483fd4b8f6420
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.19.0"`) until the 1.0 release.
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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Action
5
+ class Base
6
+ include Info::DSL
7
+ include Context::DSL
8
+ include Versions::DSL
9
+ end
10
+ end
11
+ end
@@ -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
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Treaty
4
- module Info
5
- module Rest
4
+ module Action
5
+ module Info
6
6
  class Builder
7
7
  attr_reader :versions
8
8
 
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Treaty
4
- module Info
5
- module Rest
4
+ module Action
5
+ module Info
6
6
  module DSL
7
7
  def self.included(base)
8
8
  base.extend(ClassMethods)
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Treaty
4
- module Info
5
- module Rest
4
+ module Action
5
+ module Info
6
6
  class Result
7
7
  attr_reader :versions
8
8
 
@@ -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