subroutine 3.0.2 → 4.1.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 +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
|