interactor_support 1.0.3 → 1.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b479b98d9029e0865ab4526efcc08ec47736ecba96547d2ab9be294f54d2880
4
- data.tar.gz: 74f5d2a0024afb6b7c49ff153a339d6373d25072d37e546827407e65cfd0bc8d
3
+ metadata.gz: 315dd265352ed997c5b69934609478e6e5f8b61bbe74bba065acf495f7dac41c
4
+ data.tar.gz: 6a942d0ccb3f3323d156d85f302987b4af7809cdcce852dca56fb9b1ae8419bc
5
5
  SHA512:
6
- metadata.gz: e1d24d1b7fe20316f26d14f029387d90e1c88e2dc9654500dd40ae9546e030121efcb5de991d091b752308f0f976533a4dabaebc3cb2bebc269b7f5ca70ab7d3
7
- data.tar.gz: 32c320144c9f90dc53769bd104a308d17ce663e8618cc0f55f8435d89835ed3aa2cb3049df023422de0d34068ced12795e12127e8eb976f18a3db8a56c0e759a
6
+ metadata.gz: 2fb25732876e4702b12da283cf4d205504c597ecb904cc9406af3a9825814e8ba518e84bcc615c1c8aadc31ed81cfb9008e2addf759273c2688e029d5b07628e
7
+ data.tar.gz: 5615bf986b8ab272cff775eebbd874c336be93f4e17333d37fd6e87a04b9d74d110b2fcab2a48fb1e0ff86956785b4a7825d03f4201701700566825c2335c633
data/CHANGELOG.md CHANGED
@@ -17,3 +17,15 @@
17
17
  - Added support for rewriting attribute names in a request object
18
18
  - Better support for type coersion, using Active model + Array, Hash, and Symbol
19
19
  - Better support for `AnyClass` type validations
20
+
21
+ ## [1.0.4] - 2025-04-05
22
+
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
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**
@@ -527,6 +553,159 @@ UserRequest.new(params.require(:user).permit(:name, :email, :age))
527
553
 
528
554
  But with RequestObject, that’s often unnecessary because you’re already defining a schema.
529
555
 
556
+ ## InteractorSupport::Organizable
557
+
558
+ The Organizable concern provides utility methods to simplify working with interactors and request objects. It gives you a clean and consistent pattern for extracting, transforming, and preparing parameters for use in service objects or interactors.
559
+
560
+ Features
561
+
562
+ - organize: Call interactors with request objects, optionally namespaced under a context_key.
563
+ - request_params: Extract, shape, filter, rename, flatten, and merge incoming params in a clear and declarative way.
564
+ - Built for controllers or service entry points.
565
+ - Rails-native feel — works seamlessly with strong params.
566
+
567
+ #### API Reference
568
+
569
+ **#organize(interactor, params:, request_object:, context_key: nil)**
570
+ Calls the given interactor with a request object built from the provided params.
571
+
572
+ | Argument | Type | Description |
573
+ | -------------- | ------------- | --------------------------------------------------------------------------- |
574
+ | interactor | Class | The interactor to call (.call must be defined). |
575
+ | params | Hash | Parameters passed to the request object. |
576
+ | request_object | Class | A request object class that accepts params in its initializer. |
577
+ | context_key | Symbol or nil | Optional key to namespace the request object inside the interactor context. |
578
+
579
+ Examples
580
+
581
+ ```rb
582
+ organize(MyInteractor, params: request_params, request_object: MyRequest)
583
+
584
+ # => MyInteractor.call(MyRequest.new(params))
585
+
586
+ organize(MyInteractor, params: request_params, request_object: MyRequest, context_key: :request)
587
+
588
+ # => MyInteractor.call({ request: MyRequest.new(params) })
589
+ ```
590
+
591
+ #### #request_params(\*top_level_keys, merge: {}, except: [], rewrite: [])
592
+
593
+ 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.
594
+
595
+ | Argument | Type | Description |
596
+ | ----------------- | -------------------------------- | ------------------------------------------------------------------------- |
597
+ | `*top_level_keys` | `Symbol...` | Optional list of top-level keys to include. If omitted, includes all. |
598
+ | `merge:` | `Hash` | Extra values to merge into the result. |
599
+ | `except:` | `Array<Symbol or Array<Symbol>>` | Keys or nested key paths to exclude. |
600
+ | `rewrite:` | `Array<Hash>` | Rules for renaming, flattening, filtering, merging, or defaulting values. |
601
+
602
+ Rewrite Options
603
+
604
+ Each rewrite entry is a hash in the form { key => options }, where options may include:
605
+ | Option | Type | Description |
606
+ |-----------|---------------------------|-------------------------------------------------------------------|
607
+ | `as` | `Symbol` | Rename the key to a new top-level key. |
608
+ | `only` | `Array<Symbol>` | Include only these subkeys in the result. |
609
+ | `except` | `Array<Symbol>` | Remove these subkeys from the result. |
610
+ | `flatten` | `true` or `Array<Symbol>` | Flatten all subkeys into top-level (or just the specified ones). |
611
+ | `default` | `Hash` | Use this value if the original key is missing or nil. |
612
+ | `merge` | `Hash` | Merge this hash into the result (after filtering and flattening). |
613
+
614
+ Example: full usage
615
+
616
+ ```rb
617
+ # Incoming params:
618
+ params = {
619
+ order: {
620
+ product_id: 1,
621
+ quantity: 2,
622
+ internal: "should be removed"
623
+ },
624
+ metadata: {
625
+ source: "mobile",
626
+ internal: "hidden",
627
+ location: { ip: "1.2.3.4" }
628
+ },
629
+ flags: {
630
+ foo: true
631
+ },
632
+ internal: "global_internal",
633
+ session: nil
634
+ }
635
+
636
+ # Incantation:
637
+ request_params(:order, :metadata, :flags, :session,
638
+ merge: { user: current_user }, # <- Add the user
639
+ except: [[:order, :internal], :internal], # <- remove `order.internal`, and the top level key `internal`
640
+ rewrite: [
641
+ { order: { flatten: true } }, # <- moves all the values from order to top level keys
642
+ { metadata: { as: :meta, only: [:source, :location], flatten: [:location] } }, # <- Rename metadata to meta, pluck source and location, move location's values to meta
643
+ { flags: { merge: { debug: true } } }, # <- add flags.debug = true
644
+ { session: { default: { id: nil } } } # <- create a default value for session
645
+ ]
646
+ )
647
+
648
+ # Result
649
+ {
650
+ product_id: 1,
651
+ quantity: 2,
652
+ meta: {
653
+ source: "mobile",
654
+ ip: "1.2.3.4"
655
+ },
656
+ flags: {
657
+ foo: true,
658
+ debug: true
659
+ },
660
+ session: {
661
+ id: nil
662
+ },
663
+ user: current_user
664
+ }
665
+ ```
666
+
667
+ ⚠️ Array flattening is not supported
668
+
669
+ Flattening arrays of hashes (e.g., { events: [{ id: 1 }] }) is intentionally not supported to avoid accidental key collisions. If needed, transform such structures manually before passing to request_params.
670
+
671
+ **Usage**
672
+
673
+ Include in a controller or service base class
674
+
675
+ ```rb
676
+ class ApplicationController < ActionController::Base
677
+ include InteractorSupport::Concerns::Organizable
678
+ end
679
+ ```
680
+
681
+ ---
682
+
683
+ ## 🛠 Configuration Reference
684
+
685
+ All global settings for InteractorSupport can be set via the `InteractorSupport.configure` block. Here's a full list of configuration options:
686
+
687
+ <!-- prettier-ignore-start -->
688
+ | Key | Type | Default | Description |
689
+ | --- | ---- | ------- | -----------|
690
+ | `logger` | `Logger` | `Logger.new($stdout)` | Logger instance used for internal logging. |
691
+ | `log_level` | `Integer` (Logger constant) | `Logger::INFO` | Logging level (e.g., `Logger::DEBUG`, `Logger::WARN`, etc.). |
692
+ | `log_unknown_request_object_attributes` | `Boolean` | `true` | Whether to log unknown request attributes that are ignored. |
693
+ | `request_object_behavior` | `Symbol` | `:returns_context` | Controls what `RequestObject.new(...)` returns (`:returns_self` or `:returns_context`). |
694
+ | `request_object_key_type` | `Symbol` | `:symbol` | Controls the output format of keys in `#to_context` (`:symbol`, `:string`, `:struct`). |
695
+ <!-- prettier-ignore-end -->
696
+
697
+ To update these settings, use:
698
+
699
+ ```ruby
700
+ InteractorSupport.configure do |config|
701
+ config.logger = Rails.logger
702
+ config.log_level = Logger::WARN
703
+ config.log_unknown_request_object_attributes = true
704
+ config.request_object_behavior = :returns_self
705
+ config.request_object_key_type = :struct
706
+ end
707
+ ```
708
+
530
709
  ## 🤝 **Contributing**
531
710
 
532
711
  Pull requests are welcome on [GitHub](https://github.com/charliemitchell/interactor_support).
@@ -7,11 +7,11 @@ module InteractorSupport
7
7
  # This module is intended to be included into an `Interactor` or `Organizer`,
8
8
  # providing access to a suite of declarative action helpers:
9
9
  #
10
- # - {Skippable} — Conditionally skip execution
11
- # - {Transactionable} — Wrap logic in an ActiveRecord transaction
12
- # - {Updatable} — Update records using context-driven attributes
13
- # - {Findable} — Find one or many records into context
14
- # - {Transformable} — Normalize or modify context values before execution
10
+ # - {InteractorSupport::Concerns::Skippable} — Conditionally skip execution
11
+ # - {InteractorSupport::Concerns::Transactionable} — Wrap logic in an ActiveRecord transaction
12
+ # - {InteractorSupport::Concerns::Updatable} — Update records using context-driven attributes
13
+ # - {InteractorSupport::Concerns::Findable} — Find one or many records into context
14
+ # - {InteractorSupport::Concerns::Transformable} — Normalize or modify context values before execution
15
15
  #
16
16
  # @example Use in an interactor
17
17
  # class UpdateUser
@@ -25,6 +25,7 @@ module InteractorSupport
25
25
  # update :user, attributes: { email: :email }
26
26
  # end
27
27
  #
28
+ #
28
29
  # @see InteractorSupport::Concerns::Skippable
29
30
  # @see InteractorSupport::Concerns::Transactionable
30
31
  # @see InteractorSupport::Concerns::Updatable
@@ -32,6 +33,7 @@ module InteractorSupport
32
33
  # @see InteractorSupport::Concerns::Transformable
33
34
  module Actions
34
35
  extend ActiveSupport::Concern
36
+
35
37
  included do
36
38
  include InteractorSupport::Concerns::Skippable
37
39
  include InteractorSupport::Concerns::Transactionable
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InteractorSupport
4
+ module Concerns
5
+ ##
6
+ # The `Organizable` module provides utility methods for organizing interactors
7
+ # and shaping request parameters in a structured way.
8
+ #
9
+ # It is intended to be included into a controller or a base service class that
10
+ # delegates to interactors using request objects.
11
+ #
12
+ # @example Include in a controller
13
+ # class ApplicationController < ActionController::Base
14
+ # include InteractorSupport::Organizable
15
+ # end
16
+ #
17
+ # @see InteractorSupport::Organizable#organize
18
+ # @see InteractorSupport::Organizable#request_params
19
+ module Organizable
20
+ include ActiveSupport::Concern
21
+
22
+ # Calls the given interactor with a request object.
23
+ # Optionally wraps the request object under a key in the interactor context.
24
+ #
25
+ # @param interactor [Class] The interactor class to call.
26
+ # @param params [Hash] Parameters to initialize the request object.
27
+ # @param request_object [Class] A request object class that responds to `#new(params)`.
28
+ # @param context_key [Symbol, nil] Optional key to assign the request object under in the context.
29
+ #
30
+ # @return [void]
31
+ #
32
+ # @example
33
+ # organize(MyInteractor, params: request_params, request_object: MyRequest)
34
+ # # => Calls MyInteractor with an instance of MyRequest initialized with request_params.
35
+ #
36
+ # @example
37
+ # organize(MyInteractor, params: request_params, request_object: MyRequest, context_key: :request)
38
+ # # => Calls MyInteractor with an instance of MyRequest initialized with request_params at :context_key.
39
+ # # # => The context will contain { request: MyRequest.new(request_params) }
40
+ def organize(interactor, params:, request_object:, context_key: nil)
41
+ @context = interactor.call(
42
+ context_key ? { context_key => request_object.new(params) } : request_object.new(params),
43
+ )
44
+ end
45
+
46
+ # Builds a structured and optionally transformed parameter hash from Rails' `params`.
47
+ #
48
+ # This method supports extracting specific top-level keys, applying optional rewrite
49
+ # transformations, merging in additional values, and excluding unwanted keys.
50
+ #
51
+ # @param top_level_keys [Array<Symbol>] Top-level keys to extract from `params`. If empty, all keys are included.
52
+ # @param merge [Hash] Additional values to merge into the final result.
53
+ # @param except [Array<Symbol, Array<Symbol>>] Keys or nested key paths to exclude from the result.
54
+ # @param rewrite [Array<Hash>] A set of transformation rules applied to the top-level keys.
55
+ #
56
+ # @return [Hash] The final, shaped parameters hash.
57
+ #
58
+ # @example Extracting a specific top-level key
59
+ # # Given: params = { order: { product_id: 1, quantity: 2 } }
60
+ # request_params(:order)
61
+ # # => { order: { product_id: 1, quantity: 2 } }
62
+ #
63
+ # @example Without top-level keys (includes all)
64
+ # # Given: params = { order: { product_id: 1 }, app_id: 123 }
65
+ # request_params()
66
+ # # => { order: { product_id: 1 }, app_id: 123 }
67
+ #
68
+ # @example Merging and excluding
69
+ # # Given: params = { order: { product_id: 1, quantity: 2 }, internal: "yes" }
70
+ # request_params(:order, merge: { user_id: 123 }, except: [[:order, :quantity], :internal])
71
+ # # => { order: { product_id: 1 }, user_id: 123 }
72
+ #
73
+ # @example Flattening a nested hash into the top-level
74
+ # # Given: params = { order: { product_id: 1, quantity: 2 }, app_id: 123 }
75
+ # request_params(:order, rewrite: [{ order: { flatten: true } }])
76
+ # # => { product_id: 1, quantity: 2 }
77
+ #
78
+ # @example Rename a top-level key and filter nested keys
79
+ # # Given: params = { metadata: { source: "mobile", internal: "x" } }
80
+ # request_params(:metadata, rewrite: [
81
+ # { metadata: { as: :meta, only: [:source] } }
82
+ # ])
83
+ # # => { meta: { source: "mobile" } }
84
+ #
85
+ # @example Provide a default value if a key is missing
86
+ # # Given: params = {}
87
+ # request_params(:session, rewrite: [
88
+ # { session: { default: { id: nil } } }
89
+ # ])
90
+ # # => { session: { id: nil } }
91
+ #
92
+ # @example Merge values into a nested structure
93
+ # # Given: params = { flags: { foo: true } }
94
+ # request_params(:flags, rewrite: [
95
+ # { flags: { merge: { debug: true } } }
96
+ # ])
97
+ # # => { flags: { foo: true, debug: true } }
98
+ #
99
+ # @example Combine multiple rewrite rules
100
+ # # Given:
101
+ # # params = {
102
+ # # order: { product_id: 1, quantity: 2 },
103
+ # # metadata: { source: "mobile", location: { ip: "1.2.3.4" } },
104
+ # # tracking: { click_id: "abc", session_id: "def" }
105
+ # # }
106
+ # request_params(:order, :metadata, :tracking, rewrite: [
107
+ # { order: { flatten: true } },
108
+ # { metadata: { as: :meta, only: [:source, :location], flatten: [:location] } }
109
+ # ])
110
+ # # => {
111
+ # # product_id: 1,
112
+ # # quantity: 2,
113
+ # # meta: { source: "mobile", ip: "1.2.3.4" },
114
+ # # tracking: { click_id: "abc", session_id: "def" }
115
+ # # }
116
+ def request_params(*top_level_keys, merge: {}, except: [], rewrite: [])
117
+ permitted = params.permit!.to_h.deep_symbolize_keys
118
+ data = top_level_keys.any? ? permitted.slice(*top_level_keys) : permitted
119
+
120
+ apply_rewrites!(data, rewrite)
121
+
122
+ data
123
+ .deep_merge(merge)
124
+ .then { |result| except.any? ? deep_except(result, except) : result }
125
+ end
126
+
127
+ private
128
+
129
+ def apply_rewrites!(data, rewrites)
130
+ rewrites.each do |rule|
131
+ key, config = rule.first
132
+ config = { flatten: true } if config == :flatten
133
+
134
+ original = data.key?(key) ? data.delete(key) : nil
135
+ transformed = original.deep_dup if original.is_a?(Hash)
136
+ transformed ||= original
137
+
138
+ # Filtering
139
+ transformed.slice!(*config[:only]) if config[:only] && transformed.respond_to?(:slice!)
140
+ transformed.except!(*config[:except]) if config[:except] && transformed.respond_to?(:except!)
141
+
142
+ # Flatten specific nested keys
143
+ if config[:flatten].is_a?(Array) && transformed.is_a?(Hash)
144
+ config[:flatten].each do |subkey|
145
+ nested = transformed.delete(subkey)
146
+ if nested.is_a?(Hash)
147
+ transformed.merge!(nested)
148
+ elsif nested.is_a?(Array)
149
+ raise ArgumentError,
150
+ "Cannot flatten array for the key `#{subkey}`. Flattening arrays of hashes is not supported."
151
+ end
152
+ end
153
+ end
154
+
155
+ # Apply default if nil or missing
156
+ transformed ||= config[:default]
157
+
158
+ # Merge additional keys
159
+ if config[:merge]
160
+ transformed = transformed.is_a?(Hash) ? transformed.merge(config[:merge]) : config[:merge]
161
+ end
162
+
163
+ # Fully flatten to top level
164
+ if config[:flatten] == true && transformed.is_a?(Hash)
165
+ data.merge!(transformed)
166
+ else
167
+ target_key = config[:as] || key
168
+ data[target_key] = transformed
169
+ end
170
+ end
171
+ end
172
+
173
+ def deep_except(hash, paths)
174
+ paths.reduce(hash) { |acc, path| remove_nested_key(acc, Array(path)) }
175
+ end
176
+
177
+ def remove_nested_key(hash, path)
178
+ return hash unless path.is_a?(Array) && path.any?
179
+
180
+ key, *rest = path
181
+ return hash unless hash.key?(key)
182
+
183
+ duped = hash.dup
184
+ if rest.empty?
185
+ duped.delete(key)
186
+ elsif duped[key].is_a?(Hash)
187
+ duped[key] = remove_nested_key(duped[key], rest)
188
+ end
189
+
190
+ duped
191
+ end
192
+ end
193
+ end
194
+ end
@@ -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,9 @@
1
+ module InteractorSupport
2
+ module Errors
3
+ class UnknownAttribute < StandardError
4
+ def initialize(attribute)
5
+ super("Unknown attribute: #{attribute}")
6
+ end
7
+ end
8
+ end
9
+ 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
  ##
@@ -82,11 +83,38 @@ module InteractorSupport
82
83
  value
83
84
  end
84
85
  end
86
+
85
87
  return Struct.new(*attrs.keys).new(*attrs.values) if key_type == :struct
86
88
 
87
89
  attrs
88
90
  end
89
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
+ InteractorSupport.configuration.logger.log(
109
+ InteractorSupport.configuration.log_level,
110
+ "InteractorSupport::RequestObject ignoring unknown attribute '#{k}' for #{self.class.name}.",
111
+ )
112
+ else
113
+ raise Errors::UnknownAttribute, "`#{k}` for #{self.class.name}."
114
+ end
115
+ end
116
+ end
117
+
90
118
  class << self
91
119
  ##
92
120
  # Custom constructor that optionally returns the context instead of the object itself.
@@ -102,6 +130,19 @@ module InteractorSupport
102
130
  super(*args, **kwargs).to_context
103
131
  end
104
132
 
133
+ ##
134
+ # Defines whether to ignore unknown attributes during assignment.
135
+ # If true, unknown attributes are logged but not raised as errors.
136
+ # @example
137
+ # class MyRequest
138
+ # include InteractorSupport::RequestObject
139
+ # ignore_unknown_attributes
140
+ # end
141
+ # @return [void]
142
+ def ignore_unknown_attributes
143
+ define_method(:ignore_unknown_attributes?) { true }
144
+ end
145
+
105
146
  ##
106
147
  # Defines one or more attributes with optional coercion, default values, transformation,
107
148
  # and an optional `rewrite:` key to rename the underlying attribute.
File without changes
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module InteractorSupport
4
- VERSION = '1.0.3'
4
+ VERSION = '1.0.5'
5
5
  end
@@ -3,6 +3,7 @@
3
3
  require 'interactor'
4
4
  require 'logger'
5
5
  require 'active_support/concern'
6
+ require 'interactor_support/errors'
6
7
  require_relative 'interactor_support/core'
7
8
  require_relative 'interactor_support/version'
8
9
  require_relative 'interactor_support/actions'
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.3
4
+ version: 1.0.5
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-04-04 00:00:00.000000000 Z
11
+ date: 2025-07-01 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -30,13 +30,16 @@ files:
30
30
  - lib/interactor_support.rb
31
31
  - lib/interactor_support/actions.rb
32
32
  - lib/interactor_support/concerns/findable.rb
33
+ - lib/interactor_support/concerns/organizable.rb
33
34
  - lib/interactor_support/concerns/skippable.rb
34
35
  - lib/interactor_support/concerns/transactionable.rb
35
36
  - lib/interactor_support/concerns/transformable.rb
36
37
  - lib/interactor_support/concerns/updatable.rb
37
38
  - lib/interactor_support/configuration.rb
38
39
  - lib/interactor_support/core.rb
40
+ - lib/interactor_support/errors.rb
39
41
  - lib/interactor_support/request_object.rb
42
+ - lib/interactor_support/response_object.rb
40
43
  - lib/interactor_support/rubocop.rb
41
44
  - lib/interactor_support/rubocop/cop/base_interactor_cop.rb
42
45
  - lib/interactor_support/rubocop/cop/require_required_for_interactor_support.rb