active_interaction 1.4.1 → 2.0.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/CHANGELOG.md +65 -3
- data/CONTRIBUTING.md +19 -0
- data/README.md +1121 -180
- data/lib/active_interaction/backports.rb +58 -13
- data/lib/active_interaction/base.rb +16 -52
- data/lib/active_interaction/concerns/active_recordable.rb +57 -0
- data/lib/active_interaction/concerns/runnable.rb +4 -14
- data/lib/active_interaction/errors.rb +14 -66
- data/lib/active_interaction/filters/array_filter.rb +12 -9
- data/lib/active_interaction/filters/file_filter.rb +5 -24
- data/lib/active_interaction/filters/hash_filter.rb +11 -13
- data/lib/active_interaction/filters/interface_filter.rb +2 -2
- data/lib/active_interaction/filters/{model_filter.rb → object_filter.rb} +6 -6
- data/lib/active_interaction/locale/en.yml +1 -1
- data/lib/active_interaction/modules/validation.rb +2 -2
- data/lib/active_interaction/version.rb +1 -1
- data/lib/active_interaction.rb +25 -13
- data/spec/active_interaction/base_spec.rb +15 -39
- data/spec/active_interaction/concerns/active_recordable_spec.rb +51 -0
- data/spec/active_interaction/concerns/runnable_spec.rb +2 -34
- data/spec/active_interaction/errors_spec.rb +6 -89
- data/spec/active_interaction/filters/array_filter_spec.rb +2 -2
- data/spec/active_interaction/filters/file_filter_spec.rb +4 -4
- data/spec/active_interaction/filters/hash_filter_spec.rb +1 -17
- data/spec/active_interaction/filters/{model_filter_spec.rb → object_filter_spec.rb} +17 -17
- data/spec/active_interaction/i18n_spec.rb +1 -2
- data/spec/active_interaction/integration/array_interaction_spec.rb +10 -0
- data/spec/active_interaction/integration/hash_interaction_spec.rb +12 -2
- data/spec/active_interaction/integration/interface_interaction_spec.rb +10 -1
- data/spec/active_interaction/integration/object_interaction_spec.rb +16 -0
- data/spec/active_interaction/modules/validation_spec.rb +1 -2
- metadata +32 -29
- data/lib/active_interaction/concerns/transactable.rb +0 -79
- data/spec/active_interaction/concerns/transactable_spec.rb +0 -135
- data/spec/active_interaction/integration/model_interaction_spec.rb +0 -7
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
2
|
|
|
3
|
-
# rubocop:disable Documentation
|
|
4
3
|
module ActiveInteraction
|
|
5
|
-
class GroupedInput
|
|
4
|
+
class GroupedInput # rubocop:disable Style/Documentation
|
|
6
5
|
# Required for Ruby <= 1.9.3.
|
|
7
6
|
def [](name)
|
|
8
7
|
send(name)
|
|
@@ -14,20 +13,66 @@ module ActiveInteraction
|
|
|
14
13
|
end unless method_defined?(:[]=)
|
|
15
14
|
end
|
|
16
15
|
|
|
17
|
-
class Errors
|
|
16
|
+
class Errors # rubocop:disable Style/Documentation
|
|
18
17
|
# Required for Rails < 3.2.13.
|
|
19
18
|
protected :initialize_dup
|
|
19
|
+
|
|
20
|
+
# Required for Rails < 5.
|
|
21
|
+
#
|
|
22
|
+
# Extracted from active_model-errors_details 1.2.0. Modified to add support
|
|
23
|
+
# for ActiveModel 3.2.0.
|
|
24
|
+
module Details
|
|
25
|
+
extend ActiveSupport::Concern
|
|
26
|
+
|
|
27
|
+
CALLBACKS_OPTIONS = ::ActiveModel::Errors::CALLBACKS_OPTIONS
|
|
28
|
+
private_constant :CALLBACKS_OPTIONS
|
|
29
|
+
|
|
30
|
+
included do
|
|
31
|
+
attr_reader :details
|
|
32
|
+
|
|
33
|
+
%w[initialize initialize_dup add clear delete].each do |method|
|
|
34
|
+
alias_method_chain method, :details
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def initialize_with_details(base)
|
|
39
|
+
@details = Hash.new { |details, attribute| details[attribute] = [] }
|
|
40
|
+
initialize_without_details(base)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def initialize_dup_with_details(other)
|
|
44
|
+
@details = other.details.deep_dup
|
|
45
|
+
initialize_dup_without_details(other)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def add_with_details(attribute, message = :invalid, options = {})
|
|
49
|
+
message = message.call if message.respond_to?(:call)
|
|
50
|
+
error = options.except(*CALLBACKS_OPTIONS).merge(error: message)
|
|
51
|
+
details[attribute].push(error)
|
|
52
|
+
add_without_details(attribute, message, options)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def clear_with_details
|
|
56
|
+
details.clear
|
|
57
|
+
clear_without_details
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def delete_with_details(attribute)
|
|
61
|
+
details.delete(attribute)
|
|
62
|
+
delete_without_details(attribute)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
include Details unless method_defined?(:details)
|
|
20
66
|
end
|
|
21
|
-
end
|
|
22
67
|
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
result[
|
|
68
|
+
class HashFilter # rubocop:disable Style/Documentation
|
|
69
|
+
# Required for Rails < 4.0.0.
|
|
70
|
+
def self.transform_keys(hash, &block)
|
|
71
|
+
return hash.transform_keys(&block) if hash.respond_to?(:transform_keys)
|
|
72
|
+
|
|
73
|
+
result = {}
|
|
74
|
+
hash.each_key { |key| result[block.call(key)] = hash[key] }
|
|
75
|
+
result
|
|
30
76
|
end
|
|
31
|
-
|
|
32
|
-
end unless method_defined?(:transform_keys)
|
|
77
|
+
end
|
|
33
78
|
end
|
|
@@ -29,6 +29,7 @@ module ActiveInteraction
|
|
|
29
29
|
# end
|
|
30
30
|
class Base
|
|
31
31
|
include ActiveModelable
|
|
32
|
+
include ActiveRecordable
|
|
32
33
|
include Runnable
|
|
33
34
|
|
|
34
35
|
define_callbacks :type_check
|
|
@@ -58,28 +59,6 @@ module ActiveInteraction
|
|
|
58
59
|
#
|
|
59
60
|
# @raise (see ActiveInteraction::Runnable::ClassMethods#run!)
|
|
60
61
|
|
|
61
|
-
# @!method transaction(enable, options = {})
|
|
62
|
-
# Configure transactions by enabling or disabling them and setting
|
|
63
|
-
# their options.
|
|
64
|
-
#
|
|
65
|
-
# @example Disable transactions
|
|
66
|
-
# Class.new(ActiveInteraction::Base) do
|
|
67
|
-
# transaction false
|
|
68
|
-
# end
|
|
69
|
-
#
|
|
70
|
-
# @example Use different transaction options
|
|
71
|
-
# Class.new(ActiveInteraction::Base) do
|
|
72
|
-
# transaction true, isolation: :serializable
|
|
73
|
-
# end
|
|
74
|
-
#
|
|
75
|
-
# @param enable [Boolean] Should transactions be enabled?
|
|
76
|
-
# @param options [Hash] Options to pass to
|
|
77
|
-
# `ActiveRecord::Base.transaction`.
|
|
78
|
-
#
|
|
79
|
-
# @return [nil]
|
|
80
|
-
#
|
|
81
|
-
# @since 1.2.0
|
|
82
|
-
|
|
83
62
|
# Get or set the description.
|
|
84
63
|
#
|
|
85
64
|
# @example
|
|
@@ -120,6 +99,11 @@ module ActiveInteraction
|
|
|
120
99
|
end
|
|
121
100
|
end
|
|
122
101
|
|
|
102
|
+
def model(*)
|
|
103
|
+
super
|
|
104
|
+
end
|
|
105
|
+
ActiveInteraction.deprecate self, :model, 'use `object` instead'
|
|
106
|
+
|
|
123
107
|
private
|
|
124
108
|
|
|
125
109
|
# @param klass [Class]
|
|
@@ -168,7 +152,13 @@ module ActiveInteraction
|
|
|
168
152
|
attr_accessor filter.name
|
|
169
153
|
define_method("#{filter.name}?") { !public_send(filter.name).nil? }
|
|
170
154
|
|
|
171
|
-
filter
|
|
155
|
+
eagerly_evaluate_default(filter)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# @param filter [Filter]
|
|
159
|
+
def eagerly_evaluate_default(filter)
|
|
160
|
+
default = filter.options[:default]
|
|
161
|
+
filter.default if default && !default.is_a?(Proc)
|
|
172
162
|
end
|
|
173
163
|
end
|
|
174
164
|
|
|
@@ -181,31 +171,6 @@ module ActiveInteraction
|
|
|
181
171
|
process_inputs(inputs.symbolize_keys)
|
|
182
172
|
end
|
|
183
173
|
|
|
184
|
-
# Returns the column object for the named filter.
|
|
185
|
-
#
|
|
186
|
-
# @param name [Symbol] The name of a filter.
|
|
187
|
-
#
|
|
188
|
-
# @example
|
|
189
|
-
# class Interaction < ActiveInteraction::Base
|
|
190
|
-
# string :email, default: nil
|
|
191
|
-
#
|
|
192
|
-
# def execute; end
|
|
193
|
-
# end
|
|
194
|
-
#
|
|
195
|
-
# Interaction.new.column_for_attribute(:email)
|
|
196
|
-
# # => #<ActiveInteraction::FilterColumn:0x007faebeb2a6c8 @type=:string>
|
|
197
|
-
#
|
|
198
|
-
# Interaction.new.column_for_attribute(:not_a_filter)
|
|
199
|
-
# # => nil
|
|
200
|
-
#
|
|
201
|
-
# @return [FilterColumn, nil]
|
|
202
|
-
#
|
|
203
|
-
# @since 1.2.0
|
|
204
|
-
def column_for_attribute(name)
|
|
205
|
-
filter = self.class.filters[name]
|
|
206
|
-
FilterColumn.intern(filter.database_column_type) if filter
|
|
207
|
-
end
|
|
208
|
-
|
|
209
174
|
# @!method compose(other, inputs = {})
|
|
210
175
|
# Run another interaction and return its result. If the other interaction
|
|
211
176
|
# fails, halt execution.
|
|
@@ -220,8 +185,7 @@ module ActiveInteraction
|
|
|
220
185
|
#
|
|
221
186
|
# Runs the business logic associated with the interaction. This method is
|
|
222
187
|
# only run when there are no validation errors. The return value is
|
|
223
|
-
# placed into {#result}.
|
|
224
|
-
# if ActiveRecord is available (see {.transaction}).
|
|
188
|
+
# placed into {#result}.
|
|
225
189
|
#
|
|
226
190
|
# @raise (see ActiveInteraction::Runnable#execute)
|
|
227
191
|
|
|
@@ -264,7 +228,7 @@ module ActiveInteraction
|
|
|
264
228
|
self.class.filters.each do |name, filter|
|
|
265
229
|
begin
|
|
266
230
|
public_send("#{name}=", filter.clean(inputs[name]))
|
|
267
|
-
rescue
|
|
231
|
+
rescue Error
|
|
268
232
|
# #type_check will add errors if appropriate.
|
|
269
233
|
end
|
|
270
234
|
end
|
|
@@ -273,7 +237,7 @@ module ActiveInteraction
|
|
|
273
237
|
def type_check
|
|
274
238
|
run_callbacks(:type_check) do
|
|
275
239
|
Validation.validate(self.class.filters, inputs).each do |error|
|
|
276
|
-
errors.
|
|
240
|
+
errors.add(*error)
|
|
277
241
|
end
|
|
278
242
|
end
|
|
279
243
|
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
module ActiveInteraction
|
|
4
|
+
# Implement the minimal ActiveRecord interface.
|
|
5
|
+
#
|
|
6
|
+
# @private
|
|
7
|
+
module ActiveRecordable
|
|
8
|
+
# Returns the column object for the named filter.
|
|
9
|
+
#
|
|
10
|
+
# @param name [Symbol] The name of a filter.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# class Interaction < ActiveInteraction::Base
|
|
14
|
+
# string :email, default: nil
|
|
15
|
+
#
|
|
16
|
+
# def execute; end
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# Interaction.new.column_for_attribute(:email)
|
|
20
|
+
# # => #<ActiveInteraction::FilterColumn:0x007faebeb2a6c8 @type=:string>
|
|
21
|
+
#
|
|
22
|
+
# Interaction.new.column_for_attribute(:not_a_filter)
|
|
23
|
+
# # => nil
|
|
24
|
+
#
|
|
25
|
+
# @return [FilterColumn, nil]
|
|
26
|
+
#
|
|
27
|
+
# @since 1.2.0
|
|
28
|
+
def column_for_attribute(name)
|
|
29
|
+
filter = self.class.filters[name]
|
|
30
|
+
FilterColumn.intern(filter.database_column_type) if filter
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Returns true if a filter of that name exists.
|
|
34
|
+
#
|
|
35
|
+
# @param name [String, Symbol] The name of a filter.
|
|
36
|
+
#
|
|
37
|
+
# @example
|
|
38
|
+
# class Interaction < ActiveInteraction::Base
|
|
39
|
+
# string :email, default: nil
|
|
40
|
+
#
|
|
41
|
+
# def execute; end
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# Interaction.new.has_attribute?(:email)
|
|
45
|
+
# # => true
|
|
46
|
+
#
|
|
47
|
+
# Interaction.new.has_attribute?(:not_a_filter)
|
|
48
|
+
# # => false
|
|
49
|
+
#
|
|
50
|
+
# @return [Boolean]
|
|
51
|
+
#
|
|
52
|
+
# @since 1.5.0
|
|
53
|
+
def has_attribute?(name) # rubocop:disable Style/PredicateName
|
|
54
|
+
self.class.filters.key?(name.to_sym)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -6,14 +6,12 @@ module ActiveInteraction
|
|
|
6
6
|
#
|
|
7
7
|
# @note Must be included after `ActiveModel::Validations`.
|
|
8
8
|
#
|
|
9
|
-
# Runs code
|
|
10
|
-
# validation errors.
|
|
9
|
+
# Runs code and provides the result.
|
|
11
10
|
#
|
|
12
11
|
# @private
|
|
13
12
|
module Runnable
|
|
14
13
|
extend ActiveSupport::Concern
|
|
15
14
|
include ActiveModel::Validations
|
|
16
|
-
include ActiveInteraction::Transactable
|
|
17
15
|
|
|
18
16
|
included do
|
|
19
17
|
define_callbacks :execute
|
|
@@ -41,13 +39,8 @@ module ActiveInteraction
|
|
|
41
39
|
#
|
|
42
40
|
# @return (see #result)
|
|
43
41
|
def result=(result)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
@_interaction_valid = true
|
|
47
|
-
else
|
|
48
|
-
@_interaction_result = nil
|
|
49
|
-
@_interaction_valid = false
|
|
50
|
-
end
|
|
42
|
+
@_interaction_result = result
|
|
43
|
+
@_interaction_valid = errors.empty?
|
|
51
44
|
end
|
|
52
45
|
|
|
53
46
|
# @return [Boolean]
|
|
@@ -82,15 +75,12 @@ module ActiveInteraction
|
|
|
82
75
|
def run
|
|
83
76
|
return unless valid?
|
|
84
77
|
|
|
85
|
-
self.result =
|
|
78
|
+
self.result =
|
|
86
79
|
begin
|
|
87
80
|
run_callbacks(:execute) { execute }
|
|
88
81
|
rescue Interrupt => interrupt
|
|
89
82
|
merge_errors_onto_base(interrupt.outcome.errors)
|
|
90
|
-
|
|
91
|
-
raise ActiveRecord::Rollback if self.class.transaction?
|
|
92
83
|
end
|
|
93
|
-
end
|
|
94
84
|
end
|
|
95
85
|
|
|
96
86
|
def merge_errors_onto_base(new_errors)
|
|
@@ -67,7 +67,7 @@ module ActiveInteraction
|
|
|
67
67
|
# @param filter_name [Symbol]
|
|
68
68
|
# @param input_value [Object]
|
|
69
69
|
def initialize(filter_name, input_value)
|
|
70
|
-
super()
|
|
70
|
+
super("#{filter_name}: #{input_value.inspect}")
|
|
71
71
|
|
|
72
72
|
@filter_name = filter_name
|
|
73
73
|
@input_value = input_value
|
|
@@ -89,72 +89,20 @@ module ActiveInteraction
|
|
|
89
89
|
end
|
|
90
90
|
private_constant :Interrupt
|
|
91
91
|
|
|
92
|
-
# An extension that provides
|
|
93
|
-
# and testing easier.
|
|
92
|
+
# An extension that provides the ability to merge other errors into itself.
|
|
94
93
|
class Errors < ActiveModel::Errors
|
|
95
|
-
# Maps attributes to arrays of symbolic messages.
|
|
96
|
-
#
|
|
97
|
-
# @return [Hash{Symbol => Array<Symbol>}]
|
|
98
|
-
attr_reader :symbolic
|
|
99
|
-
|
|
100
|
-
# Adds a symbolic error message to an attribute.
|
|
101
|
-
#
|
|
102
|
-
# @example
|
|
103
|
-
# errors.add_sym(:attribute)
|
|
104
|
-
# errors.symbolic
|
|
105
|
-
# # => {:attribute=>[:invalid]}
|
|
106
|
-
# errors.messages
|
|
107
|
-
# # => {:attribute=>["is invalid"]}
|
|
108
|
-
#
|
|
109
|
-
# @param attribute [Symbol] The attribute to add an error to.
|
|
110
|
-
# @param symbol [Symbol, nil] The symbolic error to add.
|
|
111
|
-
# @param message [String, Symbol, Proc, nil] The message to add.
|
|
112
|
-
# @param options [Hash]
|
|
113
|
-
#
|
|
114
|
-
# @return (see #symbolic)
|
|
115
|
-
#
|
|
116
|
-
# @see ActiveModel::Errors#add
|
|
117
|
-
def add_sym(attribute, symbol = :invalid, message = nil, options = {})
|
|
118
|
-
add(attribute, message || symbol, options)
|
|
119
|
-
|
|
120
|
-
symbolic[attribute] += [symbol]
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# @see ActiveModel::Errors#initialize
|
|
124
|
-
#
|
|
125
|
-
# @private
|
|
126
|
-
def initialize(*)
|
|
127
|
-
@symbolic = Hash.new([]).with_indifferent_access
|
|
128
|
-
|
|
129
|
-
super
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
# @see ActiveModel::Errors#initialize_dup
|
|
133
|
-
#
|
|
134
|
-
# @private
|
|
135
|
-
def initialize_dup(other)
|
|
136
|
-
@symbolic = other.symbolic.with_indifferent_access
|
|
137
|
-
|
|
138
|
-
super
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
# @see ActiveModel::Errors#clear
|
|
142
|
-
#
|
|
143
|
-
# @private
|
|
144
|
-
def clear
|
|
145
|
-
symbolic.clear
|
|
146
|
-
|
|
147
|
-
super
|
|
148
|
-
end
|
|
149
|
-
|
|
150
94
|
# Merge other errors into this one.
|
|
151
95
|
#
|
|
152
96
|
# @param other [Errors]
|
|
153
97
|
#
|
|
154
98
|
# @return [Errors]
|
|
155
99
|
def merge!(other)
|
|
156
|
-
|
|
157
|
-
|
|
100
|
+
if other.respond_to?(:details)
|
|
101
|
+
merge_details!(other)
|
|
102
|
+
else
|
|
103
|
+
merge_messages!(other)
|
|
104
|
+
end
|
|
105
|
+
|
|
158
106
|
self
|
|
159
107
|
end
|
|
160
108
|
|
|
@@ -168,12 +116,12 @@ module ActiveInteraction
|
|
|
168
116
|
end
|
|
169
117
|
end
|
|
170
118
|
|
|
171
|
-
def
|
|
172
|
-
other.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
119
|
+
def merge_details!(other)
|
|
120
|
+
other.details.each do |attribute, details|
|
|
121
|
+
details.each do |detail|
|
|
122
|
+
detail = detail.dup
|
|
123
|
+
error = detail.delete(:error)
|
|
124
|
+
add(attribute, error, detail) unless added?(attribute, error, detail)
|
|
177
125
|
end
|
|
178
126
|
end
|
|
179
127
|
end
|
|
@@ -48,30 +48,33 @@ module ActiveInteraction
|
|
|
48
48
|
super do |klass, names, options|
|
|
49
49
|
filter = klass.new(name.to_s.singularize.to_sym, options, &block)
|
|
50
50
|
|
|
51
|
-
validate(filter, names)
|
|
51
|
+
validate!(filter, names)
|
|
52
52
|
|
|
53
53
|
filters[filter.name] = filter
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
def model(*)
|
|
58
|
+
super
|
|
59
|
+
end
|
|
60
|
+
ActiveInteraction.deprecate self, :model, 'use `object` instead'
|
|
61
|
+
|
|
57
62
|
private
|
|
58
63
|
|
|
59
64
|
# @return [Array<Class>]
|
|
60
65
|
def classes
|
|
61
66
|
result = [Array]
|
|
67
|
+
return result unless Object.const_defined?(:ActiveRecord)
|
|
68
|
+
return result unless ActiveRecord.const_defined?(:Relation)
|
|
62
69
|
|
|
63
|
-
|
|
64
|
-
result.push(ActiveRecord::Relation)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
result
|
|
70
|
+
result.push(ActiveRecord::Relation)
|
|
68
71
|
end
|
|
69
72
|
|
|
70
73
|
# @param filter [Filter]
|
|
71
74
|
# @param names [Array<Symbol>]
|
|
72
75
|
#
|
|
73
76
|
# @raise [InvalidFilterError]
|
|
74
|
-
def validate(filter, names)
|
|
77
|
+
def validate!(filter, names)
|
|
75
78
|
unless filters.empty?
|
|
76
79
|
fail InvalidFilterError, 'multiple filters in array block'
|
|
77
80
|
end
|
|
@@ -80,11 +83,11 @@ module ActiveInteraction
|
|
|
80
83
|
fail InvalidFilterError, 'attribute names in array block'
|
|
81
84
|
end
|
|
82
85
|
|
|
83
|
-
# rubocop:disable GuardClause
|
|
84
86
|
if filter.default?
|
|
85
87
|
fail InvalidDefaultError, 'default values in array block'
|
|
86
88
|
end
|
|
87
|
-
|
|
89
|
+
|
|
90
|
+
nil
|
|
88
91
|
end
|
|
89
92
|
end
|
|
90
93
|
end
|
|
@@ -4,9 +4,8 @@ module ActiveInteraction
|
|
|
4
4
|
class Base
|
|
5
5
|
# @!method self.file(*attributes, options = {})
|
|
6
6
|
# Creates accessors for the attributes and ensures that values passed to
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# in Rails params that include a file upload.
|
|
7
|
+
# the attributes respond to the `eof?` method. This is useful when passing
|
|
8
|
+
# in Rails params that include a file upload or another generic IO object.
|
|
10
9
|
#
|
|
11
10
|
# @!macro filter_method_params
|
|
12
11
|
#
|
|
@@ -15,35 +14,17 @@ module ActiveInteraction
|
|
|
15
14
|
end
|
|
16
15
|
|
|
17
16
|
# @private
|
|
18
|
-
class FileFilter <
|
|
17
|
+
class FileFilter < InterfaceFilter
|
|
19
18
|
register :file
|
|
20
19
|
|
|
21
|
-
def cast(value)
|
|
22
|
-
value = extract_file(value)
|
|
23
|
-
|
|
24
|
-
case value
|
|
25
|
-
when File, Tempfile
|
|
26
|
-
value
|
|
27
|
-
else
|
|
28
|
-
super
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
20
|
def database_column_type
|
|
33
21
|
self.class.slug
|
|
34
22
|
end
|
|
35
23
|
|
|
36
24
|
private
|
|
37
25
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
# @return [File]
|
|
41
|
-
def extract_file(value)
|
|
42
|
-
if value.respond_to?(:tempfile)
|
|
43
|
-
value.tempfile
|
|
44
|
-
else
|
|
45
|
-
value
|
|
46
|
-
end
|
|
26
|
+
def methods
|
|
27
|
+
[:eof?]
|
|
47
28
|
end
|
|
48
29
|
end
|
|
49
30
|
end
|
|
@@ -8,17 +8,13 @@ module ActiveInteraction
|
|
|
8
8
|
#
|
|
9
9
|
# @!macro filter_method_params
|
|
10
10
|
# @param block [Proc] filter methods to apply for select keys
|
|
11
|
-
# @option options [Boolean] :strip (true)
|
|
12
|
-
# keys are symbolized. Ruby does not GC symbols so this can cause
|
|
13
|
-
# memory bloat. Setting this option to `false` and passing in non-safe
|
|
14
|
-
# input (e.g. Rails `params`) opens your software to a denial of
|
|
15
|
-
# service attack.)
|
|
11
|
+
# @option options [Boolean] :strip (true) remove unknown keys
|
|
16
12
|
#
|
|
17
13
|
# @example
|
|
18
14
|
# hash :order
|
|
19
15
|
# @example
|
|
20
16
|
# hash :order do
|
|
21
|
-
#
|
|
17
|
+
# object :item
|
|
22
18
|
# integer :quantity, default: 1
|
|
23
19
|
# end
|
|
24
20
|
end
|
|
@@ -32,11 +28,12 @@ module ActiveInteraction
|
|
|
32
28
|
def cast(value)
|
|
33
29
|
case value
|
|
34
30
|
when Hash
|
|
35
|
-
value =
|
|
31
|
+
value = value.with_indifferent_access
|
|
32
|
+
initial = strip? ? ActiveSupport::HashWithIndifferentAccess.new : value
|
|
36
33
|
|
|
37
|
-
filters.each_with_object(
|
|
34
|
+
filters.each_with_object(initial) do |(name, filter), h|
|
|
38
35
|
clean_value(h, name.to_s, filter, value)
|
|
39
|
-
end
|
|
36
|
+
end
|
|
40
37
|
else
|
|
41
38
|
super
|
|
42
39
|
end
|
|
@@ -52,6 +49,11 @@ module ActiveInteraction
|
|
|
52
49
|
end
|
|
53
50
|
end
|
|
54
51
|
|
|
52
|
+
def model(*)
|
|
53
|
+
super
|
|
54
|
+
end
|
|
55
|
+
ActiveInteraction.deprecate self, :model, 'use `object` instead'
|
|
56
|
+
|
|
55
57
|
private
|
|
56
58
|
|
|
57
59
|
def clean_value(h, name, filter, value)
|
|
@@ -74,9 +76,5 @@ module ActiveInteraction
|
|
|
74
76
|
def strip?
|
|
75
77
|
options.fetch(:strip, true)
|
|
76
78
|
end
|
|
77
|
-
|
|
78
|
-
def stringify_the_symbol_keys(hash)
|
|
79
|
-
hash.transform_keys { |key| key.is_a?(Symbol) ? key.to_s : key }
|
|
80
|
-
end
|
|
81
79
|
end
|
|
82
80
|
end
|
|
@@ -7,8 +7,8 @@ module ActiveInteraction
|
|
|
7
7
|
# the attributes implement an interface.
|
|
8
8
|
#
|
|
9
9
|
# @!macro filter_method_params
|
|
10
|
-
# @option options [Array<Symbol>] :methods ([]) the methods that
|
|
11
|
-
# conforming to this interface should respond to
|
|
10
|
+
# @option options [Array<String,Symbol>] :methods ([]) the methods that
|
|
11
|
+
# objects conforming to this interface should respond to
|
|
12
12
|
#
|
|
13
13
|
# @example
|
|
14
14
|
# interface :anything
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module ActiveInteraction
|
|
4
4
|
class Base
|
|
5
|
-
# @!method self.
|
|
5
|
+
# @!method self.object(*attributes, options = {})
|
|
6
6
|
# Creates accessors for the attributes and ensures that values passed to
|
|
7
7
|
# the attributes are the correct class.
|
|
8
8
|
#
|
|
@@ -11,14 +11,14 @@ module ActiveInteraction
|
|
|
11
11
|
# Class name used to ensure the value.
|
|
12
12
|
#
|
|
13
13
|
# @example
|
|
14
|
-
#
|
|
14
|
+
# object :account
|
|
15
15
|
# @example
|
|
16
|
-
#
|
|
16
|
+
# object :account, class: User
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
# @private
|
|
20
|
-
class
|
|
21
|
-
register :
|
|
20
|
+
class ObjectFilter < Filter
|
|
21
|
+
register :object
|
|
22
22
|
|
|
23
23
|
def cast(value, reconstantize = true)
|
|
24
24
|
@klass ||= klass
|
|
@@ -49,7 +49,7 @@ module ActiveInteraction
|
|
|
49
49
|
#
|
|
50
50
|
# @return [Boolean]
|
|
51
51
|
def matches?(value)
|
|
52
|
-
@klass === value || # rubocop:disable CaseEquality
|
|
52
|
+
@klass === value || # rubocop:disable Style/CaseEquality
|
|
53
53
|
value.is_a?(@klass)
|
|
54
54
|
end
|
|
55
55
|
end
|
|
@@ -25,10 +25,10 @@ module ActiveInteraction
|
|
|
25
25
|
def error_args(filter, error)
|
|
26
26
|
case error
|
|
27
27
|
when InvalidNestedValueError
|
|
28
|
-
[filter.name, :invalid_nested,
|
|
28
|
+
[filter.name, :invalid_nested,
|
|
29
29
|
name: error.filter_name.inspect, value: error.input_value.inspect]
|
|
30
30
|
when InvalidValueError
|
|
31
|
-
[filter.name, :invalid_type,
|
|
31
|
+
[filter.name, :invalid_type, type: type(filter)]
|
|
32
32
|
when MissingValueError
|
|
33
33
|
[filter.name, :missing]
|
|
34
34
|
end
|