subroutine 3.0.5 → 4.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.MD +14 -0
- data/lib/subroutine/association_fields/configuration.rb +4 -0
- data/lib/subroutine/association_fields.rb +11 -10
- data/lib/subroutine/fields/configuration.rb +0 -2
- data/lib/subroutine/fields.rb +89 -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/type_caster.rb +3 -19
- data/lib/subroutine/version.rb +2 -2
- data/lib/subroutine.rb +13 -4
- 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 +5 -142
- data/test/support/ops.rb +6 -5
- 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: c838c235e50f88722d2f39f19560fa5208a1e18f7cd0ee8edeeeea568e47166c
|
4
|
+
data.tar.gz: 4bb808af75863ee0785ef8989b6ffc3423838dae84bb7a481d18aa759325c5be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a008be5744be667696675df24ba4edf19a39716f27074ec192500ed30ba750d11974f1c3892144b13ff693631899cd0aa0931c5995b823ebe112073646fb5363
|
7
|
+
data.tar.gz: f45212abc2f417d85c73db00ee83b9359ddc80d46fd77f78a45c802efe51a7374dcc978c0d7ded73ddd67c6637f252516c86abdf0f6f0ab309ef763476c17113
|
data/CHANGELOG.MD
CHANGED
@@ -1,7 +1,21 @@
|
|
1
|
+
## Subroutine 4.0.1
|
2
|
+
|
3
|
+
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.
|
4
|
+
|
5
|
+
## Subroutine 4.0
|
6
|
+
|
7
|
+
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.
|
8
|
+
|
9
|
+
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`.
|
10
|
+
|
1
11
|
## Subroutine 3.0
|
2
12
|
|
3
13
|
Add support for Rails 6.1. Drop support for Rails 6.0 and lower.
|
4
14
|
|
15
|
+
## Subroutine 2.3
|
16
|
+
|
17
|
+
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.
|
18
|
+
|
5
19
|
## Subroutine 2.2
|
6
20
|
|
7
21
|
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)
|
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,101 @@ 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
|
-
get_param_group(
|
188
|
+
def set_param_group_value(name, key, value)
|
189
|
+
get_param_group(name)[key.to_sym] = value
|
177
190
|
end
|
178
191
|
|
179
|
-
def
|
180
|
-
get_param_group(:
|
192
|
+
def original_params
|
193
|
+
get_param_group(:original)
|
181
194
|
end
|
182
195
|
|
183
|
-
def
|
184
|
-
get_param_group(:
|
196
|
+
def all_provided_params
|
197
|
+
get_param_group(:provided)
|
185
198
|
end
|
186
|
-
alias
|
199
|
+
alias provided_params all_provided_params
|
187
200
|
|
188
201
|
def all_default_params
|
189
202
|
get_param_group(:default)
|
190
203
|
end
|
191
204
|
alias defaults all_default_params
|
205
|
+
alias default_params all_default_params
|
192
206
|
|
193
|
-
def
|
194
|
-
|
195
|
-
end
|
196
|
-
alias params_with_defaults all_params_with_defaults
|
207
|
+
def all_params
|
208
|
+
return all_params_with_default_params if include_defaults_in_params?
|
197
209
|
|
198
|
-
|
199
|
-
default_params.slice(*ungrouped_fields.keys)
|
210
|
+
all_provided_params
|
200
211
|
end
|
212
|
+
alias params all_params
|
201
213
|
|
202
|
-
def
|
203
|
-
|
214
|
+
def all_params_with_default_params
|
215
|
+
param_cache[:provided_and_default] ||= all_default_params.merge(all_provided_params)
|
204
216
|
end
|
217
|
+
alias all_params_with_defaults all_params_with_default_params
|
218
|
+
alias params_with_defaults all_params_with_defaults
|
219
|
+
|
205
220
|
|
206
221
|
def get_field_config(field_name)
|
207
222
|
self.class.get_field_config(field_name)
|
208
223
|
end
|
209
224
|
|
210
|
-
# check if a specific field was provided
|
211
225
|
def field_provided?(key)
|
212
|
-
|
226
|
+
all_provided_params.key?(key)
|
213
227
|
end
|
214
228
|
|
215
229
|
def get_field(name)
|
216
|
-
|
230
|
+
all_params_with_default_params[name]
|
217
231
|
end
|
218
232
|
|
219
|
-
def set_field(name, value,
|
233
|
+
def set_field(name, value, group_type: :provided)
|
220
234
|
config = get_field_config(name)
|
221
|
-
@provided_fields[name] = true unless opts[:track_provided] == false
|
222
235
|
value = attempt_cast(value, config) do |e|
|
223
236
|
"Error during assignment of field `#{name}`: #{e}"
|
224
237
|
end
|
225
|
-
|
226
|
-
|
238
|
+
|
239
|
+
set_param_group_value(group_type, config.field_name, value)
|
240
|
+
|
241
|
+
config.groups.each do |group_name|
|
242
|
+
set_param_group_value(:"#{group_name}_#{group_type}", config.field_name, value)
|
227
243
|
end
|
244
|
+
|
245
|
+
param_cache.clear
|
246
|
+
|
228
247
|
value
|
229
248
|
end
|
230
249
|
|
231
250
|
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
|
251
|
+
param_cache.clear
|
252
|
+
param_groups.each_pair do |key, group|
|
253
|
+
next if key == :original
|
254
|
+
next if key == :default
|
240
255
|
|
241
|
-
|
242
|
-
fields.select { |f| f.groups.empty? }.each_with_object({}) do |f, h|
|
243
|
-
h[f.name] = f
|
256
|
+
group.delete(name)
|
244
257
|
end
|
245
258
|
end
|
246
259
|
|
@@ -253,16 +266,12 @@ module Subroutine
|
|
253
266
|
end
|
254
267
|
|
255
268
|
if original_params.key?(field_name)
|
256
|
-
set_field(field_name, original_params[field_name])
|
269
|
+
set_field(field_name, original_params[field_name], group_type: :provided)
|
257
270
|
end
|
258
271
|
|
259
272
|
next unless config.has_default?
|
260
273
|
|
261
|
-
|
262
|
-
"Error for default `#{field}`: #{e}"
|
263
|
-
end
|
264
|
-
|
265
|
-
param_groups[:default][field_name] = value
|
274
|
+
set_field(field_name, config.get_default, group_type: :default)
|
266
275
|
end
|
267
276
|
end
|
268
277
|
|
@@ -273,18 +282,5 @@ module Subroutine
|
|
273
282
|
raise ::Subroutine::TypeCaster::TypeCastError, message, e.backtrace
|
274
283
|
end
|
275
284
|
|
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
285
|
end
|
290
286
|
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
@@ -4,16 +4,9 @@ require 'date'
|
|
4
4
|
require 'time'
|
5
5
|
require 'bigdecimal'
|
6
6
|
require 'securerandom'
|
7
|
-
require 'active_support'
|
8
|
-
require 'active_support/json'
|
9
|
-
require 'active_support/core_ext/date_time/acts_like'
|
10
|
-
require 'active_support/core_ext/date_time/calculations'
|
11
|
-
require 'active_support/core_ext/object/acts_like'
|
12
7
|
require 'active_support/core_ext/object/blank'
|
13
8
|
require 'active_support/core_ext/object/try'
|
14
9
|
require 'active_support/core_ext/array/wrap'
|
15
|
-
require 'active_support/core_ext/time/acts_like'
|
16
|
-
require 'active_support/core_ext/time/calculations'
|
17
10
|
|
18
11
|
module Subroutine
|
19
12
|
module TypeCaster
|
@@ -113,7 +106,7 @@ end
|
|
113
106
|
t ||= value if value.is_a?(::Time)
|
114
107
|
t ||= value if value.try(:acts_like?, :time)
|
115
108
|
t ||= ::Time.parse(String(value))
|
116
|
-
t.utc.iso8601
|
109
|
+
t.utc.iso8601
|
117
110
|
end
|
118
111
|
|
119
112
|
::Subroutine::TypeCaster.register :date do |value, _options = {}|
|
@@ -122,19 +115,10 @@ end
|
|
122
115
|
::Date.parse(String(value))
|
123
116
|
end
|
124
117
|
|
125
|
-
::Subroutine::TypeCaster.register :time, :timestamp, :datetime do |value,
|
118
|
+
::Subroutine::TypeCaster.register :time, :timestamp, :datetime do |value, _options = {}|
|
126
119
|
next nil unless value.present?
|
127
120
|
|
128
|
-
|
129
|
-
value.to_time
|
130
|
-
else
|
131
|
-
::Time.parse(String(value))
|
132
|
-
end
|
133
|
-
|
134
|
-
# High precision must be opted into. The original implementation is to set usec:0
|
135
|
-
next value if options[:precision] == :high || ::Subroutine.preserve_time_precision?
|
136
|
-
|
137
|
-
value.change(usec: 0)
|
121
|
+
::Time.parse(String(value))
|
138
122
|
end
|
139
123
|
|
140
124
|
::Subroutine::TypeCaster.register :hash, :object, :hashmap, :dict do |value, _options = {}|
|
data/lib/subroutine/version.rb
CHANGED
data/lib/subroutine.rb
CHANGED
@@ -1,17 +1,26 @@
|
|
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"
|
6
15
|
|
7
16
|
module Subroutine
|
8
17
|
|
9
|
-
def self.
|
10
|
-
@
|
18
|
+
def self.include_defaults_in_params=(bool)
|
19
|
+
@include_defaults_in_params = !!bool
|
11
20
|
end
|
12
21
|
|
13
|
-
def self.
|
14
|
-
return !!@
|
22
|
+
def self.include_defaults_in_params?
|
23
|
+
return !!@include_defaults_in_params if defined?(@instance_defaults_in_params)
|
15
24
|
|
16
25
|
false
|
17
26
|
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
|
@@ -241,7 +241,7 @@ module Subroutine
|
|
241
241
|
assert_nil op.date_input
|
242
242
|
end
|
243
243
|
|
244
|
-
def
|
244
|
+
def test_time_inputs
|
245
245
|
op.time_input = nil
|
246
246
|
assert_nil op.time_input
|
247
247
|
|
@@ -256,153 +256,16 @@ module Subroutine
|
|
256
256
|
assert_equal 0, op.time_input.min
|
257
257
|
assert_equal 0, op.time_input.sec
|
258
258
|
|
259
|
-
op.time_input =
|
260
|
-
assert_equal ::Time, op.time_input.class
|
261
|
-
refute_equal ::DateTime, op.time_input.class
|
262
|
-
|
263
|
-
assert_equal 0, op.time_input.utc_offset
|
264
|
-
assert_equal 2022, op.time_input.year
|
265
|
-
assert_equal 12, op.time_input.month
|
266
|
-
assert_equal 22, op.time_input.day
|
267
|
-
assert_equal 0, op.time_input.hour
|
268
|
-
assert_equal 0, op.time_input.min
|
269
|
-
assert_equal 0, op.time_input.sec
|
270
|
-
|
271
|
-
op.time_input = '2023-05-05T10:00:30.123456Z'
|
259
|
+
op.time_input = '2023-05-05T10:00:30Z'
|
272
260
|
assert_equal ::Time, op.time_input.class
|
273
261
|
refute_equal ::DateTime, op.time_input.class
|
274
262
|
|
275
|
-
assert_equal 0, op.time_input.utc_offset
|
276
263
|
assert_equal 2023, op.time_input.year
|
277
264
|
assert_equal 5, op.time_input.month
|
278
265
|
assert_equal 5, op.time_input.day
|
279
266
|
assert_equal 10, op.time_input.hour
|
280
267
|
assert_equal 0, op.time_input.min
|
281
268
|
assert_equal 30, op.time_input.sec
|
282
|
-
assert_equal 0, op.time_input.usec
|
283
|
-
|
284
|
-
op.time_input = '2023-05-05T10:00:30Z'
|
285
|
-
assert_equal ::Time, op.time_input.class
|
286
|
-
assert_equal 0, op.time_input.utc_offset
|
287
|
-
assert_equal 2023, op.time_input.year
|
288
|
-
assert_equal 5, op.time_input.month
|
289
|
-
assert_equal 5, op.time_input.day
|
290
|
-
assert_equal 10, op.time_input.hour
|
291
|
-
assert_equal 0, op.time_input.min
|
292
|
-
assert_equal 30, op.time_input.sec
|
293
|
-
assert_equal 0, op.time_input.usec
|
294
|
-
|
295
|
-
op.time_input = '2024-11-11T16:42:23.246+0100'
|
296
|
-
assert_equal ::Time, op.time_input.class
|
297
|
-
assert_equal 3600, op.time_input.utc_offset
|
298
|
-
assert_equal 2024, op.time_input.year
|
299
|
-
assert_equal 11, op.time_input.month
|
300
|
-
assert_equal 11, op.time_input.day
|
301
|
-
assert_equal 16, op.time_input.hour
|
302
|
-
assert_equal 42, op.time_input.min
|
303
|
-
assert_equal 23, op.time_input.sec
|
304
|
-
assert_equal 0, op.time_input.usec
|
305
|
-
|
306
|
-
time = Time.at(1678741605.123456).utc
|
307
|
-
op.time_input = time
|
308
|
-
refute_equal time, op.time_input
|
309
|
-
refute_equal time.object_id, op.time_input.object_id
|
310
|
-
assert_equal 2023, op.time_input.year
|
311
|
-
assert_equal 3, op.time_input.month
|
312
|
-
assert_equal 13, op.time_input.day
|
313
|
-
assert_equal 21, op.time_input.hour
|
314
|
-
assert_equal 6, op.time_input.min
|
315
|
-
assert_equal 45, op.time_input.sec
|
316
|
-
assert_equal 0, op.time_input.usec
|
317
|
-
end
|
318
|
-
|
319
|
-
def test_time_inputs__with_preserve_time_precision
|
320
|
-
Subroutine.stubs(:preserve_time_precision?).returns(true)
|
321
|
-
|
322
|
-
time = Time.at(1678741605.123456).utc
|
323
|
-
op.time_input = time
|
324
|
-
assert_equal 2023, op.time_input.year
|
325
|
-
assert_equal 3, op.time_input.month
|
326
|
-
assert_equal 13, op.time_input.day
|
327
|
-
assert_equal 21, op.time_input.hour
|
328
|
-
assert_equal 6, op.time_input.min
|
329
|
-
assert_equal 45, op.time_input.sec
|
330
|
-
assert_equal 123456, op.time_input.usec
|
331
|
-
end
|
332
|
-
|
333
|
-
def test_time_inputs__with_high_precision
|
334
|
-
op.precise_time_input = nil
|
335
|
-
assert_nil op.precise_time_input
|
336
|
-
|
337
|
-
op.precise_time_input = '2022-12-22'
|
338
|
-
assert_equal ::Time, op.precise_time_input.class
|
339
|
-
refute_equal ::DateTime, op.precise_time_input.class
|
340
|
-
|
341
|
-
assert_equal 2022, op.precise_time_input.year
|
342
|
-
assert_equal 12, op.precise_time_input.month
|
343
|
-
assert_equal 22, op.precise_time_input.day
|
344
|
-
assert_equal 0, op.precise_time_input.hour
|
345
|
-
assert_equal 0, op.precise_time_input.min
|
346
|
-
assert_equal 0, op.precise_time_input.sec
|
347
|
-
|
348
|
-
op.precise_time_input = ::DateTime.new(2022, 12, 22)
|
349
|
-
assert_equal ::Time, op.precise_time_input.class
|
350
|
-
refute_equal ::DateTime, op.precise_time_input.class
|
351
|
-
|
352
|
-
assert_equal 0, op.precise_time_input.utc_offset
|
353
|
-
assert_equal 2022, op.precise_time_input.year
|
354
|
-
assert_equal 12, op.precise_time_input.month
|
355
|
-
assert_equal 22, op.precise_time_input.day
|
356
|
-
assert_equal 0, op.precise_time_input.hour
|
357
|
-
assert_equal 0, op.precise_time_input.min
|
358
|
-
assert_equal 0, op.precise_time_input.sec
|
359
|
-
|
360
|
-
op.precise_time_input = '2023-05-05T10:00:30.123456Z'
|
361
|
-
assert_equal ::Time, op.precise_time_input.class
|
362
|
-
refute_equal ::DateTime, op.precise_time_input.class
|
363
|
-
|
364
|
-
assert_equal 0, op.precise_time_input.utc_offset
|
365
|
-
assert_equal 2023, op.precise_time_input.year
|
366
|
-
assert_equal 5, op.precise_time_input.month
|
367
|
-
assert_equal 5, op.precise_time_input.day
|
368
|
-
assert_equal 10, op.precise_time_input.hour
|
369
|
-
assert_equal 0, op.precise_time_input.min
|
370
|
-
assert_equal 30, op.precise_time_input.sec
|
371
|
-
assert_equal 123456, op.precise_time_input.usec
|
372
|
-
|
373
|
-
op.precise_time_input = '2023-05-05T10:00:30Z'
|
374
|
-
assert_equal ::Time, op.precise_time_input.class
|
375
|
-
assert_equal 0, op.precise_time_input.utc_offset
|
376
|
-
assert_equal 2023, op.precise_time_input.year
|
377
|
-
assert_equal 5, op.precise_time_input.month
|
378
|
-
assert_equal 5, op.precise_time_input.day
|
379
|
-
assert_equal 10, op.precise_time_input.hour
|
380
|
-
assert_equal 0, op.precise_time_input.min
|
381
|
-
assert_equal 30, op.precise_time_input.sec
|
382
|
-
assert_equal 0, op.precise_time_input.usec
|
383
|
-
|
384
|
-
op.precise_time_input = '2024-11-11T16:42:23.246+0100'
|
385
|
-
assert_equal ::Time, op.precise_time_input.class
|
386
|
-
assert_equal 3600, op.precise_time_input.utc_offset
|
387
|
-
assert_equal 2024, op.precise_time_input.year
|
388
|
-
assert_equal 11, op.precise_time_input.month
|
389
|
-
assert_equal 11, op.precise_time_input.day
|
390
|
-
assert_equal 16, op.precise_time_input.hour
|
391
|
-
assert_equal 42, op.precise_time_input.min
|
392
|
-
assert_equal 23, op.precise_time_input.sec
|
393
|
-
assert_equal 246000, op.precise_time_input.usec
|
394
|
-
|
395
|
-
time = Time.at(1678741605.123456).utc
|
396
|
-
op.precise_time_input = time
|
397
|
-
assert_equal time, op.precise_time_input
|
398
|
-
assert_equal time.object_id, op.precise_time_input.object_id
|
399
|
-
assert_equal 2023, op.precise_time_input.year
|
400
|
-
assert_equal 3, op.precise_time_input.month
|
401
|
-
assert_equal 13, op.precise_time_input.day
|
402
|
-
assert_equal 21, op.precise_time_input.hour
|
403
|
-
assert_equal 6, op.precise_time_input.min
|
404
|
-
assert_equal 45, op.precise_time_input.sec
|
405
|
-
assert_equal 123456, op.precise_time_input.usec
|
406
269
|
end
|
407
270
|
|
408
271
|
def test_iso_date_inputs
|
@@ -424,11 +287,11 @@ module Subroutine
|
|
424
287
|
|
425
288
|
op.iso_time_input = '2022-12-22T10:30:24Z'
|
426
289
|
assert_equal ::String, op.iso_time_input.class
|
427
|
-
assert_equal '2022-12-22T10:30:
|
290
|
+
assert_equal '2022-12-22T10:30:24Z', op.iso_time_input
|
428
291
|
|
429
|
-
op.iso_time_input = Time.parse('2022-12-22T10:30:
|
292
|
+
op.iso_time_input = Time.parse('2022-12-22T10:30:24Z')
|
430
293
|
assert_equal ::String, op.iso_time_input.class
|
431
|
-
assert_equal '2022-12-22T10:30:
|
294
|
+
assert_equal '2022-12-22T10:30:24Z', op.iso_time_input
|
432
295
|
end
|
433
296
|
|
434
297
|
def test_file_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
|
@@ -177,7 +173,6 @@ class TypeCastOp < ::Subroutine::Op
|
|
177
173
|
boolean :boolean_input
|
178
174
|
date :date_input
|
179
175
|
time :time_input, default: -> { Time.now }
|
180
|
-
time :precise_time_input, precision: :high
|
181
176
|
iso_date :iso_date_input
|
182
177
|
iso_time :iso_time_input
|
183
178
|
object :object_input
|
@@ -343,6 +338,12 @@ class SimpleAssociationOp < ::OpWithAssociation
|
|
343
338
|
|
344
339
|
end
|
345
340
|
|
341
|
+
class SafeAssociationOp < ::OpWithAssociation
|
342
|
+
|
343
|
+
association :user, raise_on_miss: false
|
344
|
+
|
345
|
+
end
|
346
|
+
|
346
347
|
class SimpleAssociationWithStringIdOp < ::OpWithAssociation
|
347
348
|
|
348
349
|
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.0.1
|
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-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|