interactor_support 1.0.4 → 1.0.6
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 +14 -0
- data/README.md +67 -1
- data/lib/interactor_support/concerns/organizable.rb +15 -1
- data/lib/interactor_support/configuration.rb +22 -0
- data/lib/interactor_support/errors.rb +56 -0
- data/lib/interactor_support/request_object.rb +49 -1
- data/lib/interactor_support/response_object.rb +0 -0
- data/lib/interactor_support/version.rb +1 -1
- data/lib/interactor_support.rb +1 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bf8cda13999c9971a1499398e23135ef6e870af7581ffe7e9f173b9d7c4231ea
|
|
4
|
+
data.tar.gz: 48c401b5b42e80ccea3db170ecb1e8476861522804b800ad28651eebfa674222
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2adf5f317e6432571a24358e9e1ba453397fcfd5f6b83758930fd45bef652dfb01ead87aa68fcfec385151bd60646127a087968e332baa0193e5bb9f7ac14ff6
|
|
7
|
+
data.tar.gz: a6a670cacb4abd50812a82232dcbeb1e582eea72d3526b2659f4dccd827226c6e07c6b12f6c39b282eb276db482d02911cd4d0e125094f8c1d114fba5459c899
|
data/CHANGELOG.md
CHANGED
|
@@ -21,3 +21,17 @@
|
|
|
21
21
|
## [1.0.4] - 2025-04-05
|
|
22
22
|
|
|
23
23
|
- Added the organizable concern
|
|
24
|
+
|
|
25
|
+
## [1.0.5] - 2025-06-30
|
|
26
|
+
|
|
27
|
+
- Add support for ignoring unknown attributes in RequestObjects via `ignore_unknown_attributes` class method
|
|
28
|
+
- Introduce `InteractorSupport.configuration.log_unknown_request_object_attributes` to optionally log ignored attributes
|
|
29
|
+
- Introduce `InteractorSupport.configuration.logger` and `log_level` for customizable logging
|
|
30
|
+
- Override `assign_attributes` to integrate attribute ignoring and error-raising behavior
|
|
31
|
+
- Improve test coverage for unknown attribute handling and logging
|
|
32
|
+
|
|
33
|
+
## [1.0.6] - 2025-09-24
|
|
34
|
+
|
|
35
|
+
- Wrap request object validation failures from `Organizable#organize` in `InteractorSupport::Errors::InvalidRequestObject` for consistent controller handling
|
|
36
|
+
- Honor `configuration.log_unknown_request_object_attributes` when logging ignored request object keys
|
|
37
|
+
- Improve request object error messaging for failed casts and unknown attributes
|
data/README.md
CHANGED
|
@@ -148,6 +148,7 @@ Instead of raw hashes, **Request Objects** provide **validation, transformation,
|
|
|
148
148
|
- Works just like an **ActiveRecord model**
|
|
149
149
|
- Supports **validations** out of the box
|
|
150
150
|
- Automatically **transforms & sanitizes** data
|
|
151
|
+
- Gracefully ignores unknown attributes if configured, with optional logging
|
|
151
152
|
|
|
152
153
|
```ruby
|
|
153
154
|
class TodoRequest
|
|
@@ -162,6 +163,31 @@ class TodoRequest
|
|
|
162
163
|
end
|
|
163
164
|
```
|
|
164
165
|
|
|
166
|
+
### 🔍 Ignoring Unknown Attributes
|
|
167
|
+
|
|
168
|
+
To prevent `RequestObject` from raising an error on unexpected keys, declare `ignore_unknown_attributes` in your class.
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
class CalendarRequest
|
|
172
|
+
include InteractorSupport::RequestObject
|
|
173
|
+
|
|
174
|
+
ignore_unknown_attributes
|
|
175
|
+
|
|
176
|
+
attribute :start_date, type: :date
|
|
177
|
+
attribute :end_date, type: :date
|
|
178
|
+
attribute :timezone, transform: :strip
|
|
179
|
+
|
|
180
|
+
validates :start_date, :end_date, presence: true
|
|
181
|
+
end
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Now, if you initialize the request with an unexpected attribute, it will be logged (if logging is enabled) and ignored:
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
CalendarRequest.new(start_date: "2025-07-01", end_date: "2025-07-02", foo: "bar")
|
|
188
|
+
# => No exception raised; 'foo' is ignored.
|
|
189
|
+
```
|
|
190
|
+
|
|
165
191
|
---
|
|
166
192
|
|
|
167
193
|
## 📖 **Documentation**
|
|
@@ -533,7 +559,7 @@ The Organizable concern provides utility methods to simplify working with intera
|
|
|
533
559
|
|
|
534
560
|
Features
|
|
535
561
|
|
|
536
|
-
- organize: Call interactors with request objects, optionally namespaced under a context_key.
|
|
562
|
+
- organize: Call interactors with request objects, optionally namespaced under a context_key, and wrap validation failures in a consistent error.
|
|
537
563
|
- request_params: Extract, shape, filter, rename, flatten, and merge incoming params in a clear and declarative way.
|
|
538
564
|
- Built for controllers or service entry points.
|
|
539
565
|
- Rails-native feel — works seamlessly with strong params.
|
|
@@ -562,6 +588,18 @@ organize(MyInteractor, params: request_params, request_object: MyRequest, contex
|
|
|
562
588
|
# => MyInteractor.call({ request: MyRequest.new(params) })
|
|
563
589
|
```
|
|
564
590
|
|
|
591
|
+
Validation failures inside the request object raise `InteractorSupport::Errors::InvalidRequestObject`. The exception exposes the request class and its validation messages, making it straightforward to surface errors back to the caller.
|
|
592
|
+
|
|
593
|
+
```rb
|
|
594
|
+
def create
|
|
595
|
+
organize(CreateUserInteractor, params: request_params(:user), request_object: CreateUserRequest)
|
|
596
|
+
redirect_to dashboard_path
|
|
597
|
+
rescue InteractorSupport::Errors::InvalidRequestObject => e
|
|
598
|
+
flash.now[:alert] = "Unable to continue: #{e.errors.to_sentence}"
|
|
599
|
+
render :new, status: :unprocessable_entity
|
|
600
|
+
end
|
|
601
|
+
```
|
|
602
|
+
|
|
565
603
|
#### #request_params(\*top_level_keys, merge: {}, except: [], rewrite: [])
|
|
566
604
|
|
|
567
605
|
Returns a shaped parameter hash derived from params.permit!. You can extract specific top-level keys, rename them, flatten values, apply defaults, and remove unwanted fields.
|
|
@@ -652,6 +690,34 @@ class ApplicationController < ActionController::Base
|
|
|
652
690
|
end
|
|
653
691
|
```
|
|
654
692
|
|
|
693
|
+
---
|
|
694
|
+
|
|
695
|
+
## 🛠 Configuration Reference
|
|
696
|
+
|
|
697
|
+
All global settings for InteractorSupport can be set via the `InteractorSupport.configure` block. Here's a full list of configuration options:
|
|
698
|
+
|
|
699
|
+
<!-- prettier-ignore-start -->
|
|
700
|
+
| Key | Type | Default | Description |
|
|
701
|
+
| --- | ---- | ------- | -----------|
|
|
702
|
+
| `logger` | `Logger` | `Logger.new($stdout)` | Logger instance used for internal logging. |
|
|
703
|
+
| `log_level` | `Integer` (Logger constant) | `Logger::INFO` | Logging level (e.g., `Logger::DEBUG`, `Logger::WARN`, etc.). |
|
|
704
|
+
| `log_unknown_request_object_attributes` | `Boolean` | `true` | Whether to log unknown request attributes that are ignored. |
|
|
705
|
+
| `request_object_behavior` | `Symbol` | `:returns_context` | Controls what `RequestObject.new(...)` returns (`:returns_self` or `:returns_context`). |
|
|
706
|
+
| `request_object_key_type` | `Symbol` | `:symbol` | Controls the output format of keys in `#to_context` (`:symbol`, `:string`, `:struct`). |
|
|
707
|
+
<!-- prettier-ignore-end -->
|
|
708
|
+
|
|
709
|
+
To update these settings, use:
|
|
710
|
+
|
|
711
|
+
```ruby
|
|
712
|
+
InteractorSupport.configure do |config|
|
|
713
|
+
config.logger = Rails.logger
|
|
714
|
+
config.log_level = Logger::WARN
|
|
715
|
+
config.log_unknown_request_object_attributes = true
|
|
716
|
+
config.request_object_behavior = :returns_self
|
|
717
|
+
config.request_object_key_type = :struct
|
|
718
|
+
end
|
|
719
|
+
```
|
|
720
|
+
|
|
655
721
|
## 🤝 **Contributing**
|
|
656
722
|
|
|
657
723
|
Pull requests are welcome on [GitHub](https://github.com/charliemitchell/interactor_support).
|
|
@@ -38,8 +38,22 @@ module InteractorSupport
|
|
|
38
38
|
# # => Calls MyInteractor with an instance of MyRequest initialized with request_params at :context_key.
|
|
39
39
|
# # # => The context will contain { request: MyRequest.new(request_params) }
|
|
40
40
|
def organize(interactor, params:, request_object:, context_key: nil)
|
|
41
|
+
request_payload = request_object.new(params)
|
|
42
|
+
|
|
41
43
|
@context = interactor.call(
|
|
42
|
-
context_key ? { context_key =>
|
|
44
|
+
context_key ? { context_key => request_payload } : request_payload,
|
|
45
|
+
)
|
|
46
|
+
rescue ActiveModel::ValidationError => e
|
|
47
|
+
errors =
|
|
48
|
+
if e.model&.respond_to?(:errors)
|
|
49
|
+
e.model.errors.full_messages
|
|
50
|
+
else
|
|
51
|
+
[]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
raise InteractorSupport::Errors::InvalidRequestObject.new(
|
|
55
|
+
request_class: request_object,
|
|
56
|
+
errors: errors,
|
|
43
57
|
)
|
|
44
58
|
end
|
|
45
59
|
|
|
@@ -29,13 +29,35 @@ module InteractorSupport
|
|
|
29
29
|
# @return [:string, :symbol, :struct]
|
|
30
30
|
attr_accessor :request_object_key_type
|
|
31
31
|
|
|
32
|
+
##
|
|
33
|
+
# Logger for InteractorSupport, defaults to STDOUT.
|
|
34
|
+
# @return [Logger]
|
|
35
|
+
attr_accessor :logger
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# The log level for InteractorSupport logs.
|
|
39
|
+
# @return [Integer]
|
|
40
|
+
attr_accessor :log_level
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
# Whether to log unknown request object attributes when they are ignored.
|
|
44
|
+
# If true, logs a warning when an unknown attribute is encountered.
|
|
45
|
+
# @see InteractorSupport::RequestObject#ignore_unknown_attributes
|
|
46
|
+
attr_accessor :log_unknown_request_object_attributes
|
|
47
|
+
|
|
32
48
|
##
|
|
33
49
|
# Initializes the configuration with default values:
|
|
34
50
|
# - `request_object_behavior` defaults to `:returns_context`
|
|
35
51
|
# - `request_object_key_type` defaults to `:symbol`
|
|
52
|
+
# - `logger` defaults to a new Logger instance writing to STDOUT
|
|
53
|
+
# - `log_level` defaults to `Logger::INFO`
|
|
54
|
+
# - `log_unknown_request_object_attributes` defaults to `true`
|
|
36
55
|
def initialize
|
|
37
56
|
@request_object_behavior = :returns_context
|
|
38
57
|
@request_object_key_type = :symbol
|
|
58
|
+
@logger = Logger.new($stdout)
|
|
59
|
+
@log_level = Logger::INFO
|
|
60
|
+
@log_unknown_request_object_attributes = true
|
|
39
61
|
end
|
|
40
62
|
end
|
|
41
63
|
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module InteractorSupport
|
|
2
|
+
module Errors
|
|
3
|
+
class UnknownAttribute < StandardError
|
|
4
|
+
attr_reader :attribute, :owner
|
|
5
|
+
|
|
6
|
+
def initialize(attribute, owner: nil)
|
|
7
|
+
@attribute = attribute
|
|
8
|
+
@owner = owner
|
|
9
|
+
super(build_message(attribute, owner))
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def build_message(attribute, owner)
|
|
15
|
+
name =
|
|
16
|
+
case attribute
|
|
17
|
+
when String then attribute
|
|
18
|
+
when Symbol then attribute.to_s
|
|
19
|
+
else attribute.inspect
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
owner_name =
|
|
23
|
+
if owner.respond_to?(:name) && owner.name
|
|
24
|
+
owner.name
|
|
25
|
+
elsif owner.respond_to?(:to_s)
|
|
26
|
+
owner.to_s
|
|
27
|
+
else
|
|
28
|
+
owner
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
suffix = owner_name ? " for #{owner_name}" : ''
|
|
32
|
+
"Unknown attribute: #{name}#{suffix}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class InvalidRequestObject < StandardError
|
|
37
|
+
attr_reader :request_class, :errors
|
|
38
|
+
|
|
39
|
+
def initialize(request_class:, errors: [])
|
|
40
|
+
@request_class = request_class
|
|
41
|
+
@errors = Array(errors)
|
|
42
|
+
|
|
43
|
+
request_name =
|
|
44
|
+
if request_class.respond_to?(:name) && request_class.name
|
|
45
|
+
request_class.name
|
|
46
|
+
else
|
|
47
|
+
request_class.to_s
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
detail = @errors.any? ? ": #{@errors.join(', ')}" : ''
|
|
51
|
+
|
|
52
|
+
super("Invalid #{request_name}#{detail}")
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -40,6 +40,7 @@ module InteractorSupport
|
|
|
40
40
|
included do
|
|
41
41
|
include ActiveModel::Model
|
|
42
42
|
include ActiveModel::Attributes
|
|
43
|
+
include ActiveModel::AttributeAssignment
|
|
43
44
|
include ActiveModel::Validations::Callbacks
|
|
44
45
|
|
|
45
46
|
##
|
|
@@ -88,6 +89,34 @@ module InteractorSupport
|
|
|
88
89
|
attrs
|
|
89
90
|
end
|
|
90
91
|
|
|
92
|
+
##
|
|
93
|
+
# Assigns the given attributes to the request object.
|
|
94
|
+
#
|
|
95
|
+
# - Known attributes are assigned normally via their setters.
|
|
96
|
+
# - If `ignore_unknown_attributes?` is defined and true, unknown keys are ignored and logged.
|
|
97
|
+
# - Otherwise, raises `Errors::UnknownAttribute`.
|
|
98
|
+
#
|
|
99
|
+
# @param attrs [Hash] input attributes to assign
|
|
100
|
+
# @raise [Errors::UnknownAttribute] if unknown attribute is encountered and not ignored
|
|
101
|
+
# @return [void]
|
|
102
|
+
def assign_attributes(attrs)
|
|
103
|
+
attrs.each do |k, v|
|
|
104
|
+
setter = "#{k}="
|
|
105
|
+
if respond_to?(setter)
|
|
106
|
+
send(setter, v)
|
|
107
|
+
elsif respond_to?(:ignore_unknown_attributes?) && ignore_unknown_attributes?
|
|
108
|
+
if InteractorSupport.configuration.log_unknown_request_object_attributes
|
|
109
|
+
InteractorSupport.configuration.logger.log(
|
|
110
|
+
InteractorSupport.configuration.log_level,
|
|
111
|
+
"InteractorSupport::RequestObject ignoring unknown attribute '#{k}' for #{self.class.name}.",
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
else
|
|
115
|
+
raise Errors::UnknownAttribute.new(k, owner: self.class)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
91
120
|
class << self
|
|
92
121
|
##
|
|
93
122
|
# Custom constructor that optionally returns the context instead of the object itself.
|
|
@@ -103,6 +132,19 @@ module InteractorSupport
|
|
|
103
132
|
super(*args, **kwargs).to_context
|
|
104
133
|
end
|
|
105
134
|
|
|
135
|
+
##
|
|
136
|
+
# Defines whether to ignore unknown attributes during assignment.
|
|
137
|
+
# If true, unknown attributes are logged but not raised as errors.
|
|
138
|
+
# @example
|
|
139
|
+
# class MyRequest
|
|
140
|
+
# include InteractorSupport::RequestObject
|
|
141
|
+
# ignore_unknown_attributes
|
|
142
|
+
# end
|
|
143
|
+
# @return [void]
|
|
144
|
+
def ignore_unknown_attributes
|
|
145
|
+
define_method(:ignore_unknown_attributes?) { true }
|
|
146
|
+
end
|
|
147
|
+
|
|
106
148
|
##
|
|
107
149
|
# Defines one or more attributes with optional coercion, default values, transformation,
|
|
108
150
|
# and an optional `rewrite:` key to rename the underlying attribute.
|
|
@@ -192,7 +234,13 @@ module InteractorSupport
|
|
|
192
234
|
message = ":#{type} is not a supported type. Supported types are: #{SUPPORTED_TYPES.join(", ")}"
|
|
193
235
|
raise TypeError, message
|
|
194
236
|
rescue
|
|
195
|
-
|
|
237
|
+
type_name =
|
|
238
|
+
if type.respond_to?(:name) && type.name
|
|
239
|
+
type.name
|
|
240
|
+
else
|
|
241
|
+
type.to_s
|
|
242
|
+
end
|
|
243
|
+
raise TypeError, "Cannot cast #{value.inspect} to #{type_name}"
|
|
196
244
|
end
|
|
197
245
|
end
|
|
198
246
|
end
|
|
File without changes
|
data/lib/interactor_support.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: interactor_support
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Charlie Mitchell
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-09-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description:
|
|
14
14
|
email:
|
|
@@ -37,7 +37,9 @@ files:
|
|
|
37
37
|
- lib/interactor_support/concerns/updatable.rb
|
|
38
38
|
- lib/interactor_support/configuration.rb
|
|
39
39
|
- lib/interactor_support/core.rb
|
|
40
|
+
- lib/interactor_support/errors.rb
|
|
40
41
|
- lib/interactor_support/request_object.rb
|
|
42
|
+
- lib/interactor_support/response_object.rb
|
|
41
43
|
- lib/interactor_support/rubocop.rb
|
|
42
44
|
- lib/interactor_support/rubocop/cop/base_interactor_cop.rb
|
|
43
45
|
- lib/interactor_support/rubocop/cop/require_required_for_interactor_support.rb
|