subroutine 3.0.2 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.MD +27 -0
- data/lib/subroutine/association_fields/configuration.rb +4 -0
- data/lib/subroutine/association_fields.rb +11 -10
- data/lib/subroutine/fields/configuration.rb +4 -2
- data/lib/subroutine/fields.rb +96 -93
- data/lib/subroutine/op.rb +11 -2
- data/lib/subroutine/outputs/configuration.rb +0 -2
- data/lib/subroutine/outputs.rb +0 -1
- data/lib/subroutine/version.rb +3 -3
- data/lib/subroutine.rb +23 -0
- data/test/subroutine/association_test.rb +10 -0
- data/test/subroutine/base_test.rb +6 -0
- data/test/subroutine/fields_test.rb +77 -6
- data/test/subroutine/type_caster_test.rb +28 -0
- data/test/support/ops.rb +7 -4
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5b099aef955fc06d44e4a9c628f261e15300fe1dca247c4f8ee042be538037a8
|
4
|
+
data.tar.gz: 3879046fc03f34b01c243f8a1cc1630c30075f6c186593509935b4ae1b62d6a9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 60327bcf9acf93b74b5ff94b45d18c7765830f2c2456481585274b75348b316dca245597f332fe95c54b47d6a91f21de7876c96bdcfb3418d213a38d45f1fbdb
|
7
|
+
data.tar.gz: aa61977d8dec95499096f58d9590a059796d0cfe211f604237cb53ab69bc72ad634515c621ad76b128dea11bfba6acb1d6c82b607c22c8fe3fae74364353a3cc
|
data/CHANGELOG.MD
CHANGED
@@ -1,7 +1,34 @@
|
|
1
|
+
## Subroutine 4.1.0
|
2
|
+
|
3
|
+
A field can no opt out of the natural assignment behavior of ActiveSupport::HashWithIndifferentAccess. Top level param groups are still accessible via indifferent access but if a field sets the `bypass_indifferent_assignment` option to `true` the HashWithIndifferentAccess assignment behavior will be bypassed in favor of direct Hash-like assignment.
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
class MyOp < Subroutine::Op
|
7
|
+
|
8
|
+
object :some_hash
|
9
|
+
object :some_other_hash, bypass_indifferent_assignment: true
|
10
|
+
|
11
|
+
end
|
12
|
+
```
|
13
|
+
|
14
|
+
## Subroutine 4.0.1
|
15
|
+
|
16
|
+
Association fields can now use `find_by()` instead of `find_by!()` by passing a `raise_on_miss: false` option. This places the responsibility on the op to manage nil cases rather than handling RecordNotFound errors.
|
17
|
+
|
18
|
+
## Subroutine 4.0
|
19
|
+
|
20
|
+
The `Subroutine::Fields` module now contains a class_attribute that allows the altering of param accessor behaviors. `include_defaults_in_params` is now available to opt into including the default values in usage of the `all_params (alias params)` method. Backwards compatibility is preserved by defaulting the value to `false`. If switched to true, when an input is omitted and the field is configured with a default value, it will be included in the `all_params` object.
|
21
|
+
|
22
|
+
Removed all usage of `ungrouped` params and refactored the storage of grouped params. Params are now stored in either the provided group or the defaults group and accessed via provided_params, params, default_params, and params_with_defaults. Grouped params are accessed the same way but with the group name prefixed eg. `my_private_default_params`.
|
23
|
+
|
1
24
|
## Subroutine 3.0
|
2
25
|
|
3
26
|
Add support for Rails 6.1. Drop support for Rails 6.0 and lower.
|
4
27
|
|
28
|
+
## Subroutine 2.3
|
29
|
+
|
30
|
+
Support dynamic types for foreign keys on association fields. The class type is used at runtime to determine the casting behavior of the foreign key field.
|
31
|
+
|
5
32
|
## Subroutine 2.2
|
6
33
|
|
7
34
|
Add `type` validation for Output.
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "delegate"
|
4
|
-
require "active_support/concern"
|
5
3
|
require "subroutine/association_fields/configuration"
|
6
4
|
require "subroutine/association_fields/association_type_mismatch_error"
|
7
5
|
|
@@ -72,8 +70,7 @@ module Subroutine
|
|
72
70
|
field config.foreign_type_method, config.build_foreign_type_field
|
73
71
|
else
|
74
72
|
class_eval <<-EV, __FILE__, __LINE__ + 1
|
75
|
-
|
76
|
-
def #{config.foreign_type_method}
|
73
|
+
silence_redefinition_of_method def #{config.foreign_type_method}
|
77
74
|
#{config.inferred_foreign_type.inspect}
|
78
75
|
end
|
79
76
|
EV
|
@@ -113,20 +110,20 @@ module Subroutine
|
|
113
110
|
out
|
114
111
|
end
|
115
112
|
|
116
|
-
def set_field_with_association(field_name, value, opts
|
113
|
+
def set_field_with_association(field_name, value, **opts)
|
117
114
|
config = get_field_config(field_name)
|
118
115
|
|
119
116
|
if config&.behavior == :association
|
120
117
|
maybe_raise_on_association_type_mismatch!(config, value)
|
121
|
-
set_field(config.foreign_type_method, value&.class&.name, opts) if config.polymorphic?
|
122
|
-
set_field(config.foreign_key_method, value&.send(config.find_by), opts)
|
118
|
+
set_field(config.foreign_type_method, value&.class&.name, **opts) if config.polymorphic?
|
119
|
+
set_field(config.foreign_key_method, value&.send(config.find_by), **opts)
|
123
120
|
association_cache[config.field_name] = value
|
124
121
|
else
|
125
122
|
if config&.behavior == :association_component
|
126
123
|
clear_field_without_association(config.association_name)
|
127
124
|
end
|
128
125
|
|
129
|
-
set_field_without_association(field_name, value, opts)
|
126
|
+
set_field_without_association(field_name, value, **opts)
|
130
127
|
end
|
131
128
|
end
|
132
129
|
|
@@ -180,7 +177,7 @@ module Subroutine
|
|
180
177
|
get_field(config.foreign_type_method)
|
181
178
|
end
|
182
179
|
|
183
|
-
klass = klass.
|
180
|
+
klass = klass.camelize.constantize if klass.is_a?(String)
|
184
181
|
return nil unless klass
|
185
182
|
|
186
183
|
foreign_key = config.foreign_key_method
|
@@ -190,7 +187,11 @@ module Subroutine
|
|
190
187
|
scope = klass.all
|
191
188
|
scope = scope.unscoped if config.unscoped?
|
192
189
|
|
193
|
-
|
190
|
+
if config.raise_on_miss?
|
191
|
+
scope.find_by!(config.find_by => value)
|
192
|
+
else
|
193
|
+
scope.find_by(config.find_by => value)
|
194
|
+
end
|
194
195
|
end
|
195
196
|
|
196
197
|
def maybe_raise_on_association_type_mismatch!(config, record)
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "delegate"
|
4
|
-
|
5
3
|
module Subroutine
|
6
4
|
module Fields
|
7
5
|
class Configuration < ::SimpleDelegator
|
@@ -78,6 +76,10 @@ module Subroutine
|
|
78
76
|
config[:field_reader] != false
|
79
77
|
end
|
80
78
|
|
79
|
+
def bypass_indifferent_assignment?
|
80
|
+
config[:bypass_indifferent_assignment] == true
|
81
|
+
end
|
82
|
+
|
81
83
|
def groups
|
82
84
|
config[:groups] || NO_GROUPS
|
83
85
|
end
|
data/lib/subroutine/fields.rb
CHANGED
@@ -1,10 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/concern"
|
4
|
-
require "active_support/core_ext/object/duplicable"
|
5
|
-
require "active_support/core_ext/hash/indifferent_access"
|
6
|
-
require "active_support/core_ext/object/deep_dup"
|
7
|
-
|
8
3
|
require "subroutine/type_caster"
|
9
4
|
require "subroutine/fields/configuration"
|
10
5
|
require "subroutine/fields/mass_assignment_error"
|
@@ -27,11 +22,9 @@ module Subroutine
|
|
27
22
|
end
|
28
23
|
|
29
24
|
included do
|
30
|
-
class_attribute :
|
31
|
-
|
32
|
-
|
33
|
-
class_attribute :field_groups
|
34
|
-
self.field_groups = Set.new
|
25
|
+
class_attribute :include_defaults_in_params, instance_accessor: false, instance_predicate: false
|
26
|
+
class_attribute :field_configurations, default: {}
|
27
|
+
class_attribute :fields_by_group, default: Hash.new { |h, k| h[k] = Set.new }
|
35
28
|
end
|
36
29
|
|
37
30
|
module ClassMethods
|
@@ -44,8 +37,15 @@ module Subroutine
|
|
44
37
|
|
45
38
|
ensure_field_accessors(config)
|
46
39
|
|
47
|
-
config.groups.
|
48
|
-
|
40
|
+
if config.groups.any?
|
41
|
+
new_fields_by_group = self.fields_by_group.deep_dup
|
42
|
+
|
43
|
+
config.groups.each do |group_name|
|
44
|
+
new_fields_by_group[group_name] << config.field_name
|
45
|
+
ensure_group_accessors(group_name)
|
46
|
+
end
|
47
|
+
|
48
|
+
self.fields_by_group = new_fields_by_group
|
49
49
|
end
|
50
50
|
|
51
51
|
config
|
@@ -75,18 +75,16 @@ module Subroutine
|
|
75
75
|
end
|
76
76
|
alias inputs_from fields_from
|
77
77
|
|
78
|
-
def fields_in_group(group_name)
|
79
|
-
field_configurations.each_with_object({}) do |(field_name, config), h|
|
80
|
-
next unless config.in_group?(group_name)
|
81
|
-
|
82
|
-
h[field_name] = config
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
78
|
def get_field_config(field_name)
|
87
79
|
field_configurations[field_name.to_sym]
|
88
80
|
end
|
89
81
|
|
82
|
+
def include_defaults_in_params?
|
83
|
+
return include_defaults_in_params unless include_defaults_in_params.nil?
|
84
|
+
|
85
|
+
Subroutine.include_defaults_in_params?
|
86
|
+
end
|
87
|
+
|
90
88
|
def respond_to_missing?(method_name, *args, &block)
|
91
89
|
::Subroutine::TypeCaster.casters.key?(method_name.to_sym) || super
|
92
90
|
end
|
@@ -106,29 +104,33 @@ module Subroutine
|
|
106
104
|
protected
|
107
105
|
|
108
106
|
def ensure_group_accessors(group_name)
|
109
|
-
|
110
|
-
|
107
|
+
class_eval <<-EV, __FILE__, __LINE__ + 1
|
108
|
+
silence_redefinition_of_method def #{group_name}_params
|
109
|
+
return #{group_name}_params_with_defaults if include_defaults_in_params?
|
111
110
|
|
112
|
-
|
111
|
+
#{group_name}_provided_params
|
112
|
+
end
|
113
113
|
|
114
|
-
|
115
|
-
|
116
|
-
param_groups[:#{group_name}]
|
114
|
+
silence_redefinition_of_method def #{group_name}_provided_params
|
115
|
+
get_param_group(:#{group_name}_provided)
|
117
116
|
end
|
118
117
|
|
119
|
-
def #{group_name}_default_params
|
120
|
-
|
121
|
-
all_default_params.slice(*group_field_names)
|
118
|
+
silence_redefinition_of_method def #{group_name}_default_params
|
119
|
+
get_param_group(:#{group_name}_default)
|
122
120
|
end
|
123
121
|
alias #{group_name}_defaults #{group_name}_default_params
|
124
122
|
|
125
|
-
def #{group_name}_params_with_default_params
|
126
|
-
|
123
|
+
silence_redefinition_of_method def #{group_name}_params_with_default_params
|
124
|
+
param_cache[:#{group_name}_provided_and_default] ||= begin
|
125
|
+
#{group_name}_default_params.merge(#{group_name}_provided_params)
|
126
|
+
end
|
127
127
|
end
|
128
128
|
alias #{group_name}_params_with_defaults #{group_name}_params_with_default_params
|
129
129
|
|
130
|
-
def without_#{group_name}_params
|
131
|
-
|
130
|
+
silence_redefinition_of_method def without_#{group_name}_params
|
131
|
+
param_cache[:without_#{group_name}] ||= begin
|
132
|
+
all_params.except(*#{group_name}_params.keys)
|
133
|
+
end
|
132
134
|
end
|
133
135
|
EV
|
134
136
|
end
|
@@ -136,17 +138,15 @@ module Subroutine
|
|
136
138
|
def ensure_field_accessors(config)
|
137
139
|
if config.field_writer?
|
138
140
|
class_eval <<-EV, __FILE__, __LINE__ + 1
|
139
|
-
|
140
|
-
|
141
|
-
set_field(:#{config.field_name}, v)
|
141
|
+
silence_redefinition_of_method def #{config.field_name}=(v)
|
142
|
+
set_field(:#{config.field_name}, v, group_type: :provided)
|
142
143
|
end
|
143
144
|
EV
|
144
145
|
end
|
145
146
|
|
146
147
|
if config.field_reader?
|
147
148
|
class_eval <<-EV, __FILE__, __LINE__ + 1
|
148
|
-
|
149
|
-
def #{config.field_name}
|
149
|
+
silence_redefinition_of_method def #{config.field_name}
|
150
150
|
get_field(:#{config.field_name})
|
151
151
|
end
|
152
152
|
EV
|
@@ -159,88 +159,108 @@ module Subroutine
|
|
159
159
|
if ::Subroutine::Fields.action_controller_params_loaded? && inputs.is_a?(::ActionController::Parameters)
|
160
160
|
inputs = inputs.to_unsafe_h if inputs.respond_to?(:to_unsafe_h)
|
161
161
|
end
|
162
|
-
|
163
|
-
|
162
|
+
inputs.each_pair do |k, v|
|
163
|
+
set_param_group_value(:original, k, v)
|
164
|
+
end
|
164
165
|
mass_assign_initial_params
|
165
166
|
end
|
166
167
|
|
168
|
+
def include_defaults_in_params?
|
169
|
+
self.class.include_defaults_in_params?
|
170
|
+
end
|
171
|
+
|
172
|
+
def param_cache
|
173
|
+
@param_cache ||= {}
|
174
|
+
end
|
175
|
+
|
167
176
|
def param_groups
|
168
177
|
@param_groups ||= Hash.new { |h, k| h[k] = {}.with_indifferent_access }
|
169
178
|
end
|
170
179
|
|
171
180
|
def get_param_group(name)
|
172
|
-
|
181
|
+
name = name.to_sym
|
182
|
+
return param_groups[name] if param_groups.key?(name)
|
183
|
+
|
184
|
+
param_groups[name] = yield if block_given?
|
185
|
+
param_groups[name]
|
173
186
|
end
|
174
187
|
|
175
|
-
def
|
176
|
-
|
188
|
+
def set_param_group_value(name, key, value)
|
189
|
+
config = get_field_config(key)
|
190
|
+
group = get_param_group(name)
|
191
|
+
|
192
|
+
if config&.bypass_indifferent_assignment?
|
193
|
+
group.regular_writer(group.send(:convert_key, key), value)
|
194
|
+
else
|
195
|
+
group[key.to_sym] = value
|
196
|
+
end
|
177
197
|
end
|
178
198
|
|
179
|
-
def
|
180
|
-
get_param_group(:
|
199
|
+
def original_params
|
200
|
+
get_param_group(:original)
|
181
201
|
end
|
182
202
|
|
183
|
-
def
|
184
|
-
get_param_group(:
|
203
|
+
def all_provided_params
|
204
|
+
get_param_group(:provided)
|
185
205
|
end
|
186
|
-
alias
|
206
|
+
alias provided_params all_provided_params
|
187
207
|
|
188
208
|
def all_default_params
|
189
209
|
get_param_group(:default)
|
190
210
|
end
|
191
211
|
alias defaults all_default_params
|
212
|
+
alias default_params all_default_params
|
192
213
|
|
193
|
-
def
|
194
|
-
|
195
|
-
end
|
196
|
-
alias params_with_defaults all_params_with_defaults
|
214
|
+
def all_params
|
215
|
+
return all_params_with_default_params if include_defaults_in_params?
|
197
216
|
|
198
|
-
|
199
|
-
default_params.slice(*ungrouped_fields.keys)
|
217
|
+
all_provided_params
|
200
218
|
end
|
219
|
+
alias params all_params
|
201
220
|
|
202
|
-
def
|
203
|
-
|
221
|
+
def all_params_with_default_params
|
222
|
+
param_cache[:provided_and_default] ||= all_default_params.merge(all_provided_params)
|
204
223
|
end
|
224
|
+
alias all_params_with_defaults all_params_with_default_params
|
225
|
+
alias params_with_defaults all_params_with_defaults
|
226
|
+
|
205
227
|
|
206
228
|
def get_field_config(field_name)
|
207
229
|
self.class.get_field_config(field_name)
|
208
230
|
end
|
209
231
|
|
210
|
-
# check if a specific field was provided
|
211
232
|
def field_provided?(key)
|
212
|
-
|
233
|
+
all_provided_params.key?(key)
|
213
234
|
end
|
214
235
|
|
215
236
|
def get_field(name)
|
216
|
-
|
237
|
+
all_params_with_default_params[name]
|
217
238
|
end
|
218
239
|
|
219
|
-
def set_field(name, value,
|
240
|
+
def set_field(name, value, group_type: :provided)
|
220
241
|
config = get_field_config(name)
|
221
|
-
@provided_fields[name] = true unless opts[:track_provided] == false
|
222
242
|
value = attempt_cast(value, config) do |e|
|
223
243
|
"Error during assignment of field `#{name}`: #{e}"
|
224
244
|
end
|
225
|
-
|
226
|
-
|
245
|
+
|
246
|
+
set_param_group_value(group_type, config.field_name, value)
|
247
|
+
|
248
|
+
config.groups.each do |group_name|
|
249
|
+
set_param_group_value(:"#{group_name}_#{group_type}", config.field_name, value)
|
227
250
|
end
|
251
|
+
|
252
|
+
param_cache.clear
|
253
|
+
|
228
254
|
value
|
229
255
|
end
|
230
256
|
|
231
257
|
def clear_field(name)
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
def fields_in_group(group_name)
|
238
|
-
self.class.fields_in_group(group_name)
|
239
|
-
end
|
258
|
+
param_cache.clear
|
259
|
+
param_groups.each_pair do |key, group|
|
260
|
+
next if key == :original
|
261
|
+
next if key == :default
|
240
262
|
|
241
|
-
|
242
|
-
fields.select { |f| f.groups.empty? }.each_with_object({}) do |f, h|
|
243
|
-
h[f.name] = f
|
263
|
+
group.delete(name)
|
244
264
|
end
|
245
265
|
end
|
246
266
|
|
@@ -253,16 +273,12 @@ module Subroutine
|
|
253
273
|
end
|
254
274
|
|
255
275
|
if original_params.key?(field_name)
|
256
|
-
set_field(field_name, original_params[field_name])
|
276
|
+
set_field(field_name, original_params[field_name], group_type: :provided)
|
257
277
|
end
|
258
278
|
|
259
279
|
next unless config.has_default?
|
260
280
|
|
261
|
-
|
262
|
-
"Error for default `#{field}`: #{e}"
|
263
|
-
end
|
264
|
-
|
265
|
-
param_groups[:default][field_name] = value
|
281
|
+
set_field(field_name, config.get_default, group_type: :default)
|
266
282
|
end
|
267
283
|
end
|
268
284
|
|
@@ -273,18 +289,5 @@ module Subroutine
|
|
273
289
|
raise ::Subroutine::TypeCaster::TypeCastError, message, e.backtrace
|
274
290
|
end
|
275
291
|
|
276
|
-
def each_param_group_for_field(name)
|
277
|
-
config = get_field_config(name)
|
278
|
-
yield all_params
|
279
|
-
|
280
|
-
if config.groups.empty?
|
281
|
-
yield ungrouped_params
|
282
|
-
else
|
283
|
-
config.groups.each do |group_name|
|
284
|
-
yield param_groups[group_name]
|
285
|
-
end
|
286
|
-
end
|
287
|
-
end
|
288
|
-
|
289
292
|
end
|
290
293
|
end
|
data/lib/subroutine/op.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_model"
|
4
|
-
|
5
3
|
require "subroutine/failure"
|
6
4
|
require "subroutine/fields"
|
7
5
|
require "subroutine/outputs"
|
@@ -48,6 +46,17 @@ module Subroutine
|
|
48
46
|
yield self if block_given?
|
49
47
|
end
|
50
48
|
|
49
|
+
def inspect
|
50
|
+
values = provided_params.map do |(key, value)|
|
51
|
+
"#{key}: #{value.inspect}"
|
52
|
+
end
|
53
|
+
values.sort!
|
54
|
+
values = values.join(", ")
|
55
|
+
|
56
|
+
oid = format('%x', (object_id << 1))
|
57
|
+
"#<#{self.class}:0x#{oid} #{values}>"
|
58
|
+
end
|
59
|
+
|
51
60
|
def submit!
|
52
61
|
begin
|
53
62
|
observe_submission do
|
data/lib/subroutine/outputs.rb
CHANGED
data/lib/subroutine/version.rb
CHANGED
data/lib/subroutine.rb
CHANGED
@@ -1,5 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_model"
|
4
|
+
require "active_support/concern"
|
5
|
+
require "active_support/core_ext/hash/indifferent_access"
|
6
|
+
require "active_support/core_ext/module/redefine_method"
|
7
|
+
require "active_support/core_ext/object/deep_dup"
|
8
|
+
require "active_support/core_ext/object/duplicable"
|
9
|
+
require "active_support/core_ext/string/inflections"
|
10
|
+
require "delegate"
|
11
|
+
|
3
12
|
require "subroutine/version"
|
4
13
|
require "subroutine/fields"
|
5
14
|
require "subroutine/op"
|
15
|
+
|
16
|
+
module Subroutine
|
17
|
+
|
18
|
+
def self.include_defaults_in_params=(bool)
|
19
|
+
@include_defaults_in_params = !!bool
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.include_defaults_in_params?
|
23
|
+
return !!@include_defaults_in_params if defined?(@instance_defaults_in_params)
|
24
|
+
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -335,5 +335,15 @@ module Subroutine
|
|
335
335
|
assert_equal({}, op.without_info_params)
|
336
336
|
end
|
337
337
|
|
338
|
+
def test_find_by_is_used_if_raise_on_miss_is_false
|
339
|
+
all_mock = mock
|
340
|
+
|
341
|
+
::User.expects(:all).returns(all_mock)
|
342
|
+
all_mock.expects(:find_by).with(id: 1).returns(nil)
|
343
|
+
|
344
|
+
op = SafeAssociationOp.new user_type: "User", user_id: 1
|
345
|
+
assert_nil op.user
|
346
|
+
end
|
347
|
+
|
338
348
|
end
|
339
349
|
end
|
@@ -318,5 +318,11 @@ module Subroutine
|
|
318
318
|
assert_equal raw_params, op.params
|
319
319
|
end
|
320
320
|
|
321
|
+
def test_inspect_is_pretty
|
322
|
+
op = SignupOp.new({ email: "foo@bar.com", password: "password123!" })
|
323
|
+
oid = format('%x', (op.object_id << 1))
|
324
|
+
assert_equal "#<SignupOp:0x#{oid} email: \"foo@bar.com\", password: \"password123!\">", op.inspect
|
325
|
+
end
|
326
|
+
|
321
327
|
end
|
322
328
|
end
|
@@ -25,6 +25,25 @@ module Subroutine
|
|
25
25
|
|
26
26
|
end
|
27
27
|
|
28
|
+
class WhateverWithDefaultsIncluded < Whatever
|
29
|
+
self.include_defaults_in_params = true
|
30
|
+
end
|
31
|
+
|
32
|
+
class MutationBase
|
33
|
+
include Subroutine::Fields
|
34
|
+
|
35
|
+
field :foo, group: :three_letter
|
36
|
+
field :bar, group: :three_letter
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
class MutationChild < MutationBase
|
41
|
+
|
42
|
+
field :qux, group: :three_letter
|
43
|
+
field :food, group: :four_letter
|
44
|
+
|
45
|
+
end
|
46
|
+
|
28
47
|
def test_fields_are_configured
|
29
48
|
assert_equal 6, Whatever.field_configurations.size
|
30
49
|
assert_equal :string, Whatever.field_configurations[:foo][:type]
|
@@ -75,17 +94,45 @@ module Subroutine
|
|
75
94
|
|
76
95
|
def test_params_does_not_include_defaults
|
77
96
|
instance = Whatever.new(foo: "abc")
|
97
|
+
assert_equal({ "foo" => "abc" }, instance.provided_params)
|
78
98
|
assert_equal({ "foo" => "foo", "bar" => 3, "qux" => "qux" }, instance.defaults)
|
79
99
|
assert_equal({ "foo" => "abc" }, instance.params)
|
80
100
|
assert_equal({ "foo" => "abc", "bar" => 3, "qux" => "qux" }, instance.params_with_defaults)
|
81
101
|
end
|
82
102
|
|
83
|
-
def
|
103
|
+
def test_params_include_defaults_if_globally_configured
|
104
|
+
Subroutine.stubs(:include_defaults_in_params?).returns(true)
|
84
105
|
instance = Whatever.new(foo: "abc")
|
106
|
+
assert Whatever.include_defaults_in_params?
|
107
|
+
assert_equal({ "foo" => "abc" }, instance.provided_params)
|
108
|
+
assert_equal({ "foo" => "foo", "bar" => 3, "qux" => "qux" }, instance.defaults)
|
109
|
+
assert_equal({ "foo" => "abc", "bar" => 3, "qux" => "qux" }, instance.params)
|
110
|
+
assert_equal({ "foo" => "abc", "bar" => 3, "qux" => "qux" }, instance.params_with_defaults)
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_params_includes_defaults_if_opted_into
|
114
|
+
refute Subroutine.include_defaults_in_params?
|
115
|
+
instance = WhateverWithDefaultsIncluded.new(foo: "abc")
|
116
|
+
assert_equal({ "foo" => "abc" }, instance.provided_params)
|
117
|
+
assert_equal({ "foo" => "foo", "bar" => 3, "qux" => "qux" }, instance.defaults)
|
118
|
+
assert_equal({ "foo" => "abc", "bar" => 3, "qux" => "qux" }, instance.params)
|
119
|
+
assert_equal({ "foo" => "abc", "bar" => 3, "qux" => "qux" }, instance.params_with_defaults)
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_named_params_do_not_include_defaults_unless_asked_for
|
123
|
+
instance = Whatever.new(foo: "abc")
|
124
|
+
assert_equal({}, instance.sekret_provided_params)
|
85
125
|
assert_equal({}, instance.sekret_params)
|
86
126
|
assert_equal({ "bar" => 3 }, instance.sekret_params_with_defaults)
|
87
127
|
end
|
88
128
|
|
129
|
+
def test_named_params_include_defaults_if_configured
|
130
|
+
instance = WhateverWithDefaultsIncluded.new(foo: "abc")
|
131
|
+
assert_equal({}, instance.sekret_provided_params)
|
132
|
+
assert_equal({ "bar" => 3 }, instance.sekret_params)
|
133
|
+
assert_equal({ "bar" => 3 }, instance.sekret_params_with_defaults)
|
134
|
+
end
|
135
|
+
|
89
136
|
def test_fields_can_opt_out_of_mass_assignment
|
90
137
|
assert_raises Subroutine::Fields::MassAssignmentError do
|
91
138
|
Whatever.new(foo: "abc", protekted: "foo")
|
@@ -113,18 +160,28 @@ module Subroutine
|
|
113
160
|
assert_equal "bar", instance.foo
|
114
161
|
end
|
115
162
|
|
163
|
+
def test_set_field_adds_to_provided_params
|
164
|
+
instance = Whatever.new
|
165
|
+
instance.set_field(:foo, "bar")
|
166
|
+
assert_equal true, instance.provided_params.key?(:foo)
|
167
|
+
end
|
168
|
+
|
169
|
+
def test_set_field_can_add_to_the_default_params
|
170
|
+
instance = Whatever.new
|
171
|
+
instance.set_field(:foo, "bar", group_type: :default)
|
172
|
+
assert_equal false, instance.provided_params.key?(:foo)
|
173
|
+
assert_equal "bar", instance.default_params[:foo]
|
174
|
+
end
|
175
|
+
|
116
176
|
def test_group_fields_are_accessible_at_the_class
|
117
|
-
|
118
|
-
assert_equal
|
119
|
-
assert_equal true, results.key?(:bar)
|
120
|
-
assert_equal false, results.key?(:protekted)
|
177
|
+
fields = Whatever.fields_by_group[:sekret].sort
|
178
|
+
assert_equal %i[bar protekted_group_input], fields
|
121
179
|
end
|
122
180
|
|
123
181
|
def test_groups_fields_are_accessible
|
124
182
|
op = Whatever.new(foo: "bar", protekted_group_input: "pgi", bar: 8)
|
125
183
|
assert_equal({ protekted_group_input: "pgi", bar: 8 }.with_indifferent_access, op.sekret_params)
|
126
184
|
assert_equal({ protekted_group_input: "pgi", foo: "bar", bar: 8 }.with_indifferent_access, op.params)
|
127
|
-
assert_equal({ foo: "bar" }.with_indifferent_access, op.ungrouped_params)
|
128
185
|
end
|
129
186
|
|
130
187
|
def test_fields_from_allows_merging_of_config
|
@@ -143,5 +200,19 @@ module Subroutine
|
|
143
200
|
assert_equal(%i[amount_cents credit_cents], OuterInheritanceOp.field_configurations.keys.sort)
|
144
201
|
end
|
145
202
|
|
203
|
+
def test_field_definitions_are_not_mutated_by_subclasses
|
204
|
+
assert_equal(%i[bar foo], MutationBase.field_configurations.keys.sort)
|
205
|
+
assert_equal(%i[bar foo food qux], MutationChild.field_configurations.keys.sort)
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_group_fields_are_not_mutated_by_subclasses
|
209
|
+
assert_equal(%i[three_letter], MutationBase.fields_by_group.keys.sort)
|
210
|
+
assert_equal(%i[four_letter three_letter], MutationChild.fields_by_group.keys.sort)
|
211
|
+
|
212
|
+
assert_equal(%i[bar foo], MutationBase.fields_by_group[:three_letter].sort)
|
213
|
+
assert_equal(%i[bar foo qux], MutationChild.fields_by_group[:three_letter].sort)
|
214
|
+
assert_equal(%i[food], MutationChild.fields_by_group[:four_letter].sort)
|
215
|
+
end
|
216
|
+
|
146
217
|
end
|
147
218
|
end
|
@@ -175,6 +175,34 @@ module Subroutine
|
|
175
175
|
|
176
176
|
op.object_input = { 'foo' => { 'bar' => :baz } }
|
177
177
|
assert_equal({ 'foo' => { 'bar' => :baz } }, op.object_input)
|
178
|
+
|
179
|
+
op.object_input = { foo: "bar" }
|
180
|
+
assert op.object_input.is_a?(ActiveSupport::HashWithIndifferentAccess)
|
181
|
+
end
|
182
|
+
|
183
|
+
def test_hash_inputs_can_opt_out_of_indifferent_access_behavior
|
184
|
+
op.no_indifferent_object_input = nil
|
185
|
+
assert_nil op.no_indifferent_object_input
|
186
|
+
|
187
|
+
op.no_indifferent_object_input = ''
|
188
|
+
assert_equal({}, op.no_indifferent_object_input)
|
189
|
+
|
190
|
+
op.no_indifferent_object_input = [[:a, :b]]
|
191
|
+
assert_equal({ a: :b }, op.no_indifferent_object_input)
|
192
|
+
|
193
|
+
op.no_indifferent_object_input = { foo: "bar" }
|
194
|
+
assert_equal({ foo: "bar" }, op.no_indifferent_object_input)
|
195
|
+
assert op.no_indifferent_object_input.key?(:foo)
|
196
|
+
refute op.no_indifferent_object_input.key?("foo")
|
197
|
+
refute op.no_indifferent_object_input.is_a?(ActiveSupport::HashWithIndifferentAccess)
|
198
|
+
assert op.no_indifferent_object_input.is_a?(Hash)
|
199
|
+
|
200
|
+
op.no_indifferent_object_input = { "foo" => "bar" }
|
201
|
+
assert_equal({ "foo" => "bar" }, op.no_indifferent_object_input)
|
202
|
+
assert op.no_indifferent_object_input.key?("foo")
|
203
|
+
refute op.no_indifferent_object_input.key?(:foo)
|
204
|
+
refute op.no_indifferent_object_input.is_a?(ActiveSupport::HashWithIndifferentAccess)
|
205
|
+
assert op.no_indifferent_object_input.is_a?(Hash)
|
178
206
|
end
|
179
207
|
|
180
208
|
def test_array_inputs
|
data/test/support/ops.rb
CHANGED
@@ -27,10 +27,6 @@ class User
|
|
27
27
|
new(params)
|
28
28
|
end
|
29
29
|
|
30
|
-
def self.find_by!(params)
|
31
|
-
find_by(params) || raise
|
32
|
-
end
|
33
|
-
|
34
30
|
def self.type_for_attribute(attribute)
|
35
31
|
case attribute
|
36
32
|
when :id
|
@@ -180,6 +176,7 @@ class TypeCastOp < ::Subroutine::Op
|
|
180
176
|
iso_date :iso_date_input
|
181
177
|
iso_time :iso_time_input
|
182
178
|
object :object_input
|
179
|
+
object :no_indifferent_object_input, bypass_indifferent_assignment: true
|
183
180
|
array :array_input, default: "foo"
|
184
181
|
array :type_array_input, of: :integer
|
185
182
|
file :file_input
|
@@ -342,6 +339,12 @@ class SimpleAssociationOp < ::OpWithAssociation
|
|
342
339
|
|
343
340
|
end
|
344
341
|
|
342
|
+
class SafeAssociationOp < ::OpWithAssociation
|
343
|
+
|
344
|
+
association :user, raise_on_miss: false
|
345
|
+
|
346
|
+
end
|
347
|
+
|
345
348
|
class SimpleAssociationWithStringIdOp < ::OpWithAssociation
|
346
349
|
|
347
350
|
association :string_id_user
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: subroutine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Nelson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-05-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|