decanter 4.0.4 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +2 -1
- data/.github/workflows/versionci.yml +20 -0
- data/.ruby-version +1 -1
- data/.tool-versions +1 -0
- data/Gemfile.lock +176 -51
- data/README.md +7 -6
- data/decanter.gemspec +10 -11
- data/lib/decanter/core.rb +63 -52
- data/lib/decanter/parser/array_parser.rb +3 -3
- data/lib/decanter/parser/boolean_parser.rb +0 -1
- data/lib/decanter/parser/core.rb +4 -0
- data/lib/decanter/parser/date_parser.rb +0 -1
- data/lib/decanter/parser/datetime_parser.rb +0 -1
- data/lib/decanter/parser/float_parser.rb +0 -1
- data/lib/decanter/parser/integer_parser.rb +0 -1
- data/lib/decanter/parser/pass_parser.rb +1 -1
- data/lib/decanter/parser/phone_parser.rb +0 -1
- data/lib/decanter/parser/string_parser.rb +0 -1
- data/lib/decanter/parser/value_parser.rb +9 -1
- data/lib/decanter/version.rb +1 -1
- data/migration-guides/v5.0.0.md +11 -0
- metadata +28 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 740cf6eead3d1c13d1af276f12bd8f1e54e6052ff8791fffa3a9b133dde61b7c
|
4
|
+
data.tar.gz: 86eb37227ce42cf1dddfc7676b298f4de0681178b4540800fc6a67d682568542
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aec4940ffc2ede503acf8ea757eeaf528bc65e0cc1282cc6fe6fc42c1e1a1f6cc27b9292c62946bca1939b7714cf4cf3e53d1da5093318068c95f349e5e77e88
|
7
|
+
data.tar.gz: 1af26f35262236b5efe518eea6cfc2b9f8716627f136c96f850233d1d34b7c08e7a5c252706e6cf98fe7544a28082f0774b231d7775f8b16a993b243968088b0
|
data/.github/CODEOWNERS
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
name: Version CI
|
2
|
+
on:
|
3
|
+
pull_request:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- main
|
7
|
+
jobs:
|
8
|
+
test:
|
9
|
+
runs-on: ubuntu-latest
|
10
|
+
strategy:
|
11
|
+
fail-fast: false
|
12
|
+
matrix:
|
13
|
+
ruby: ["3.2.0", "3.2.7", "3.3.0", "3.3.7"]
|
14
|
+
steps:
|
15
|
+
- uses: actions/checkout@v3
|
16
|
+
- uses: ruby/setup-ruby@v1
|
17
|
+
with:
|
18
|
+
ruby-version: ${{ matrix.ruby }}
|
19
|
+
bundler-cache: true
|
20
|
+
- run: bundle exec rspec
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
3.3.0
|
data/.tool-versions
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby 3.3.5
|
data/Gemfile.lock
CHANGED
@@ -1,68 +1,184 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
decanter (
|
5
|
-
actionpack (>=
|
4
|
+
decanter (5.0.0)
|
5
|
+
actionpack (>= 7.1.3.2)
|
6
6
|
activesupport
|
7
|
+
rails (>= 7.1.3.2)
|
7
8
|
rails-html-sanitizer (>= 1.0.4)
|
8
9
|
|
9
10
|
GEM
|
10
11
|
remote: https://rubygems.org/
|
11
12
|
specs:
|
12
|
-
|
13
|
-
|
14
|
-
activesupport (=
|
15
|
-
|
13
|
+
actioncable (7.1.3.2)
|
14
|
+
actionpack (= 7.1.3.2)
|
15
|
+
activesupport (= 7.1.3.2)
|
16
|
+
nio4r (~> 2.0)
|
17
|
+
websocket-driver (>= 0.6.1)
|
18
|
+
zeitwerk (~> 2.6)
|
19
|
+
actionmailbox (7.1.3.2)
|
20
|
+
actionpack (= 7.1.3.2)
|
21
|
+
activejob (= 7.1.3.2)
|
22
|
+
activerecord (= 7.1.3.2)
|
23
|
+
activestorage (= 7.1.3.2)
|
24
|
+
activesupport (= 7.1.3.2)
|
25
|
+
mail (>= 2.7.1)
|
26
|
+
net-imap
|
27
|
+
net-pop
|
28
|
+
net-smtp
|
29
|
+
actionmailer (7.1.3.2)
|
30
|
+
actionpack (= 7.1.3.2)
|
31
|
+
actionview (= 7.1.3.2)
|
32
|
+
activejob (= 7.1.3.2)
|
33
|
+
activesupport (= 7.1.3.2)
|
34
|
+
mail (~> 2.5, >= 2.5.4)
|
35
|
+
net-imap
|
36
|
+
net-pop
|
37
|
+
net-smtp
|
38
|
+
rails-dom-testing (~> 2.2)
|
39
|
+
actionpack (7.1.3.2)
|
40
|
+
actionview (= 7.1.3.2)
|
41
|
+
activesupport (= 7.1.3.2)
|
42
|
+
nokogiri (>= 1.8.5)
|
43
|
+
racc
|
44
|
+
rack (>= 2.2.4)
|
45
|
+
rack-session (>= 1.0.1)
|
16
46
|
rack-test (>= 0.6.3)
|
17
|
-
rails-dom-testing (~> 2.
|
18
|
-
rails-html-sanitizer (~> 1.
|
19
|
-
|
20
|
-
|
47
|
+
rails-dom-testing (~> 2.2)
|
48
|
+
rails-html-sanitizer (~> 1.6)
|
49
|
+
actiontext (7.1.3.2)
|
50
|
+
actionpack (= 7.1.3.2)
|
51
|
+
activerecord (= 7.1.3.2)
|
52
|
+
activestorage (= 7.1.3.2)
|
53
|
+
activesupport (= 7.1.3.2)
|
54
|
+
globalid (>= 0.6.0)
|
55
|
+
nokogiri (>= 1.8.5)
|
56
|
+
actionview (7.1.3.2)
|
57
|
+
activesupport (= 7.1.3.2)
|
21
58
|
builder (~> 3.1)
|
22
|
-
erubi (~> 1.
|
23
|
-
rails-dom-testing (~> 2.
|
24
|
-
rails-html-sanitizer (~> 1.
|
25
|
-
|
59
|
+
erubi (~> 1.11)
|
60
|
+
rails-dom-testing (~> 2.2)
|
61
|
+
rails-html-sanitizer (~> 1.6)
|
62
|
+
activejob (7.1.3.2)
|
63
|
+
activesupport (= 7.1.3.2)
|
64
|
+
globalid (>= 0.3.6)
|
65
|
+
activemodel (7.1.3.2)
|
66
|
+
activesupport (= 7.1.3.2)
|
67
|
+
activerecord (7.1.3.2)
|
68
|
+
activemodel (= 7.1.3.2)
|
69
|
+
activesupport (= 7.1.3.2)
|
70
|
+
timeout (>= 0.4.0)
|
71
|
+
activestorage (7.1.3.2)
|
72
|
+
actionpack (= 7.1.3.2)
|
73
|
+
activejob (= 7.1.3.2)
|
74
|
+
activerecord (= 7.1.3.2)
|
75
|
+
activesupport (= 7.1.3.2)
|
76
|
+
marcel (~> 1.0)
|
77
|
+
activesupport (7.1.3.2)
|
78
|
+
base64
|
79
|
+
bigdecimal
|
26
80
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
27
|
-
|
28
|
-
|
29
|
-
|
81
|
+
connection_pool (>= 2.2.5)
|
82
|
+
drb
|
83
|
+
i18n (>= 1.6, < 2)
|
84
|
+
minitest (>= 5.1)
|
85
|
+
mutex_m
|
86
|
+
tzinfo (~> 2.0)
|
87
|
+
base64 (0.2.0)
|
88
|
+
bigdecimal (3.1.7)
|
30
89
|
builder (3.2.4)
|
31
|
-
concurrent-ruby (1.
|
90
|
+
concurrent-ruby (1.2.3)
|
91
|
+
connection_pool (2.4.1)
|
32
92
|
crass (1.0.6)
|
33
|
-
|
93
|
+
date (3.4.1)
|
94
|
+
diff-lcs (1.5.1)
|
34
95
|
docile (1.1.5)
|
35
|
-
dotenv (
|
36
|
-
|
37
|
-
|
96
|
+
dotenv (3.1.1)
|
97
|
+
drb (2.2.1)
|
98
|
+
erubi (1.12.0)
|
99
|
+
globalid (1.2.1)
|
100
|
+
activesupport (>= 6.1)
|
101
|
+
i18n (1.14.4)
|
38
102
|
concurrent-ruby (~> 1.0)
|
39
|
-
|
40
|
-
|
103
|
+
io-console (0.7.2)
|
104
|
+
irb (1.13.0)
|
105
|
+
rdoc (>= 4.0.0)
|
106
|
+
reline (>= 0.4.2)
|
107
|
+
json (2.7.2)
|
108
|
+
loofah (2.22.0)
|
41
109
|
crass (~> 1.0.2)
|
42
|
-
nokogiri (>= 1.
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
110
|
+
nokogiri (>= 1.12.0)
|
111
|
+
mail (2.8.1)
|
112
|
+
mini_mime (>= 0.1.1)
|
113
|
+
net-imap
|
114
|
+
net-pop
|
115
|
+
net-smtp
|
116
|
+
marcel (1.0.4)
|
117
|
+
mini_mime (1.1.5)
|
118
|
+
minitest (5.22.3)
|
119
|
+
mutex_m (0.2.0)
|
120
|
+
net-imap (0.5.6)
|
121
|
+
date
|
122
|
+
net-protocol
|
123
|
+
net-pop (0.1.2)
|
124
|
+
net-protocol
|
125
|
+
net-protocol (0.2.2)
|
126
|
+
timeout
|
127
|
+
net-smtp (0.5.1)
|
128
|
+
net-protocol
|
129
|
+
nio4r (2.7.4)
|
130
|
+
nokogiri (1.16.4-arm64-darwin)
|
131
|
+
racc (~> 1.4)
|
132
|
+
nokogiri (1.16.4-x86_64-linux)
|
133
|
+
racc (~> 1.4)
|
134
|
+
psych (5.1.2)
|
135
|
+
stringio
|
136
|
+
racc (1.7.3)
|
137
|
+
rack (3.0.10)
|
138
|
+
rack-session (2.0.0)
|
139
|
+
rack (>= 3.0.0)
|
140
|
+
rack-test (2.1.0)
|
141
|
+
rack (>= 1.3)
|
142
|
+
rackup (2.1.0)
|
143
|
+
rack (>= 3)
|
144
|
+
webrick (~> 1.8)
|
145
|
+
rails (7.1.3.2)
|
146
|
+
actioncable (= 7.1.3.2)
|
147
|
+
actionmailbox (= 7.1.3.2)
|
148
|
+
actionmailer (= 7.1.3.2)
|
149
|
+
actionpack (= 7.1.3.2)
|
150
|
+
actiontext (= 7.1.3.2)
|
151
|
+
actionview (= 7.1.3.2)
|
152
|
+
activejob (= 7.1.3.2)
|
153
|
+
activemodel (= 7.1.3.2)
|
154
|
+
activerecord (= 7.1.3.2)
|
155
|
+
activestorage (= 7.1.3.2)
|
156
|
+
activesupport (= 7.1.3.2)
|
157
|
+
bundler (>= 1.15.0)
|
158
|
+
railties (= 7.1.3.2)
|
159
|
+
rails-dom-testing (2.2.0)
|
160
|
+
activesupport (>= 5.0.0)
|
161
|
+
minitest
|
53
162
|
nokogiri (>= 1.6)
|
54
|
-
rails-html-sanitizer (1.
|
55
|
-
loofah (~> 2.
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
163
|
+
rails-html-sanitizer (1.6.0)
|
164
|
+
loofah (~> 2.21)
|
165
|
+
nokogiri (~> 1.14)
|
166
|
+
railties (7.1.3.2)
|
167
|
+
actionpack (= 7.1.3.2)
|
168
|
+
activesupport (= 7.1.3.2)
|
169
|
+
irb
|
170
|
+
rackup (>= 1.0.0)
|
171
|
+
rake (>= 12.2)
|
172
|
+
thor (~> 1.0, >= 1.2.2)
|
173
|
+
zeitwerk (~> 2.6)
|
62
174
|
rake (12.3.3)
|
175
|
+
rdoc (6.6.3.1)
|
176
|
+
psych (>= 4.0.0)
|
177
|
+
reline (0.5.5)
|
178
|
+
io-console (~> 0.5)
|
63
179
|
rspec-core (3.9.3)
|
64
180
|
rspec-support (~> 3.9.3)
|
65
|
-
rspec-expectations (3.9.
|
181
|
+
rspec-expectations (3.9.4)
|
66
182
|
diff-lcs (>= 1.2.0, < 2.0)
|
67
183
|
rspec-support (~> 3.9.0)
|
68
184
|
rspec-mocks (3.9.1)
|
@@ -82,16 +198,25 @@ GEM
|
|
82
198
|
json (>= 1.8, < 3)
|
83
199
|
simplecov-html (~> 0.10.0)
|
84
200
|
simplecov-html (0.10.2)
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
201
|
+
stringio (3.1.0)
|
202
|
+
thor (1.3.1)
|
203
|
+
timeout (0.4.3)
|
204
|
+
tzinfo (2.0.6)
|
205
|
+
concurrent-ruby (~> 1.0)
|
206
|
+
webrick (1.8.1)
|
207
|
+
websocket-driver (0.7.7)
|
208
|
+
base64
|
209
|
+
websocket-extensions (>= 0.1.0)
|
210
|
+
websocket-extensions (0.1.5)
|
211
|
+
zeitwerk (2.6.13)
|
89
212
|
|
90
213
|
PLATFORMS
|
91
|
-
|
214
|
+
arm64-darwin-22
|
215
|
+
arm64-darwin-23
|
216
|
+
x86_64-linux
|
92
217
|
|
93
218
|
DEPENDENCIES
|
94
|
-
bundler (~>
|
219
|
+
bundler (~> 2.4.22)
|
95
220
|
decanter!
|
96
221
|
dotenv
|
97
222
|
rake (~> 12.0)
|
@@ -99,4 +224,4 @@ DEPENDENCIES
|
|
99
224
|
simplecov (~> 0.15.1)
|
100
225
|
|
101
226
|
BUNDLED WITH
|
102
|
-
|
227
|
+
2.4.22
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
Decanter is a Ruby gem that makes it easy to transform incoming data before it hits the model. You can think of Decanter as the opposite of Active Model Serializers (AMS). While AMS transforms your outbound data into a format that your frontend consumes, Decanter transforms your incoming data into a format that your backend consumes.
|
4
4
|
|
5
5
|
```ruby
|
6
|
-
gem 'decanter', '~>
|
6
|
+
gem 'decanter', '~> 5.0'
|
7
7
|
```
|
8
8
|
|
9
9
|
## Migration Guides
|
@@ -77,6 +77,7 @@ end
|
|
77
77
|
```
|
78
78
|
|
79
79
|
#### Parsers
|
80
|
+
|
80
81
|
```
|
81
82
|
rails g parser TruncatedString
|
82
83
|
|
@@ -133,8 +134,8 @@ You can use the `is_collection` option for explicit control over decanting colle
|
|
133
134
|
If this option is not provided, autodetect logic is used to determine if the providing incoming params holds a single object or collection of objects.
|
134
135
|
|
135
136
|
- `nil` or not provided: will try to autodetect single vs collection
|
136
|
-
- `true` will always treat the incoming params args as
|
137
|
-
- `false` will always treat incoming params args as
|
137
|
+
- `true` will always treat the incoming params args as _collection_
|
138
|
+
- `false` will always treat incoming params args as _single object_
|
138
139
|
- `truthy` will raise an error
|
139
140
|
|
140
141
|
### Nested resources
|
@@ -178,10 +179,11 @@ Some parsers can receive options that modify their behavior. These options are p
|
|
178
179
|
```ruby
|
179
180
|
input :start_date, :date, parse_format: '%Y-%m-%d'
|
180
181
|
```
|
182
|
+
|
181
183
|
**Available Options:**
|
182
|
-
| Parser
|
184
|
+
| Parser | Option | Default | Notes
|
183
185
|
| ----------- | ----------- | -----------| -----------
|
184
|
-
| `ArrayParser`
|
186
|
+
| `ArrayParser` | `parse_each`| N/A | Accepts a parser type, then uses that parser to parse each element in the array. If this option is not defined, each element is simply returned.
|
185
187
|
| `DateParser`| `parse_format` | `'%m/%d/%Y'`| Accepts any format string accepted by Ruby's `strftime` method
|
186
188
|
| `DateTimeParser` | `parse_format` | `'%m/%d/%Y %I:%M:%S %p'` | Accepts any format string accepted by Ruby's `strftime` method
|
187
189
|
|
@@ -299,7 +301,6 @@ end
|
|
299
301
|
|
300
302
|
_Note: we recommend using [Active Record validations](https://guides.rubyonrails.org/active_record_validations.html) to check for presence of an attribute, rather than using the `required` option. This method is intended for use in non-RESTful routes or cases where Active Record validations are not available._
|
301
303
|
|
302
|
-
|
303
304
|
### Default values
|
304
305
|
|
305
306
|
If you provide the option `:default_value` for an input in your decanter, the input key will be initialized with the given default value. Input keys not found in the incoming data parameters will be set to the provided default rather than ignoring the missing key. Note: `nil` and empty keys will not be overridden.
|
data/decanter.gemspec
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
3
|
require 'decanter/version'
|
5
4
|
|
@@ -9,29 +8,29 @@ Gem::Specification.new do |spec|
|
|
9
8
|
spec.authors = ['Ryan Francis', 'David Corwin']
|
10
9
|
spec.email = ['ryan@launchpadlab.com']
|
11
10
|
|
12
|
-
spec.summary =
|
13
|
-
spec.description =
|
11
|
+
spec.summary = 'Form Parser for Rails'
|
12
|
+
spec.description = 'Decanter aims to reduce complexity in Rails controllers by creating a place for transforming data before it hits the model and database.'
|
14
13
|
spec.homepage = 'https://github.com/launchpadlab/decanter'
|
15
14
|
spec.license = 'MIT'
|
15
|
+
spec.required_ruby_version = '>= 3.2.0'
|
16
16
|
|
17
17
|
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
18
18
|
# delete this section to allow pushing this gem to any host.
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
|
23
|
-
end
|
19
|
+
raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.' unless spec.respond_to?(:metadata)
|
20
|
+
|
21
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
24
22
|
|
25
23
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
24
|
spec.bindir = 'exe'
|
27
25
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
26
|
spec.require_paths = ['lib']
|
29
27
|
|
30
|
-
spec.add_dependency '
|
28
|
+
spec.add_dependency 'rails', '>= 7.1.3.2'
|
29
|
+
spec.add_dependency 'actionpack', '>= 7.1.3.2'
|
31
30
|
spec.add_dependency 'activesupport'
|
32
31
|
spec.add_dependency 'rails-html-sanitizer', '>= 1.0.4'
|
33
32
|
|
34
|
-
spec.add_development_dependency 'bundler', '~>
|
33
|
+
spec.add_development_dependency 'bundler', '~> 2.4.22'
|
35
34
|
spec.add_development_dependency 'dotenv'
|
36
35
|
spec.add_development_dependency 'rake', '~> 12.0'
|
37
36
|
spec.add_development_dependency 'rspec-rails', '~> 3.9'
|
data/lib/decanter/core.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'action_controller'
|
2
|
+
|
1
3
|
module Decanter
|
2
4
|
module Core
|
3
5
|
DEFAULT_VALUE_KEY = :default_value
|
@@ -8,40 +10,42 @@ module Decanter
|
|
8
10
|
end
|
9
11
|
|
10
12
|
module ClassMethods
|
11
|
-
def input(name, parsers=nil, **options)
|
13
|
+
def input(name, parsers = nil, **options)
|
12
14
|
# Convert all input names to symbols to correctly calculate handled vs. unhandled keys
|
13
15
|
input_names = [name].flatten.map(&:to_sym)
|
14
16
|
|
15
17
|
if input_names.length > 1 && parsers.blank?
|
16
|
-
raise ArgumentError
|
18
|
+
raise ArgumentError, "#{self.name} no parser specified for input with multiple values."
|
17
19
|
end
|
18
20
|
|
19
21
|
handlers[input_names] = {
|
20
|
-
key:
|
21
|
-
name:
|
22
|
-
options
|
23
|
-
parsers
|
24
|
-
type:
|
22
|
+
key: options.fetch(:key, input_names.first),
|
23
|
+
name: input_names,
|
24
|
+
options:,
|
25
|
+
parsers:,
|
26
|
+
type: :input
|
25
27
|
}
|
26
28
|
end
|
27
29
|
|
30
|
+
# Adjusting has_many to explicitly define keyword arguments
|
28
31
|
def has_many(assoc, **options)
|
29
32
|
handlers[assoc] = {
|
30
|
-
assoc
|
31
|
-
key:
|
32
|
-
name:
|
33
|
-
options
|
34
|
-
type:
|
33
|
+
assoc:,
|
34
|
+
key: options.fetch(:key, assoc),
|
35
|
+
name: assoc,
|
36
|
+
options:,
|
37
|
+
type: :has_many
|
35
38
|
}
|
36
39
|
end
|
37
40
|
|
41
|
+
# Adjusting has_one similarly
|
38
42
|
def has_one(assoc, **options)
|
39
43
|
handlers[assoc] = {
|
40
|
-
assoc
|
41
|
-
key:
|
42
|
-
name:
|
43
|
-
options
|
44
|
-
type:
|
44
|
+
assoc:,
|
45
|
+
key: options.fetch(:key, assoc),
|
46
|
+
name: assoc,
|
47
|
+
options:,
|
48
|
+
type: :has_one
|
45
49
|
}
|
46
50
|
end
|
47
51
|
|
@@ -50,12 +54,15 @@ module Decanter
|
|
50
54
|
end
|
51
55
|
|
52
56
|
def strict(mode)
|
53
|
-
raise(ArgumentError, "#{
|
57
|
+
raise(ArgumentError, "#{name}: Unknown strict value #{mode}") unless [:ignore, true, false].include? mode
|
58
|
+
|
54
59
|
@strict_mode = mode
|
55
60
|
end
|
56
61
|
|
57
62
|
def log_unhandled_keys(mode)
|
58
|
-
raise(ArgumentError, "#{
|
63
|
+
raise(ArgumentError, "#{name}: Unknown log_unhandled_keys value #{mode}") unless [true,
|
64
|
+
false].include? mode
|
65
|
+
|
59
66
|
@log_unhandled_keys_mode = mode
|
60
67
|
end
|
61
68
|
|
@@ -65,16 +72,16 @@ module Decanter
|
|
65
72
|
|
66
73
|
# Convert all params passed to a decanter to a hash with indifferent access to mitigate accessor ambiguity
|
67
74
|
accessible_args = to_indifferent_hash(args)
|
68
|
-
{}.merge(
|
69
|
-
.merge(
|
70
|
-
.merge(
|
75
|
+
{}.merge(default_keys)
|
76
|
+
.merge(unhandled_keys(accessible_args))
|
77
|
+
.merge(handled_keys(accessible_args))
|
71
78
|
end
|
72
79
|
|
73
80
|
def default_keys
|
74
81
|
# return keys with provided default value when key is not defined within incoming args
|
75
82
|
default_result = default_value_inputs
|
76
|
-
|
77
|
-
|
83
|
+
.map { |input| [input[:key], input[:options][DEFAULT_VALUE_KEY]] }
|
84
|
+
.to_h
|
78
85
|
|
79
86
|
# parse handled default values, including keys
|
80
87
|
# with defaults not already managed by handled_keys
|
@@ -100,8 +107,9 @@ module Decanter
|
|
100
107
|
end
|
101
108
|
end
|
102
109
|
|
103
|
-
def required_input_keys_present?(args={})
|
110
|
+
def required_input_keys_present?(args = {})
|
104
111
|
return true unless any_inputs_required?
|
112
|
+
|
105
113
|
compact_inputs = required_inputs.compact
|
106
114
|
compact_inputs.all? do |input|
|
107
115
|
args.keys.map(&:to_sym).include?(input) && !args[input].nil?
|
@@ -109,7 +117,8 @@ module Decanter
|
|
109
117
|
end
|
110
118
|
|
111
119
|
def empty_required_input_error
|
112
|
-
raise(MissingRequiredInputValue,
|
120
|
+
raise(MissingRequiredInputValue,
|
121
|
+
'Required inputs have been declared, but no values for those inputs were passed.')
|
113
122
|
end
|
114
123
|
|
115
124
|
def empty_args_error
|
@@ -120,20 +129,20 @@ module Decanter
|
|
120
129
|
|
121
130
|
def unhandled_keys(args)
|
122
131
|
unhandled_keys = args.keys.map(&:to_sym) -
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
132
|
+
handlers.keys.flatten.uniq -
|
133
|
+
keys_to_ignore -
|
134
|
+
handlers.values
|
135
|
+
.select { |handler| handler[:type] != :input }
|
136
|
+
.map { |handler| "#{handler[:name]}_attributes".to_sym }
|
128
137
|
|
129
138
|
return {} unless unhandled_keys.any?
|
130
139
|
|
131
140
|
case strict_mode
|
132
141
|
when :ignore
|
133
|
-
p "#{
|
142
|
+
p "#{name} ignoring unhandled keys: #{unhandled_keys.join(', ')}." if log_unhandled_keys_mode
|
134
143
|
{}
|
135
144
|
when true
|
136
|
-
raise(UnhandledKeysError, "#{
|
145
|
+
raise(UnhandledKeysError, "#{name} received unhandled keys: #{unhandled_keys.join(', ')}.")
|
137
146
|
else
|
138
147
|
args.select { |key| unhandled_keys.include? key.to_sym }
|
139
148
|
end
|
@@ -155,22 +164,22 @@ module Decanter
|
|
155
164
|
def handle(handler, args)
|
156
165
|
values = args.values_at(*handler[:name])
|
157
166
|
values = values.length == 1 ? values.first : values
|
158
|
-
|
167
|
+
send("handle_#{handler[:type]}", handler, values)
|
159
168
|
end
|
160
169
|
|
161
170
|
def handle_input(handler, args)
|
162
|
-
|
163
|
-
|
164
|
-
|
171
|
+
values = args.values_at(*handler[:name])
|
172
|
+
values = values.length == 1 ? values.first : values
|
173
|
+
parse(handler[:key], handler[:parsers], values, handler[:options])
|
165
174
|
end
|
166
175
|
|
167
176
|
def handle_association(handler, args)
|
168
177
|
assoc_handlers = [
|
169
178
|
handler,
|
170
179
|
handler.merge({
|
171
|
-
|
172
|
-
|
173
|
-
|
180
|
+
key: handler[:options].fetch(:key, "#{handler[:name]}_attributes").to_sym,
|
181
|
+
name: "#{handler[:name]}_attributes".to_sym
|
182
|
+
})
|
174
183
|
]
|
175
184
|
|
176
185
|
assoc_handler_names = assoc_handlers.map { |_handler| _handler[:name] }
|
@@ -180,20 +189,21 @@ module Decanter
|
|
180
189
|
{}
|
181
190
|
when 1
|
182
191
|
_handler = assoc_handlers.detect { |_handler| args.has_key?(_handler[:name]) }
|
183
|
-
|
192
|
+
send("handle_#{_handler[:type]}", _handler, args[_handler[:name]])
|
184
193
|
else
|
185
|
-
raise ArgumentError
|
194
|
+
raise ArgumentError, "Handler #{handler[:name]} matches multiple keys: #{assoc_handler_names}."
|
186
195
|
end
|
187
196
|
end
|
188
197
|
|
189
198
|
def handle_has_many(handler, values)
|
190
199
|
decanter = decanter_for_handler(handler)
|
191
200
|
if values.is_a?(Hash)
|
192
|
-
parsed_values = values.map do |
|
201
|
+
parsed_values = values.map do |_index, input_values|
|
193
202
|
next if input_values.nil?
|
203
|
+
|
194
204
|
decanter.decant(input_values)
|
195
205
|
end
|
196
|
-
|
206
|
+
{ handler[:key] => parsed_values }
|
197
207
|
else
|
198
208
|
{
|
199
209
|
handler[:key] => values.compact.map { |value| decanter.decant(value) }
|
@@ -207,17 +217,16 @@ module Decanter
|
|
207
217
|
|
208
218
|
def decanter_for_handler(handler)
|
209
219
|
if specified_decanter = handler[:options][:decanter]
|
210
|
-
Decanter
|
220
|
+
Decanter.decanter_from(specified_decanter)
|
211
221
|
else
|
212
|
-
Decanter
|
222
|
+
Decanter.decanter_for(handler[:assoc])
|
213
223
|
end
|
214
224
|
end
|
215
225
|
|
216
226
|
def parse(key, parsers, value, options)
|
217
227
|
return { key => value } unless parsers
|
218
|
-
if options[:required] && value_missing?(value)
|
219
|
-
|
220
|
-
end
|
228
|
+
raise ArgumentError, "No value for required argument: #{key}" if options[:required] && value_missing?(value)
|
229
|
+
|
221
230
|
parser_classes = Parser.parsers_for(parsers)
|
222
231
|
Parser.compose_parsers(parser_classes).parse(key, value, options)
|
223
232
|
end
|
@@ -235,7 +244,8 @@ module Decanter
|
|
235
244
|
end
|
236
245
|
|
237
246
|
def log_unhandled_keys_mode
|
238
|
-
return !!
|
247
|
+
return !!Decanter.configuration.log_unhandled_keys if @log_unhandled_keys_mode.nil?
|
248
|
+
|
239
249
|
!!@log_unhandled_keys_mode
|
240
250
|
end
|
241
251
|
|
@@ -244,11 +254,12 @@ module Decanter
|
|
244
254
|
private
|
245
255
|
|
246
256
|
def value_missing?(value)
|
247
|
-
value.nil? || value ==
|
257
|
+
value.nil? || value == ''
|
248
258
|
end
|
249
259
|
|
250
260
|
def to_indifferent_hash(args)
|
251
|
-
return args.to_unsafe_h if args.
|
261
|
+
return args.to_unsafe_h if args.instance_of?(ActionController::Parameters)
|
262
|
+
|
252
263
|
args.to_h.with_indifferent_access
|
253
264
|
end
|
254
265
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Decanter
|
2
2
|
module Parser
|
3
|
-
class ArrayParser <
|
3
|
+
class ArrayParser < Base
|
4
4
|
|
5
5
|
DUMMY_VALUE_KEY = '_'.freeze
|
6
6
|
|
@@ -10,8 +10,8 @@ module Decanter
|
|
10
10
|
# Fetch parser classes for provided keys
|
11
11
|
parse_each = options.fetch(:parse_each, :pass)
|
12
12
|
item_parsers = Parser.parsers_for(Array.wrap(parse_each))
|
13
|
-
unless item_parsers.all? { |parser| parser <= ValueParser }
|
14
|
-
raise Decanter::ParseError.new 'parser(s) for array items must subclass ValueParser'
|
13
|
+
unless item_parsers.all? { |parser| parser <= ValueParser || parser <= PassParser }
|
14
|
+
raise Decanter::ParseError.new 'parser(s) for array items must subclass either ValueParser or PassParser'
|
15
15
|
end
|
16
16
|
# Compose supplied parsers
|
17
17
|
item_parser = Parser.compose_parsers(item_parsers)
|
data/lib/decanter/parser/core.rb
CHANGED
@@ -5,7 +5,6 @@ module Decanter
|
|
5
5
|
allow DateTime
|
6
6
|
|
7
7
|
parser do |val, options|
|
8
|
-
raise Decanter::ParseError.new 'Expects a single value' if val.is_a? Array
|
9
8
|
next if (val.nil? || val === '')
|
10
9
|
parse_format = options.fetch(:parse_format, '%m/%d/%Y %I:%M:%S %p')
|
11
10
|
::DateTime.strptime(val, parse_format)
|
@@ -3,8 +3,16 @@ require_relative 'core'
|
|
3
3
|
module Decanter
|
4
4
|
module Parser
|
5
5
|
class ValueParser < Base
|
6
|
+
|
6
7
|
def self._parse(name, value, options={})
|
7
|
-
|
8
|
+
self.validate_singularity(value)
|
9
|
+
super(name, value, options)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def self.validate_singularity(value)
|
15
|
+
raise Decanter::ParseError.new 'Expects a single value' if value.is_a? Array
|
8
16
|
end
|
9
17
|
end
|
10
18
|
end
|
data/lib/decanter/version.rb
CHANGED
@@ -0,0 +1,11 @@
|
|
1
|
+
# v5.0.0 Migration Guide
|
2
|
+
|
3
|
+
_Note: this guide assumes you are upgrading from decanter v4 to v5._
|
4
|
+
|
5
|
+
This version contains major updates including the upgrade to Ruby version 3.3.0. Ensure your environment is compatible with this Ruby version before proceeding.
|
6
|
+
|
7
|
+
## Major Changes
|
8
|
+
|
9
|
+
- **Ruby Version Update**: Decanter now requires Ruby 3.3.0. This update brings several language [improvements and performance enhancements](https://www.ruby-lang.org/en/news/2023/12/25/ruby-3-3-0-released/).
|
10
|
+
|
11
|
+
- **Rails Dependency Update**: Decanter now requires Rails 7.1.3.2.
|
metadata
CHANGED
@@ -1,30 +1,44 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: decanter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Francis
|
8
8
|
- David Corwin
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2025-04-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 7.1.3.2
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 7.1.3.2
|
14
28
|
- !ruby/object:Gem::Dependency
|
15
29
|
name: actionpack
|
16
30
|
requirement: !ruby/object:Gem::Requirement
|
17
31
|
requirements:
|
18
32
|
- - ">="
|
19
33
|
- !ruby/object:Gem::Version
|
20
|
-
version:
|
34
|
+
version: 7.1.3.2
|
21
35
|
type: :runtime
|
22
36
|
prerelease: false
|
23
37
|
version_requirements: !ruby/object:Gem::Requirement
|
24
38
|
requirements:
|
25
39
|
- - ">="
|
26
40
|
- !ruby/object:Gem::Version
|
27
|
-
version:
|
41
|
+
version: 7.1.3.2
|
28
42
|
- !ruby/object:Gem::Dependency
|
29
43
|
name: activesupport
|
30
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -59,14 +73,14 @@ dependencies:
|
|
59
73
|
requirements:
|
60
74
|
- - "~>"
|
61
75
|
- !ruby/object:Gem::Version
|
62
|
-
version:
|
76
|
+
version: 2.4.22
|
63
77
|
type: :development
|
64
78
|
prerelease: false
|
65
79
|
version_requirements: !ruby/object:Gem::Requirement
|
66
80
|
requirements:
|
67
81
|
- - "~>"
|
68
82
|
- !ruby/object:Gem::Version
|
69
|
-
version:
|
83
|
+
version: 2.4.22
|
70
84
|
- !ruby/object:Gem::Dependency
|
71
85
|
name: dotenv
|
72
86
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,9 +150,11 @@ files:
|
|
136
150
|
- ".github/ISSUE_TEMPLATE/BUG_REPORT.md"
|
137
151
|
- ".github/ISSUE_TEMPLATE/FEATURE_REQUEST.md"
|
138
152
|
- ".github/PULL_REQUEST_TEMPLATE.md"
|
153
|
+
- ".github/workflows/versionci.yml"
|
139
154
|
- ".gitignore"
|
140
155
|
- ".rspec"
|
141
156
|
- ".ruby-version"
|
157
|
+
- ".tool-versions"
|
142
158
|
- ".travis.yml"
|
143
159
|
- CODE_OF_CONDUCT.md
|
144
160
|
- CONTRIBUTING.md
|
@@ -184,12 +200,13 @@ files:
|
|
184
200
|
- lib/generators/rails/templates/parser.rb.erb
|
185
201
|
- migration-guides/v3.0.0.md
|
186
202
|
- migration-guides/v4.0.0.md
|
203
|
+
- migration-guides/v5.0.0.md
|
187
204
|
homepage: https://github.com/launchpadlab/decanter
|
188
205
|
licenses:
|
189
206
|
- MIT
|
190
207
|
metadata:
|
191
208
|
allowed_push_host: https://rubygems.org
|
192
|
-
post_install_message:
|
209
|
+
post_install_message:
|
193
210
|
rdoc_options: []
|
194
211
|
require_paths:
|
195
212
|
- lib
|
@@ -197,15 +214,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
197
214
|
requirements:
|
198
215
|
- - ">="
|
199
216
|
- !ruby/object:Gem::Version
|
200
|
-
version:
|
217
|
+
version: 3.2.0
|
201
218
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
202
219
|
requirements:
|
203
220
|
- - ">="
|
204
221
|
- !ruby/object:Gem::Version
|
205
222
|
version: '0'
|
206
223
|
requirements: []
|
207
|
-
rubygems_version: 3.
|
208
|
-
signing_key:
|
224
|
+
rubygems_version: 3.5.16
|
225
|
+
signing_key:
|
209
226
|
specification_version: 4
|
210
227
|
summary: Form Parser for Rails
|
211
228
|
test_files: []
|