decanter 3.4.2 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c49220d0b25ab5f09d58aded4f247e71793d1f98129efebe336651c1e58303dd
4
- data.tar.gz: b28b971df1dba14570abfcb9cb6116e13512afcb3412656be1c169af1ecef69a
3
+ metadata.gz: fa10b029acfb863227bd9b42b915f32ce94d65dd0a82955bc13b530d3278fe87
4
+ data.tar.gz: c22c844568de79508809ceb5609e8166cc547bfc26fa4a293085baae56ce6ade
5
5
  SHA512:
6
- metadata.gz: 911b7de4eaf39749fbb1029016e00da3d72a7b0f46d828d6c1d9f9f49f945c51ab91bbefc4ef2fd45b1e69e2d488ec07b09358c29a5009d7222122047cf24785
7
- data.tar.gz: 6acdf1923faec6984bb42884c47afb0574f94b65a12c1275953eb583c61a77b860074b3ef7b03bd157f3fa37e10fea8abf6bd358872fdce3dc614f3d16626323
6
+ metadata.gz: 1df6bc91a47bc1dbbf9a528b895407fed1d84cdf733121aff44440d03b4fe82a8871143d1b2d0bb87b6c9cfb1467c0a839cb394ac0bd813e5df7230faa00b9a4
7
+ data.tar.gz: 9e26fe9068e2f58d4ae8b1987a05fde413e9edac2bd0f123add82c7dfcb18f50a9d08629b71fb2c1d2d0387668f32ecfc53bda6a7e5a93d07289d96f8bbc5eaf
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- decanter (3.4.2)
4
+ decanter (4.0.0)
5
5
  actionpack (>= 4.2.10)
6
6
  activesupport
7
7
  rails-html-sanitizer (>= 1.0.4)
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', '~> 3.0'
6
+ gem 'decanter', '~> 4.0'
7
7
  ```
8
8
 
9
9
  ## Migration Guides
@@ -101,6 +101,7 @@ If this option is not provided, autodetect logic is used to determine if the pro
101
101
  - `nil` or not provided: will try to autodetect single vs collection
102
102
  - `true` will always treat the incoming params args as *collection*
103
103
  - `false` will always treat incoming params args as *single object*
104
+ - `truthy` will raise an error
104
105
 
105
106
  ### Nested resources
106
107
 
@@ -144,7 +145,11 @@ input :start_date, :date, parse_format: '%Y-%m-%d'
144
145
 
145
146
  ### Exceptions
146
147
 
147
- By default, `Decanter#decant` will raise an exception when unexpected parameters are passed. To override this behavior, you can disable strict mode:
148
+ By default, `Decanter#decant` will raise an exception when unexpected parameters are passed. To override this behavior, you can change the strict mode option to one of:
149
+
150
+ - `true` (default): unhandled keys will raise an unexpected parameters exception
151
+ - `false`: all parameter key-value pairs will be included in the result
152
+ - `:ignore`: unhandled keys will be excluded from the decanted result
148
153
 
149
154
  ```ruby
150
155
  class TripDecanter < Decanter::Base
@@ -275,11 +280,14 @@ TripDecanter.decant({ name: 'Vacation 2020' })
275
280
 
276
281
  You can generate a local copy of the default configuration with `rails generate decanter:install`. This will create an initializer where you can do global configuration:
277
282
 
283
+ Setting strict mode to :ignore will log out any unhandled keys. To avoid excessive logging, the global configuration can be set to `log_unhandled_keys = false`
284
+
278
285
  ```ruby
279
286
  # ./config/initializers/decanter.rb
280
287
 
281
288
  Decanter.config do |config|
282
289
  config.strict = false
290
+ config.log_unhandled_keys = false
283
291
  end
284
292
  ```
285
293
 
data/lib/decanter/base.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  require 'decanter/core'
2
+ require 'decanter/collection_detection'
2
3
 
3
4
  module Decanter
4
5
  class Base
5
6
  include Core
7
+ include CollectionDetection
6
8
  end
7
9
  end
@@ -0,0 +1,26 @@
1
+ module Decanter
2
+ module CollectionDetection
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ def decant(args, **options)
9
+ return super(args) unless collection?(args, options[:is_collection])
10
+
11
+ args.map { |resource| super(resource) }
12
+ end
13
+
14
+ private
15
+
16
+ # leveraging the approach used in the [fast JSON API gem](https://github.com/Netflix/fast_jsonapi#collection-serialization)
17
+ def collection?(args, collection_option = nil)
18
+ raise(ArgumentError, "#{name}: Unknown collection option value: #{collection_option}") unless [true, false, nil].include? collection_option
19
+
20
+ return collection_option unless collection_option.nil?
21
+
22
+ args.respond_to?(:size) && !args.respond_to?(:each_pair)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,10 +1,11 @@
1
1
  module Decanter
2
2
  class Configuration
3
3
 
4
- attr_accessor :strict
4
+ attr_accessor :strict, :log_unhandled_keys
5
5
 
6
6
  def initialize
7
7
  @strict = true
8
+ @log_unhandled_keys = true
8
9
  end
9
10
  end
10
11
  end
data/lib/decanter/core.rb CHANGED
@@ -50,10 +50,15 @@ module Decanter
50
50
  end
51
51
 
52
52
  def strict(mode)
53
- raise(ArgumentError, "#{self.name}: Unknown strict value #{mode}") unless [true, false].include? mode
53
+ raise(ArgumentError, "#{self.name}: Unknown strict value #{mode}") unless [:ignore, true, false].include? mode
54
54
  @strict_mode = mode
55
55
  end
56
56
 
57
+ def log_unhandled_keys(mode)
58
+ raise(ArgumentError, "#{self.name}: Unknown log_unhandled_keys value #{mode}") unless [true, false].include? mode
59
+ @log_unhandled_keys_mode = mode
60
+ end
61
+
57
62
  def decant(args)
58
63
  return handle_empty_args if args.blank?
59
64
  return empty_required_input_error unless required_input_keys_present?(args)
@@ -71,8 +76,9 @@ module Decanter
71
76
  .map { |input| [input[:key], input[:options][DEFAULT_VALUE_KEY]] }
72
77
  .to_h
73
78
 
74
- # parse default values
75
- handled_keys(default_result)
79
+ # parse handled default values, including keys
80
+ # with defaults not already managed by handled_keys
81
+ default_result.merge(handled_keys(default_result))
76
82
  end
77
83
 
78
84
  def default_value_inputs
@@ -121,14 +127,21 @@ module Decanter
121
127
  .map { |handler| "#{handler[:name]}_attributes".to_sym }
122
128
 
123
129
  return {} unless unhandled_keys.any?
124
- raise(UnhandledKeysError, "#{self.name} received unhandled keys: #{unhandled_keys.join(', ')}.") if strict_mode
125
- args.select { |key| unhandled_keys.include? key.to_sym }
130
+
131
+ case strict_mode
132
+ when :ignore
133
+ p "#{self.name} ignoring unhandled keys: #{unhandled_keys.join(', ')}." if log_unhandled_keys_mode
134
+ {}
135
+ when true
136
+ raise(UnhandledKeysError, "#{self.name} received unhandled keys: #{unhandled_keys.join(', ')}.")
137
+ else
138
+ args.select { |key| unhandled_keys.include? key.to_sym }
139
+ end
126
140
  end
127
141
 
128
142
  def handled_keys(args)
129
143
  arg_keys = args.keys.map(&:to_sym)
130
144
  inputs, assocs = handlers.values.partition { |handler| handler[:type] == :input }
131
-
132
145
  {}.merge(
133
146
  # Inputs
134
147
  inputs.select { |handler| (arg_keys & handler[:name]).any? }
@@ -221,6 +234,11 @@ module Decanter
221
234
  @strict_mode.nil? ? Decanter.configuration.strict : @strict_mode
222
235
  end
223
236
 
237
+ def log_unhandled_keys_mode
238
+ return !!(Decanter.configuration.log_unhandled_keys) if @log_unhandled_keys_mode.nil?
239
+ !!@log_unhandled_keys_mode
240
+ end
241
+
224
242
  # Helpers
225
243
 
226
244
  private
@@ -1,6 +1,5 @@
1
1
  module Decanter
2
2
  module Extensions
3
-
4
3
  def self.included(base)
5
4
  base.extend(ClassMethods)
6
5
  end
@@ -34,29 +33,13 @@ module Decanter
34
33
  .save!(context: options[:context])
35
34
  end
36
35
 
37
- def decant(args, options={})
38
- is_collection?(args, options[:is_collection]) ? decant_collection(args, options) : decant_args(args, options)
39
- end
40
-
41
- def decant_collection(args, options)
42
- args.map { |resource| decant_args(resource, options) }
43
- end
44
-
45
- def decant_args(args, options)
46
- if specified_decanter = options[:decanter]
36
+ def decant(args, options = {})
37
+ if (specified_decanter = options[:decanter])
47
38
  Decanter.decanter_from(specified_decanter)
48
39
  else
49
40
  Decanter.decanter_for(self)
50
41
  end.decant(args)
51
42
  end
52
-
53
- private
54
-
55
- # leveraging the approach used in the [fast JSON API gem](https://github.com/Netflix/fast_jsonapi#collection-serialization)
56
- def is_collection?(args, collection_option=nil)
57
- return collection_option[:is_collection] unless collection_option.nil?
58
- args.respond_to?(:size) && !args.respond_to?(:each_pair)
59
- end
60
43
  end
61
44
 
62
45
  module ActiveRecordExtensions
@@ -1,7 +1,7 @@
1
1
  module Decanter
2
2
  module Parser
3
3
  class FloatParser < ValueParser
4
- REGEX = /(\d|[.])/
4
+ REGEX = /(\d|[.]|[-])/
5
5
 
6
6
  allow Float, Integer
7
7
 
@@ -1,7 +1,7 @@
1
1
  module Decanter
2
2
  module Parser
3
3
  class IntegerParser < ValueParser
4
- REGEX = /(\d|[.])/
4
+ REGEX = /(\d|[.]|[-])/
5
5
 
6
6
  allow Integer
7
7
 
@@ -1,3 +1,3 @@
1
1
  module Decanter
2
- VERSION = '3.4.2'.freeze
2
+ VERSION = '4.0.0'.freeze
3
3
  end
data/lib/decanter.rb CHANGED
@@ -57,7 +57,6 @@ end
57
57
 
58
58
  require 'decanter/version'
59
59
  require 'decanter/configuration'
60
- require 'decanter/core'
61
60
  require 'decanter/base'
62
61
  require 'decanter/extensions'
63
62
  require 'decanter/exceptions'
@@ -0,0 +1,35 @@
1
+ # v4.0.0 Migration Guide
2
+
3
+ _Note: this guide assumes you are upgrading from decanter v3 to v4._
4
+
5
+ This version contains the following breaking changes:
6
+
7
+ 1. `FloatParser` and `IntegerParser` have been updated to address a bug where negative numbers were being parsed as positive. In the (unlikely) event that your project was relying on the previous behavior, you can pin the gem version to `v3.6.0` or include the legacy version(s) of the parsers as custom parsers in your project.
8
+
9
+ To add a custom parser, add the new parser class to your project:
10
+
11
+ ```rb
12
+ # app/parsers/postive_float_parser.rb
13
+
14
+ class PositiveFloatParser < Decanter::Parser::ValueParser
15
+ REGEX = /(\d|[.])/
16
+
17
+ allow Float, Integer
18
+
19
+ parser do |val, options|
20
+ raise Decanter::ParseError.new 'Expects a single value' if val.is_a? Array
21
+ next if (val.nil? || val === '')
22
+ val.scan(REGEX).join.try(:to_f)
23
+ end
24
+ end
25
+ ```
26
+
27
+ Then, use the appropriate key to look up the parser in your decanter:
28
+
29
+ ```rb
30
+ # app/decanters/product_decanter.rb
31
+
32
+ class ProductDecanter < Decanter::Base
33
+ input :price, :positive_float #=> PositiveFloatParser
34
+ end
35
+ ```
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: decanter
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.4.2
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Francis
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2021-01-05 00:00:00.000000000 Z
12
+ date: 2021-11-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: actionpack
@@ -152,6 +152,7 @@ files:
152
152
  - decanter.gemspec
153
153
  - lib/decanter.rb
154
154
  - lib/decanter/base.rb
155
+ - lib/decanter/collection_detection.rb
155
156
  - lib/decanter/configuration.rb
156
157
  - lib/decanter/core.rb
157
158
  - lib/decanter/exceptions.rb
@@ -182,6 +183,7 @@ files:
182
183
  - lib/generators/rails/templates/decanter.rb.erb
183
184
  - lib/generators/rails/templates/parser.rb.erb
184
185
  - migration-guides/v3.0.0.md
186
+ - migration-guides/v4.0.0.md
185
187
  homepage: https://github.com/launchpadlab/decanter
186
188
  licenses:
187
189
  - MIT