smart_params 2.3.1 → 2.4.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/README.md +4 -3
- data/lib/smart_params.rb +2 -6
- data/lib/smart_params/field.rb +81 -8
- data/lib/smart_params/version.rb +1 -1
- data/lib/smart_params_spec.rb +164 -4
- metadata +3 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dee376c52b0335bfc7ae84b37b2fc1e10575b216ad11e131fbf858621fa7431f
|
4
|
+
data.tar.gz: cfa443e6596f128bc313a83d8e744ca586c52891eecab9011edb330cbf365d40
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2ab41e38e0ad3411de6703a3f37e3c1f1fa3cdef75de1404729d329727883c269da29119667bcdf5ceb9f7528e60802b708567e1f2dacc72eed8dd99824f7b7
|
7
|
+
data.tar.gz: 0213e4c1bb2854ba43fd6ac7c8300726e882e486d94c9032f7964929239854fb9780f84dc81c7f45200ebc2e0113afb64a856cd6711aee9544f6d275d71603c0
|
data/README.md
CHANGED
@@ -148,6 +148,7 @@ Or install it yourself with:
|
|
148
148
|
1. Read the [Code of Conduct](/CONDUCT.md)
|
149
149
|
2. Fork it
|
150
150
|
3. Create your feature branch (`git checkout -b my-new-feature`)
|
151
|
-
4.
|
152
|
-
5.
|
153
|
-
6.
|
151
|
+
4. Test your code: `rake spec`
|
152
|
+
5. Commit your changes (`git commit -am 'Add some feature'`)
|
153
|
+
6. Push to the branch (`git push origin my-new-feature`)
|
154
|
+
7. Create new Pull Request
|
data/lib/smart_params.rb
CHANGED
@@ -6,7 +6,7 @@ require "active_support/core_ext/module/delegation"
|
|
6
6
|
|
7
7
|
module SmartParams
|
8
8
|
extend ActiveSupport::Concern
|
9
|
-
include Dry
|
9
|
+
include Dry.Types()
|
10
10
|
|
11
11
|
require_relative "smart_params/field"
|
12
12
|
require_relative "smart_params/error"
|
@@ -68,12 +68,8 @@ module SmartParams
|
|
68
68
|
# This function basically takes a list of fields and reduces them into a tree of values
|
69
69
|
private def structure
|
70
70
|
fields
|
71
|
-
.reject(&:
|
71
|
+
.reject(&:removable?)
|
72
72
|
.map(&:to_hash)
|
73
|
-
.map do |hash|
|
74
|
-
# NOTE: okay, so this looks weird, but it's because the root type has no key
|
75
|
-
if hash.key?(nil) then hash.fetch(nil) else hash end
|
76
|
-
end
|
77
73
|
.reduce(&:deep_merge)
|
78
74
|
end
|
79
75
|
|
data/lib/smart_params/field.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
module SmartParams
|
2
2
|
class Field
|
3
3
|
attr_reader :keychain
|
4
|
-
attr_reader :value
|
5
4
|
attr_reader :subfields
|
6
5
|
attr_reader :type
|
7
6
|
|
8
|
-
def initialize(keychain:, type:, &nesting)
|
7
|
+
def initialize(keychain:, type:, nullable: false, &nesting)
|
9
8
|
@keychain = Array(keychain)
|
10
9
|
@subfields = Set.new
|
11
10
|
@type = type
|
11
|
+
@nullable = nullable
|
12
|
+
@specified = false
|
13
|
+
@dirty = false
|
12
14
|
|
13
15
|
if block_given?
|
14
16
|
instance_eval(&nesting)
|
@@ -16,9 +18,52 @@ module SmartParams
|
|
16
18
|
end
|
17
19
|
|
18
20
|
def deep?
|
21
|
+
# We check @specified directly because we want to know if ANY
|
22
|
+
# subfields have been passed, not just ones that match the schema.
|
23
|
+
return false if nullable? && !!@specified
|
19
24
|
subfields.present?
|
20
25
|
end
|
21
26
|
|
27
|
+
def root?
|
28
|
+
keychain.size == 0
|
29
|
+
end
|
30
|
+
|
31
|
+
def value
|
32
|
+
@value || ({} if root?)
|
33
|
+
end
|
34
|
+
|
35
|
+
def nullable?
|
36
|
+
!!@nullable
|
37
|
+
end
|
38
|
+
|
39
|
+
def specified?
|
40
|
+
if nullable?
|
41
|
+
!!@specified && clean?
|
42
|
+
else
|
43
|
+
!!@specified
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# For nullable hashes: Any keys not in the schema make the hash dirty.
|
48
|
+
# If a key is found that matches the schema, we can consider the hash
|
49
|
+
# clean.
|
50
|
+
def dirty?
|
51
|
+
!!@dirty
|
52
|
+
end
|
53
|
+
|
54
|
+
def clean?
|
55
|
+
return false if dirty?
|
56
|
+
return true if empty? || subfields.select { |sub| !sub.empty? }.any?
|
57
|
+
false
|
58
|
+
end
|
59
|
+
|
60
|
+
# Check if we should consider this value even when empty.
|
61
|
+
def allow_empty?
|
62
|
+
return true if specified? && nullable?
|
63
|
+
return subfields.select(&:allow_empty?).any?
|
64
|
+
false
|
65
|
+
end
|
66
|
+
|
22
67
|
def claim(raw)
|
23
68
|
return type[dug(raw)] if deep?
|
24
69
|
|
@@ -37,20 +82,48 @@ module SmartParams
|
|
37
82
|
value.nil?
|
38
83
|
end
|
39
84
|
|
85
|
+
# Should this field be removed from resulting hash?
|
86
|
+
def removable?
|
87
|
+
empty? && !allow_empty?
|
88
|
+
end
|
89
|
+
|
40
90
|
def weight
|
41
91
|
keychain.map(&:to_s)
|
42
92
|
end
|
43
93
|
|
44
|
-
private def field(key, type:, &subfield)
|
45
|
-
@subfields << self.class.new(keychain: [*keychain, key], type: type, &subfield)
|
94
|
+
private def field(key, type:, nullable: false, &subfield)
|
95
|
+
@subfields << self.class.new(keychain: [*keychain, key], type: type, nullable: nullable, &subfield)
|
46
96
|
end
|
47
97
|
|
98
|
+
# Very busy method with recent changes. TODO: clean-up
|
48
99
|
private def dug(raw)
|
49
|
-
if keychain.empty?
|
50
|
-
|
51
|
-
|
52
|
-
|
100
|
+
return raw if keychain.empty?
|
101
|
+
|
102
|
+
# If value provided is a hash, check if it's dirty. See #dirty? for
|
103
|
+
# more info.
|
104
|
+
if nullable?
|
105
|
+
hash = raw.dig(*keychain)
|
106
|
+
if hash.respond_to?(:keys)
|
107
|
+
others = hash.keys - [keychain.last]
|
108
|
+
@dirty = others.any?
|
109
|
+
end
|
53
110
|
end
|
111
|
+
|
112
|
+
# Trace the keychain to find out if the field is explicitly set in the
|
113
|
+
# input hash.
|
114
|
+
at = raw
|
115
|
+
exact = true
|
116
|
+
keychain.each { |key|
|
117
|
+
if at.respond_to?(:key?) && at.key?(key)
|
118
|
+
at = at[key]
|
119
|
+
else
|
120
|
+
exact = false
|
121
|
+
break
|
122
|
+
end
|
123
|
+
}
|
124
|
+
@specified = exact
|
125
|
+
|
126
|
+
raw.dig(*keychain)
|
54
127
|
end
|
55
128
|
end
|
56
129
|
end
|
data/lib/smart_params/version.rb
CHANGED
data/lib/smart_params_spec.rb
CHANGED
@@ -2,6 +2,8 @@ require "spec_helper"
|
|
2
2
|
|
3
3
|
RSpec.describe SmartParams do
|
4
4
|
let(:schema) { CreateAccountSchema.new(params) }
|
5
|
+
let(:nullable_schema) { NullableSchema.new(params) }
|
6
|
+
let(:nullable_required_subfield_schema) { NullableRequiredSubfieldSchema.new(params) }
|
5
7
|
|
6
8
|
describe ".new" do
|
7
9
|
context "with an empty params" do
|
@@ -42,7 +44,8 @@ RSpec.describe SmartParams do
|
|
42
44
|
data: {
|
43
45
|
type: "accounts",
|
44
46
|
attributes: {
|
45
|
-
email: "kurtis@example.com"
|
47
|
+
email: "kurtis@example.com",
|
48
|
+
password: "secret"
|
46
49
|
}
|
47
50
|
},
|
48
51
|
meta: {
|
@@ -155,7 +158,8 @@ RSpec.describe SmartParams do
|
|
155
158
|
data: {
|
156
159
|
type: "accounts",
|
157
160
|
attributes: {
|
158
|
-
email: "kurtis@example.com"
|
161
|
+
email: "kurtis@example.com",
|
162
|
+
password: "secret"
|
159
163
|
}
|
160
164
|
},
|
161
165
|
meta: {
|
@@ -234,7 +238,8 @@ RSpec.describe SmartParams do
|
|
234
238
|
data: {
|
235
239
|
type: "accounts",
|
236
240
|
attributes: {
|
237
|
-
email: "kurtis@example.com"
|
241
|
+
email: "kurtis@example.com",
|
242
|
+
password: "secret"
|
238
243
|
}
|
239
244
|
},
|
240
245
|
meta: {
|
@@ -322,7 +327,8 @@ RSpec.describe SmartParams do
|
|
322
327
|
data: {
|
323
328
|
type: "accounts",
|
324
329
|
attributes: {
|
325
|
-
email: "kurtis@example.com"
|
330
|
+
email: "kurtis@example.com",
|
331
|
+
password: "secret"
|
326
332
|
}
|
327
333
|
},
|
328
334
|
meta: {
|
@@ -368,4 +374,158 @@ RSpec.describe SmartParams do
|
|
368
374
|
end
|
369
375
|
end
|
370
376
|
end
|
377
|
+
|
378
|
+
describe "nullable values" do
|
379
|
+
context "set to nil" do
|
380
|
+
subject {nullable_schema.to_hash}
|
381
|
+
|
382
|
+
let(:params) do
|
383
|
+
{
|
384
|
+
data: nil
|
385
|
+
}
|
386
|
+
end
|
387
|
+
|
388
|
+
it "returns explicit nil" do
|
389
|
+
expect(
|
390
|
+
subject
|
391
|
+
).to match(
|
392
|
+
hash_including(
|
393
|
+
{
|
394
|
+
"data" => nil
|
395
|
+
}
|
396
|
+
)
|
397
|
+
)
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
context "provided matching data" do
|
402
|
+
subject {nullable_schema.to_hash}
|
403
|
+
|
404
|
+
let(:params) do
|
405
|
+
{
|
406
|
+
data: {
|
407
|
+
id: "1",
|
408
|
+
type: "people"
|
409
|
+
}
|
410
|
+
}
|
411
|
+
end
|
412
|
+
|
413
|
+
it "returns matching data" do
|
414
|
+
expect(
|
415
|
+
subject
|
416
|
+
).to match(
|
417
|
+
hash_including(
|
418
|
+
{
|
419
|
+
"data" => hash_including(
|
420
|
+
{
|
421
|
+
"id" => "1",
|
422
|
+
"type" => "people"
|
423
|
+
}
|
424
|
+
)
|
425
|
+
}
|
426
|
+
)
|
427
|
+
)
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
context "not provided" do
|
432
|
+
subject {nullable_schema.to_hash}
|
433
|
+
|
434
|
+
let(:params) do
|
435
|
+
{
|
436
|
+
}
|
437
|
+
end
|
438
|
+
|
439
|
+
it "does not set nil relationship" do
|
440
|
+
expect(
|
441
|
+
subject
|
442
|
+
).to match(
|
443
|
+
hash_excluding(
|
444
|
+
{
|
445
|
+
"data" => nil
|
446
|
+
}
|
447
|
+
)
|
448
|
+
)
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
context "with non matching subfield data" do
|
453
|
+
subject {nullable_schema.to_hash}
|
454
|
+
|
455
|
+
let(:params) do
|
456
|
+
{
|
457
|
+
data: {
|
458
|
+
is: "garbage"
|
459
|
+
}
|
460
|
+
}
|
461
|
+
end
|
462
|
+
|
463
|
+
it "does not provide data" do
|
464
|
+
expect(
|
465
|
+
subject
|
466
|
+
).to match(
|
467
|
+
hash_excluding(
|
468
|
+
{
|
469
|
+
"data" => nil
|
470
|
+
}
|
471
|
+
)
|
472
|
+
)
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
context "specified with unclean data" do
|
477
|
+
subject {nullable_required_subfield_schema.to_hash}
|
478
|
+
|
479
|
+
let(:params) do
|
480
|
+
{
|
481
|
+
# This will raise an exception becase the data hash is specified
|
482
|
+
# but its required subfields are not.
|
483
|
+
data: {
|
484
|
+
is: 'garbage'
|
485
|
+
}
|
486
|
+
}
|
487
|
+
end
|
488
|
+
|
489
|
+
it "checks subfields" do
|
490
|
+
expect {
|
491
|
+
subject
|
492
|
+
}.to raise_exception(SmartParams::Error::InvalidPropertyType)
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
context "specified as null" do
|
497
|
+
subject {nullable_required_subfield_schema.to_hash}
|
498
|
+
|
499
|
+
let(:params) do
|
500
|
+
{
|
501
|
+
# This will not raise an error, since data is allowed to be null.
|
502
|
+
# Subfields will not be checked.
|
503
|
+
data: nil
|
504
|
+
}
|
505
|
+
end
|
506
|
+
|
507
|
+
it "checks subfields" do
|
508
|
+
expect {
|
509
|
+
subject
|
510
|
+
}.not_to raise_exception
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
context "unspecified with required subfield" do
|
515
|
+
subject {nullable_required_subfield_schema.to_hash}
|
516
|
+
|
517
|
+
let(:params) do
|
518
|
+
{
|
519
|
+
# In this case, the nullable data hash is not specified so we
|
520
|
+
# don't need to enforce constraints on subfields.
|
521
|
+
}
|
522
|
+
end
|
523
|
+
|
524
|
+
it "allows null value" do
|
525
|
+
expect {
|
526
|
+
subject
|
527
|
+
}.not_to raise_exception
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
371
531
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smart_params
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kurtis Rainbolt-Greene
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-06-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -176,8 +176,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
176
176
|
- !ruby/object:Gem::Version
|
177
177
|
version: '0'
|
178
178
|
requirements: []
|
179
|
-
|
180
|
-
rubygems_version: 2.7.3
|
179
|
+
rubygems_version: 3.0.3
|
181
180
|
signing_key:
|
182
181
|
specification_version: 4
|
183
182
|
summary: Apply an organized and easy to maintain schema to request params
|