interactor_support 1.0.1 → 1.0.3

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.
@@ -1,15 +1,41 @@
1
1
  module InteractorSupport
2
2
  module Concerns
3
+ ##
4
+ # Adds helpers for assigning and transforming values in interactor context.
5
+ #
6
+ # The `context_variable` method sets static or dynamic values before the interactor runs.
7
+ # The `transform` method allows chaining transformations (methods or lambdas) on context values.
8
+ #
9
+ # @example Assign context variables before the interactor runs
10
+ # context_variable user: -> { User.find(user_id) }, numbers: [1, 2, 3]
11
+ #
12
+ # @example Normalize email and name before using them
13
+ # transform :email, :name, with: [:strip, :downcase]
14
+ #
15
+ # @example Apply a lambda to clean up input
16
+ # transform :name, with: ->(value) { value.gsub(/\s+/, ' ').strip }
17
+ #
18
+ # @example Mixing symbols and lambdas
19
+ # transform :email, with: [:strip, :downcase, -> { email.gsub(/\s+/, '') }]
20
+ #
21
+ # @see InteractorSupport::Actions
3
22
  module Transformable
4
23
  extend ActiveSupport::Concern
5
24
  include InteractorSupport::Core
6
25
 
7
26
  included do
8
27
  class << self
9
- # context_variable first_post: Post.first
10
- # context_variable user: -> { User.find(user_id) }
11
- # context_variable items: Item.all
12
- # context_variable numbers: [1, 2, 3]
28
+ # Assigns one or more values to the context before the interactor runs.
29
+ #
30
+ # Values can be static or lazily evaluated with a lambda/proc using `instance_exec`,
31
+ # which provides access to the context and interactor instance.
32
+ #
33
+ # @param key_values [Hash{Symbol => Object, Proc}] a mapping of context keys to values or Procs
34
+ #
35
+ # @example Static and dynamic values
36
+ # context_variable first_post: Post.first
37
+ # context_variable user: -> { User.find(user_id) }
38
+ # context_variable numbers: [1, 2, 3]
13
39
  def context_variable(key_values)
14
40
  before do
15
41
  key_values.each do |key, value|
@@ -22,12 +48,40 @@ module InteractorSupport
22
48
  end
23
49
  end
24
50
 
25
- # transform :email, :name, with: [:downcase, :strip]
26
- # transform :url, with: :downcase
27
- # transform :items, with: :compact
28
- # transform :items, with: ->(value) { value.compact }
29
- # transform :email, :url, with: ->(value) { value.downcase.strip }
30
- # transform :items, with: :compact
51
+ # Transforms one or more context values using a method, a proc, or a sequence of methods.
52
+ #
53
+ # This allows simple transformations like `:strip` or `:downcase`, or more complex lambdas.
54
+ # You can also chain transformations by passing an array of method names.
55
+ #
56
+ # If a transformation fails, the context fails with an error message.
57
+ #
58
+ # @param keys [Array<Symbol>] one or more context keys to transform
59
+ # @param with [Symbol, Array<Symbol>, Proc] a single method name, an array of method names, or a proc
60
+ #
61
+ # @raise [ArgumentError] if no keys are given, or if an invalid `with:` value is passed
62
+ #
63
+ # @example Single method
64
+ # transform :email, with: :strip
65
+ #
66
+ # @example Method chain
67
+ # transform :email, with: [:strip, :downcase]
68
+ #
69
+ # @example Lambda
70
+ # transform :url, with: ->(value) { value.downcase.strip }
71
+ #
72
+ # @example Multiple keys
73
+ # transform :email, :name, with: [:downcase, :strip]
74
+ #
75
+ # @example Normalize user input
76
+ # transform :email, :name, with: [
77
+ # :strip,
78
+ # :downcase,
79
+ # ->(value) { value.gsub(/\s+/, ' ') }, # collapse duplicate spaces
80
+ # ]
81
+ #
82
+ # # Result:
83
+ # # context[:email] = "someone@example.com"
84
+ # # context[:name] = "john doe"
31
85
  def transform(*keys, with: [])
32
86
  before do
33
87
  if keys.empty?
@@ -42,11 +96,20 @@ module InteractorSupport
42
96
  context.fail!(errors: ["#{key} failed to transform: #{e.message}"])
43
97
  end
44
98
  elsif with.is_a?(Array)
45
- context.fail!(errors: ["#{key} does not respond to all transforms"]) unless with.all? do |t|
46
- t.is_a?(Symbol) && context[key].respond_to?(t)
47
- end
48
- context[key] = with.inject(context[key]) do |value, method|
49
- value.send(method)
99
+ with.each do |method|
100
+ if method.is_a?(Proc)
101
+ begin
102
+ context[key] = context.instance_exec(&method)
103
+ rescue => e
104
+ context.fail!(errors: ["#{key} failed to transform: #{e.message}"])
105
+ end
106
+ else
107
+ context.fail!(
108
+ errors: ["#{key} does not respond to all transforms"],
109
+ ) unless context[key].respond_to?(method)
110
+
111
+ context[key] = context[key].send(method)
112
+ end
50
113
  end
51
114
  elsif with.is_a?(Symbol) && context[key].respond_to?(with)
52
115
  context[key] = context[key].send(with)
@@ -1,11 +1,54 @@
1
1
  module InteractorSupport
2
2
  module Concerns
3
+ ##
4
+ # Adds an `update` DSL method for updating a context-loaded model with attributes.
5
+ #
6
+ # This concern allows flexible updates using data from the interactor's context.
7
+ # It supports direct mapping from context keys, nested attribute extraction from parent objects,
8
+ # lambdas for dynamic evaluation, or passing a symbol pointing to an entire context object.
9
+ #
10
+ # This is useful for updating records cleanly and consistently in declarative steps.
11
+ #
12
+ # @example Update a user using context values
13
+ # update :user, attributes: { name: :new_name, email: :new_email }
14
+ #
15
+ # @example Extract nested fields from a context object
16
+ # update :user, attributes: { form_data: [:name, :email] }
17
+ #
18
+ # @example Use a lambda for computed value
19
+ # update :post, attributes: { published_at: -> { Time.current } }
20
+ #
21
+ # @see InteractorSupport::Actions
3
22
  module Updatable
4
23
  extend ActiveSupport::Concern
5
24
  include InteractorSupport::Core
6
25
 
7
26
  included do
8
27
  class << self
28
+ # Updates a model using values from the context before the interactor runs.
29
+ #
30
+ # Supports flexible ways of specifying attributes:
31
+ # - A hash mapping attribute names to context keys, nested keys, or lambdas
32
+ # - A symbol pointing to a hash in context
33
+ #
34
+ # If the record or required data is missing, the context fails with an error.
35
+ #
36
+ # @param model [Symbol] the key in the context holding the record to update
37
+ # @param attributes [Hash, Symbol] a hash mapping attributes to context keys/lambdas, or a symbol pointing to a context hash
38
+ # @param context_key [Symbol, nil] key to assign the updated record to in context (defaults to `model`)
39
+ #
40
+ # @example Basic attribute update using context keys
41
+ # update :user, attributes: { name: :new_name, email: :new_email }
42
+ #
43
+ # @example Use a lambda for dynamic value
44
+ # update :post, attributes: { published_at: -> { Time.current } }
45
+ #
46
+ # @example Nested context value lookup from a parent object
47
+ # # Assuming context[:form_data] = OpenStruct.new(name: "Hi", email: "hi@example.com")
48
+ # update :user, attributes: { form_data: [:name, :email] }
49
+ #
50
+ # @example Using a symbol to fetch all attributes from another context object
51
+ # update :order, attributes: :order_attributes
9
52
  def update(model, attributes: {}, context_key: nil)
10
53
  context_key ||= model
11
54
 
@@ -46,7 +89,6 @@ module InteractorSupport
46
89
 
47
90
  record.update!(update_data)
48
91
 
49
- # Assign the updated record to context
50
92
  context[context_key] = record
51
93
  end
52
94
  end
@@ -1,17 +1,40 @@
1
1
  module InteractorSupport
2
+ ##
3
+ # Global configuration for InteractorSupport.
4
+ #
5
+ # This allows customization of how request objects behave when used in interactors.
6
+ #
7
+ # @example Set custom behavior
8
+ # InteractorSupport.configuration.request_object_behavior = :returns_self
9
+ # InteractorSupport.configuration.request_object_key_type = :struct
10
+ #
11
+ # @see InteractorSupport.configuration
2
12
  class Configuration
3
- attr_accessor :request_object_behavior, :request_object_key_type
13
+ ##
14
+ # Defines how request objects behave when called.
15
+ #
16
+ # - `:returns_context` — The request object returns an Interactor-style context.
17
+ # - `:returns_self` — The request object returns itself, allowing method chaining.
18
+ #
19
+ # @return [:returns_context, :returns_self]
20
+ attr_accessor :request_object_behavior
4
21
 
22
+ ##
23
+ # Defines the key type used in request object context when `:returns_context` is active.
24
+ #
25
+ # - `:string` — Keys are string-based (`"name"`)
26
+ # - `:symbol` — Keys are symbol-based (`:name`)
27
+ # - `:struct` — Keys are accessed via struct-style method calls (`name`)
28
+ #
29
+ # @return [:string, :symbol, :struct]
30
+ attr_accessor :request_object_key_type
31
+
32
+ ##
33
+ # Initializes the configuration with default values:
34
+ # - `request_object_behavior` defaults to `:returns_context`
35
+ # - `request_object_key_type` defaults to `:symbol`
5
36
  def initialize
6
- # Default configuration values.
7
- # :returns_context - request objects return a context object.
8
- # :returns_self - request objects return self.
9
37
  @request_object_behavior = :returns_context
10
-
11
- # Default configuration values, only applies when request_object_behavior is :returns_context.
12
- # :string - request object keys are strings.
13
- # :symbol - request object keys are symbols.
14
- # :struct - request object keys are struct objects.
15
38
  @request_object_key_type = :symbol
16
39
  end
17
40
  end
@@ -1,8 +1,26 @@
1
1
  module InteractorSupport
2
+ ##
3
+ # Core behavior that ensures the `Interactor` module is included
4
+ # when any InteractorSupport concern is mixed in.
5
+ #
6
+ # This module is automatically included by all `InteractorSupport::Concerns`,
7
+ # so you generally do not need to include it manually.
8
+ #
9
+ # @example Included implicitly
10
+ # class MyInteractor
11
+ # include InteractorSupport::Concerns::Findable
12
+ # # => Interactor is automatically included
13
+ # end
2
14
  module Core
3
15
  class << self
16
+ ##
17
+ # Ensures the `Interactor` module is included in the base class.
18
+ #
19
+ # This hook runs when `Core` is included by a concern and conditionally
20
+ # includes `Interactor` if not already present.
21
+ #
22
+ # @param base [Class] the class or module including this concern
4
23
  def included(base)
5
- # Only include Interactor if it isn’t already present.
6
24
  base.include(Interactor) unless base.included_modules.include?(Interactor)
7
25
  end
8
26
  end
@@ -1,18 +1,75 @@
1
- # app/concerns/interactor_support/request_object.rb
2
1
  module InteractorSupport
2
+ ##
3
+ # A base module for building validated, transformable, and optionally nested request objects.
4
+ #
5
+ # It builds on top of `ActiveModel::Model`, adds coercion, default values, attribute transforms,
6
+ # key rewriting, and automatic context conversion (via `#to_context`). It integrates tightly with
7
+ # `InteractorSupport::Configuration` to control return behavior and key formatting.
8
+ #
9
+ # @example Basic usage
10
+ # class CreateUserRequest
11
+ # include InteractorSupport::RequestObject
12
+ #
13
+ # attribute :name, transform: [:strip, :downcase]
14
+ # attribute :email
15
+ # attribute :metadata, default: {}
16
+ # end
17
+ #
18
+ # CreateUserRequest.new(name: " JOHN ", email: "hi@example.com")
19
+ # # => { name: "john", email: "hi@example.com", metadata: {} }
20
+ #
21
+ # @example Key rewriting
22
+ # class UploadRequest
23
+ # include InteractorSupport::RequestObject
24
+ #
25
+ # attribute :image, rewrite: :image_url
26
+ # end
27
+ #
28
+ # UploadRequest.new(image: "url").image_url # => "url"
29
+ #
30
+ # @see InteractorSupport::Configuration
3
31
  module RequestObject
4
32
  extend ActiveSupport::Concern
33
+ SUPPORTED_ACTIVEMODEL_TYPES = ActiveModel::Type.registry.send(:registrations).keys.map { |type| ":#{type}" }
34
+ SUPPORTED_PRIMITIVES = ['AnyClass', 'Symbol', 'Hash', 'Array']
35
+ SUPPORTED_TYPES = SUPPORTED_PRIMITIVES + SUPPORTED_ACTIVEMODEL_TYPES
36
+
37
+ class TypeError < StandardError
38
+ end
5
39
 
6
40
  included do
7
41
  include ActiveModel::Model
8
42
  include ActiveModel::Attributes
9
43
  include ActiveModel::Validations::Callbacks
10
44
 
45
+ ##
46
+ # Initializes the request object and raises if invalid.
47
+ #
48
+ # Rewritten keys are converted before passing to ActiveModel.
49
+ #
50
+ # @param attributes [Hash] the input attributes
51
+ # @raise [ActiveModel::ValidationError] if the object is invalid
11
52
  def initialize(attributes = {})
53
+ attributes = attributes.dup
54
+ self.class.rewritten_attributes.each do |external, internal|
55
+ if attributes.key?(external)
56
+ attributes[internal] = attributes.delete(external)
57
+ end
58
+ end
59
+
12
60
  super(attributes)
13
61
  raise ActiveModel::ValidationError, self unless valid?
14
62
  end
15
63
 
64
+ ##
65
+ # Converts the request object into a format suitable for interactor context.
66
+ #
67
+ # - If `key_type` is `:symbol` or `:string`, returns a Hash.
68
+ # - If `key_type` is `:struct`, returns a Struct instance.
69
+ #
70
+ # Nested request objects will also be converted recursively.
71
+ #
72
+ # @return [Hash, Struct]
16
73
  def to_context
17
74
  key_type = InteractorSupport.configuration.request_object_key_type
18
75
  attrs = attributes.each_with_object({}) do |(name, value), hash|
@@ -31,26 +88,42 @@ module InteractorSupport
31
88
  end
32
89
 
33
90
  class << self
91
+ ##
92
+ # Custom constructor that optionally returns the context instead of the object itself.
93
+ #
94
+ # Behavior is configured via `InteractorSupport.configuration.request_object_behavior`.
95
+ #
96
+ # @param args [Array] positional args
97
+ # @param kwargs [Hash] keyword args
98
+ # @return [RequestObject, Hash, Struct]
34
99
  def new(*args, **kwargs)
35
100
  return super(*args, **kwargs) if InteractorSupport.configuration.request_object_behavior == :returns_self
36
101
 
37
102
  super(*args, **kwargs).to_context
38
103
  end
39
104
 
40
- # Accepts one or more attribute names along with options.
105
+ ##
106
+ # Defines one or more attributes with optional coercion, default values, transformation,
107
+ # and an optional `rewrite:` key to rename the underlying attribute.
108
+ #
109
+ # @param names [Array<Symbol>] the attribute names
110
+ # @param type [Class, nil] optional class to coerce the value to (often another request object)
111
+ # @param array [Boolean] whether to treat the input as an array of typed objects
112
+ # @param default [Object] default value if not provided
113
+ # @param transform [Symbol, Array<Symbol>] method(s) to apply to the value
114
+ # @param rewrite [Symbol, nil] optional internal name to rewrite this attribute to
41
115
  #
42
- # Options:
43
- # - type: a class to cast the value (often another RequestObject)
44
- # - array: when true, expects an array; each element is cast.
45
- # - default: default value for the attribute.
46
- # - transform: a symbol or an array of symbols that will be applied (if the value responds to them).
47
- def attribute(*names, type: nil, array: false, default: nil, transform: nil)
116
+ # @raise [ArgumentError] if a transform method is not found
117
+ def attribute(*names, type: nil, array: false, default: nil, transform: nil, rewrite: nil)
48
118
  names.each do |name|
49
- transform_options[name.to_sym] = transform if transform.present?
50
- super(name, default: default)
51
- original_writer = instance_method("#{name}=")
52
- define_method("#{name}=") do |value|
53
- # Apply transforms immediately if provided.
119
+ attr_name = rewrite || name
120
+ rewritten_attributes[name.to_sym] = attr_name if rewrite
121
+ transform_options[attr_name.to_sym] = transform if transform.present?
122
+
123
+ super(attr_name, default: default)
124
+ original_writer = instance_method("#{attr_name}=")
125
+
126
+ define_method("#{attr_name}=") do |value|
54
127
  if transform
55
128
  Array(transform).each do |method|
56
129
  if value.respond_to?(method)
@@ -62,25 +135,64 @@ module InteractorSupport
62
135
  end
63
136
  end
64
137
  end
65
- # Type: only wrap if not already an instance.
138
+
139
+ # If a `type` is specified, we attempt to cast the `value` to that type
66
140
  if type
67
- value = if array
68
- Array(value).map { |v| v.is_a?(type) ? v : type.new(v) }
69
- else
70
- value.is_a?(type) ? value : type.new(value)
71
- end
141
+ value = array ? Array(value).map { |v| cast_value(v, type) } : cast_value(value, type)
72
142
  end
143
+
73
144
  original_writer.bind(self).call(value)
74
145
  end
75
146
  end
76
147
  end
77
148
 
149
+ ##
150
+ # Internal map of external attribute names to internal rewritten names.
151
+ #
152
+ # @return [Hash{Symbol => Symbol}]
153
+ def rewritten_attributes
154
+ @_rewritten_attributes ||= {}
155
+ end
156
+
78
157
  private
79
158
 
159
+ ##
160
+ # Internal storage for transform options per attribute.
161
+ #
162
+ # @return [Hash{Symbol => Symbol, Array<Symbol>}]
80
163
  def transform_options
81
164
  @_transform_options ||= {}
82
165
  end
83
166
  end
167
+
168
+ private
169
+
170
+ def cast_value(value, type)
171
+ return typecast(value, type) if type.is_a?(Symbol)
172
+ return value if value.is_a?(type)
173
+ return type.new(value) if type <= InteractorSupport::RequestObject
174
+
175
+ typecast(value, type)
176
+ end
177
+
178
+ def typecast(value, type)
179
+ if type.is_a?(Symbol)
180
+ ActiveModel::Type.lookup(type).cast(value)
181
+ elsif type == Symbol
182
+ value.to_sym
183
+ elsif type == Array
184
+ value.to_a
185
+ elsif type == Hash
186
+ value.to_h
187
+ else
188
+ raise TypeError
189
+ end
190
+ rescue ArgumentError
191
+ message = ":#{type} is not a supported type. Supported types are: #{SUPPORTED_TYPES.join(", ")}"
192
+ raise TypeError, message
193
+ rescue
194
+ raise TypeError, "Cannot cast #{value.inspect} to #{type.name}"
195
+ end
84
196
  end
85
197
  end
86
198
  end
@@ -1,6 +1,29 @@
1
1
  require 'active_model'
2
2
 
3
3
  module InteractorSupport
4
+ ##
5
+ # Provides context-aware validation DSL for interactors.
6
+ #
7
+ # This module adds `ActiveModel::Validations` and wraps it with methods like
8
+ # `required`, `optional`, `validates_before`, and `validates_after`, allowing
9
+ # declarative validation of interactor context values.
10
+ #
11
+ # Validations are executed automatically before (or after) the interactor runs.
12
+ #
13
+ # @example Required attributes with ActiveModel rules
14
+ # required :email, :name
15
+ # required age: { numericality: { greater_than: 18 } }
16
+ #
17
+ # @example Optional attributes with presence/format validations
18
+ # optional bio: { length: { maximum: 500 } }
19
+ #
20
+ # @example Type and inclusion validation before execution
21
+ # validates_before :role, type: String, inclusion: { in: %w[admin user guest] }
22
+ #
23
+ # @example Persistence validation after execution
24
+ # validates_after :user, persisted: true
25
+ #
26
+ # @see ActiveModel::Validations
4
27
  module Validations
5
28
  extend ActiveSupport::Concern
6
29
  include InteractorSupport::Core
@@ -11,14 +34,35 @@ module InteractorSupport
11
34
  end
12
35
 
13
36
  class_methods do
37
+ ##
38
+ # Declares one or more attributes as required.
39
+ #
40
+ # Values must be present in the context. You can also pass validation options
41
+ # as a hash, which will be forwarded to ActiveModel's `validates`.
42
+ #
43
+ # @param keys [Array<Symbol, Hash>] attribute names or hash of attributes with validation options
14
44
  def required(*keys)
15
45
  apply_validations(keys, required: true)
16
46
  end
17
47
 
48
+ ##
49
+ # Declares one or more attributes as optional.
50
+ #
51
+ # Optional values can be nil, but still support validation rules.
52
+ #
53
+ # @param keys [Array<Symbol, Hash>] attribute names or hash of attributes with validation options
18
54
  def optional(*keys)
19
55
  apply_validations(keys, required: false)
20
56
  end
21
57
 
58
+ ##
59
+ # Runs additional validations *after* the interactor executes.
60
+ #
61
+ # Useful for checking persisted records, custom conditions, or results
62
+ # that depend on post-processing logic.
63
+ #
64
+ # @param keys [Array<Symbol>] context keys to validate
65
+ # @param validations [Hash] validation options (e.g., presence:, type:, inclusion:, persisted:)
22
66
  def validates_after(*keys, **validations)
23
67
  after do
24
68
  keys.each do |key|
@@ -27,6 +71,15 @@ module InteractorSupport
27
71
  end
28
72
  end
29
73
 
74
+ ##
75
+ # Runs validations *before* the interactor executes.
76
+ #
77
+ # Prevents invalid data from reaching business logic.
78
+ #
79
+ # NOTE: `persisted:` validation is only available in `validates_after`.
80
+ #
81
+ # @param keys [Array<Symbol>] context keys to validate
82
+ # @param validations [Hash] validation options (e.g., presence:, type:, inclusion:)
30
83
  def validates_before(*keys, **validations)
31
84
  before do
32
85
  if validations[:persisted]
@@ -41,6 +94,11 @@ module InteractorSupport
41
94
 
42
95
  private
43
96
 
97
+ ##
98
+ # Applies ActiveModel-based validations and wires up accessors to context.
99
+ #
100
+ # @param keys [Array<Symbol, Hash>] attributes to validate
101
+ # @param required [Boolean] whether presence is enforced
44
102
  def apply_validations(keys, required:)
45
103
  keys.each do |key|
46
104
  if key.is_a?(Hash)
@@ -61,18 +119,27 @@ module InteractorSupport
61
119
  validates(key, presence: true) if required
62
120
  end
63
121
  end
64
- # Ensure validations run before execution
122
+
65
123
  before do
66
124
  context.fail!(errors: errors.full_messages) unless valid?
67
125
  end
68
126
  end
69
127
 
128
+ ##
129
+ # Defines methods to read/write from the interactor context.
130
+ #
131
+ # @param key [Symbol] the context key
70
132
  def define_context_methods(key)
71
133
  define_method(key) { context[key] }
72
134
  define_method("#{key}=") { |value| context[key] = value }
73
135
  end
74
136
  end
75
137
 
138
+ ##
139
+ # Applies custom inline validations to a context key.
140
+ #
141
+ # @param key [Symbol] the context key
142
+ # @param validations [Hash] options like presence:, type:, inclusion:, persisted:
76
143
  def apply_custom_validations(key, validations)
77
144
  validation_for_presence(key) if validations[:presence]
78
145
  validation_for_inclusion(key, validations[:inclusion]) if validations[:inclusion]
@@ -80,10 +147,20 @@ module InteractorSupport
80
147
  validation_for_type(key, validations[:type]) if validations[:type]
81
148
  end
82
149
 
150
+ ##
151
+ # Fails if context value is not of expected type.
152
+ #
153
+ # @param key [Symbol]
154
+ # @param type [Class]
83
155
  def validation_for_type(key, type)
84
156
  context.fail!(errors: ["#{key} was not of type #{type}"]) unless context[key].is_a?(type)
85
157
  end
86
158
 
159
+ ##
160
+ # Fails if value is not included in allowed values.
161
+ #
162
+ # @param key [Symbol]
163
+ # @param inclusion [Hash] with `:in` key
87
164
  def validation_for_inclusion(key, inclusion)
88
165
  unless inclusion.is_a?(Hash) && inclusion[:in].is_a?(Enumerable)
89
166
  raise ArgumentError, 'inclusion validation requires an :in key with an array or range'
@@ -94,23 +171,26 @@ module InteractorSupport
94
171
  context.fail!(errors: [e.message])
95
172
  end
96
173
 
174
+ ##
175
+ # Fails if value is nil or blank.
176
+ #
177
+ # @param key [Symbol]
97
178
  def validation_for_presence(key)
98
179
  context.fail!(errors: ["#{key} does not exist"]) unless context[key].present?
99
180
  end
100
181
 
182
+ ##
183
+ # Fails if value is not a persisted `ApplicationRecord`.
184
+ #
185
+ # @param key [Symbol]
101
186
  def validation_for_persistence(key)
102
187
  validation_for_presence(key)
188
+
103
189
  unless context[key].is_a?(ApplicationRecord)
104
- context.fail!(
105
- errors: [
106
- "#{key} is not an ApplicationRecord, which is required for persisted validation",
107
- ],
108
- )
190
+ context.fail!(errors: ["#{key} is not an ApplicationRecord, which is required for persisted validation"])
109
191
  end
110
192
 
111
- context.fail!(
112
- errors: ["#{key} was not persisted"] + context[key].errors.full_messages,
113
- ) unless context[key].persisted?
193
+ context.fail!(errors: ["#{key} was not persisted"] + context[key].errors.full_messages) unless context[key].persisted?
114
194
  end
115
195
  end
116
196
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module InteractorSupport
4
- VERSION = '1.0.1'
4
+ VERSION = '1.0.3'
5
5
  end