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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 855c0c38fda4543f1542a7519ff4234544d696a910f39fe195cf99f50ad6a780
4
- data.tar.gz: 28b8bb87ee97e7a0c371124cbb8a04319b35df6c2ee642f75290d822a2015186
3
+ metadata.gz: 740cf6eead3d1c13d1af276f12bd8f1e54e6052ff8791fffa3a9b133dde61b7c
4
+ data.tar.gz: 86eb37227ce42cf1dddfc7676b298f4de0681178b4540800fc6a67d682568542
5
5
  SHA512:
6
- metadata.gz: 523f731eb6580679bc32699b6ae473220dabf8840124657bd0bfbfbbfd2c892affdcf44ba33c9435f51366c83bb8d86ebe467f7b2a98ae0da32211b525d062ca
7
- data.tar.gz: 9923cf90108899c59990687ace8b0e23bad06dfb01b52d8c28594ba1f657a687007f33c4aacad4981dd2391106114df3098121a74e845ee967cccb52a94405c2
6
+ metadata.gz: aec4940ffc2ede503acf8ea757eeaf528bc65e0cc1282cc6fe6fc42c1e1a1f6cc27b9292c62946bca1939b7714cf4cf3e53d1da5093318068c95f349e5e77e88
7
+ data.tar.gz: 1af26f35262236b5efe518eea6cfc2b9f8716627f136c96f850233d1d34b7c08e7a5c252706e6cf98fe7544a28082f0774b231d7775f8b16a993b243968088b0
data/.github/CODEOWNERS CHANGED
@@ -1,2 +1,3 @@
1
1
  # These owners will be the default owners for everything in the repo.
2
- * @chawes13
2
+
3
+ - @nicoledow
@@ -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
- 2.6.5
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 (4.0.4)
5
- actionpack (>= 4.2.10)
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
- actionpack (5.2.4.4)
13
- actionview (= 5.2.4.4)
14
- activesupport (= 5.2.4.4)
15
- rack (~> 2.0, >= 2.0.8)
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.0)
18
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
19
- actionview (5.2.4.4)
20
- activesupport (= 5.2.4.4)
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.4)
23
- rails-dom-testing (~> 2.0)
24
- rails-html-sanitizer (~> 1.0, >= 1.0.3)
25
- activesupport (5.2.4.4)
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
- i18n (>= 0.7, < 2)
28
- minitest (~> 5.1)
29
- tzinfo (~> 1.1)
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.1.7)
90
+ concurrent-ruby (1.2.3)
91
+ connection_pool (2.4.1)
32
92
  crass (1.0.6)
33
- diff-lcs (1.4.4)
93
+ date (3.4.1)
94
+ diff-lcs (1.5.1)
34
95
  docile (1.1.5)
35
- dotenv (2.2.1)
36
- erubi (1.9.0)
37
- i18n (1.8.5)
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
- json (2.3.0)
40
- loofah (2.7.0)
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.5.9)
43
- method_source (1.0.0)
44
- mini_portile2 (2.4.0)
45
- minitest (5.14.2)
46
- nokogiri (1.10.10)
47
- mini_portile2 (~> 2.4.0)
48
- rack (2.2.3)
49
- rack-test (1.1.0)
50
- rack (>= 1.0, < 3)
51
- rails-dom-testing (2.0.3)
52
- activesupport (>= 4.2.0)
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.3.0)
55
- loofah (~> 2.3)
56
- railties (5.2.4.4)
57
- actionpack (= 5.2.4.4)
58
- activesupport (= 5.2.4.4)
59
- method_source
60
- rake (>= 0.8.7)
61
- thor (>= 0.19.0, < 2.0)
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.3)
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
- thor (1.0.1)
86
- thread_safe (0.3.6)
87
- tzinfo (1.2.7)
88
- thread_safe (~> 0.1)
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
- ruby
214
+ arm64-darwin-22
215
+ arm64-darwin-23
216
+ x86_64-linux
92
217
 
93
218
  DEPENDENCIES
94
- bundler (~> 1.9)
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
- 1.17.3
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', '~> 4.0'
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 *collection*
137
- - `false` will always treat incoming params args as *single object*
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 | Option | Default | Notes
184
+ | Parser | Option | Default | Notes
183
185
  | ----------- | ----------- | -----------| -----------
184
- | `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.
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
- # coding: utf-8
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 = %q{Form Parser for Rails}
13
- spec.description = %q{Decanter aims to reduce complexity in Rails controllers by creating a place for transforming data before it hits the model and database.}
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
- if spec.respond_to?(:metadata)
20
- spec.metadata['allowed_push_host'] = 'https://rubygems.org'
21
- else
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 'actionpack', '>= 4.2.10'
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', '~> 1.9'
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.new("#{self.name} no parser specified for input with multiple values.")
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: options.fetch(:key, input_names.first),
21
- name: input_names,
22
- options: options,
23
- parsers: parsers,
24
- type: :input
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: assoc,
31
- key: options.fetch(:key, assoc),
32
- name: assoc,
33
- options: options,
34
- type: :has_many
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: assoc,
41
- key: options.fetch(:key, assoc),
42
- name: assoc,
43
- options: options,
44
- type: :has_one
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, "#{self.name}: Unknown strict value #{mode}") unless [:ignore, true, false].include? mode
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, "#{self.name}: Unknown log_unhandled_keys value #{mode}") unless [true, false].include? mode
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( default_keys )
69
- .merge( unhandled_keys(accessible_args) )
70
- .merge( handled_keys(accessible_args) )
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
- .map { |input| [input[:key], input[:options][DEFAULT_VALUE_KEY]] }
77
- .to_h
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, 'Required inputs have been declared, but no values for those inputs were passed.')
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
- handlers.keys.flatten.uniq -
124
- keys_to_ignore -
125
- handlers.values
126
- .select { |handler| handler[:type] != :input }
127
- .map { |handler| "#{handler[:name]}_attributes".to_sym }
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 "#{self.name} ignoring unhandled keys: #{unhandled_keys.join(', ')}." if log_unhandled_keys_mode
142
+ p "#{name} ignoring unhandled keys: #{unhandled_keys.join(', ')}." if log_unhandled_keys_mode
134
143
  {}
135
144
  when true
136
- raise(UnhandledKeysError, "#{self.name} received unhandled keys: #{unhandled_keys.join(', ')}.")
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
- self.send("handle_#{handler[:type]}", handler, values)
167
+ send("handle_#{handler[:type]}", handler, values)
159
168
  end
160
169
 
161
170
  def handle_input(handler, args)
162
- values = args.values_at(*handler[:name])
163
- values = values.length == 1 ? values.first : values
164
- parse(handler[:key], handler[:parsers], values, handler[:options])
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
- key: handler[:options].fetch(:key, "#{handler[:name]}_attributes").to_sym,
172
- name: "#{handler[:name]}_attributes".to_sym
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
- self.send("handle_#{_handler[:type]}", _handler, args[_handler[:name]])
192
+ send("handle_#{_handler[:type]}", _handler, args[_handler[:name]])
184
193
  else
185
- raise ArgumentError.new("Handler #{handler[:name]} matches multiple keys: #{assoc_handler_names}.")
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 |index, input_values|
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
- return { handler[:key] => parsed_values }
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::decanter_from(specified_decanter)
220
+ Decanter.decanter_from(specified_decanter)
211
221
  else
212
- Decanter::decanter_for(handler[:assoc])
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
- raise ArgumentError.new("No value for required argument: #{key}")
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 !!(Decanter.configuration.log_unhandled_keys) if @log_unhandled_keys_mode.nil?
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.class.name == ACTION_CONTROLLER_PARAMETERS_CLASS_NAME
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 < ValueParser
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)
@@ -5,7 +5,6 @@ module Decanter
5
5
  allow TrueClass, FalseClass
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
  [1, '1'].include?(val) || !!/^true$/i.match?(val.to_s)
11
10
  end
@@ -8,6 +8,10 @@ module Decanter
8
8
 
9
9
  module ClassMethods
10
10
 
11
+ def _parse(name, value, options={})
12
+ { name => @parser.call(value, options) }
13
+ end
14
+
11
15
  # Check if allowed, parse if not
12
16
  def parse(name, value, options={})
13
17
  if allowed?(value)
@@ -5,7 +5,6 @@ module Decanter
5
5
  allow Date
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')
11
10
  ::Date.strptime(val, parse_format)
@@ -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)
@@ -6,7 +6,6 @@ module Decanter
6
6
  allow Float, Integer
7
7
 
8
8
  parser do |val, options|
9
- raise Decanter::ParseError.new 'Expects a single value' if val.is_a? Array
10
9
  next if (val.nil? || val === '')
11
10
  val.scan(REGEX).join.try(:to_f)
12
11
  end
@@ -6,7 +6,6 @@ module Decanter
6
6
  allow Integer
7
7
 
8
8
  parser do |val, options|
9
- raise Decanter::ParseError.new 'Expects a single value' if val.is_a? Array
10
9
  next if (val.nil? || val === '')
11
10
  val.is_a?(Float) ?
12
11
  val.to_i :
@@ -1,6 +1,6 @@
1
1
  module Decanter
2
2
  module Parser
3
- class PassParser < ValueParser
3
+ class PassParser < Base
4
4
 
5
5
  parser do |val, options|
6
6
  next if (val.nil? || val == '')
@@ -6,7 +6,6 @@ module Decanter
6
6
  allow Integer
7
7
 
8
8
  parser do |val, options|
9
- raise Decanter::ParseError.new 'Expects a single value' if val.is_a? Array
10
9
  next if (val.nil? || val === '')
11
10
  val.scan(REGEX).join.to_s
12
11
  end
@@ -2,7 +2,6 @@ module Decanter
2
2
  module Parser
3
3
  class StringParser < ValueParser
4
4
  parser do |val, options|
5
- raise Decanter::ParseError.new 'Expects a single value' if val.is_a? Array
6
5
  next if (val.nil? || val === '')
7
6
  next val if val.is_a? String
8
7
  val.to_s
@@ -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
- { name => @parser.call(value, options) }
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
@@ -1,3 +1,3 @@
1
1
  module Decanter
2
- VERSION = '4.0.4'.freeze
2
+ VERSION = '5.0.0'.freeze
3
3
  end
@@ -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.0.4
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: 2024-01-11 00:00:00.000000000 Z
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: 4.2.10
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: 4.2.10
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: '1.9'
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: '1.9'
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: '0'
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.0.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: []