decanter 3.3.0 → 3.5.1

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: b7ee762570171d13eede776ea7715f54e3daa9228e09b8a83f278c857eaeb04c
4
- data.tar.gz: 4dee8cfa404faeb3362925832d53b4e55c0f82cddd2427416c0fdf79a66ff85e
3
+ metadata.gz: f191e5daf97acca13c721c2ddca506a38919433f2207d2cecb59433b463dbea7
4
+ data.tar.gz: 579ec81e38550f980c0e1fe96e2db9bcf50d4c99b2a52d48ab875a8970bc2c07
5
5
  SHA512:
6
- metadata.gz: dd1b076ac0cbe78783ab19b4ae43d8fb7d87bd46b76155ec08dcbdfb55638415667c1ad19ccdeff4f81e0b0482c2ef32292a1686df648ee52e0b3ce4824ea069
7
- data.tar.gz: d34c06b627f4c1b4773829cd9e3003f3a8eb167a97acfa55be361628078d8230d8be1664d576d49eb6de9705d56f95c478ff52c56c2c656b1882d167df69e499
6
+ metadata.gz: b642637b676156b1e3f9783ceab09a5e9e8f6bf8ebf6c76e4c3ee292b95c59396bd62eb8ba6bf6ad296a5c5d116e3e25dc847a5c65da2ab7f2a443f482e6c3e5
7
+ data.tar.gz: 628ea27143c5ac74c97d141c67fbecedd99c51ac68f565d2677e15e250098848bb99c986e7d2f92809f09bd5008c1b41991d7c7f73bc97edc64b772f54fc494e
data/CONTRIBUTING.md CHANGED
@@ -33,4 +33,4 @@ For any non-trivial change, we prefer an issue to be created first. This helps u
33
33
  ### Sending a Pull Request
34
34
  If this is your first Pull Request, we recommend learning how to navigate GitHub via this free video tutorial: [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github).
35
35
 
36
- The LaunchPad Lab team monitors this repository for pull requests. Once a pull request has been created from a forked repository that **meets all the guidelines** outlined in the [Pull Request Checklist](.github/PULL_REQUEST_TEMPLATE.md), we will review the request and either merge it, request changes, or close it with an explanation. We aim to respond to pull requests within 48 hours.
36
+ The LaunchPad Lab team monitors this repository for pull requests. Once a pull request has been created from a forked repository that **meets all the guidelines** outlined in the [Pull Request Checklist](.github/PULL_REQUEST_TEMPLATE.md), we will review the request and either merge it, request changes, or close it with an explanation. We aim to respond to pull requests within 48 hours.
data/Gemfile.lock CHANGED
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- decanter (3.3.0)
4
+ decanter (3.5.1)
5
5
  actionpack (>= 4.2.10)
6
- activesupport (~> 5.2)
6
+ activesupport
7
7
  rails-html-sanitizer (>= 1.0.4)
8
8
 
9
9
  GEM
data/README.md CHANGED
@@ -15,6 +15,7 @@ gem 'decanter', '~> 3.0'
15
15
  - [Basic Usage](#basic-usage)
16
16
  - [Decanters](#decanters)
17
17
  - [Generators](#generators)
18
+ - [Decanting Collections](#decanting-collections)
18
19
  - [Nested resources](#nested-resources)
19
20
  - [Default parsers](#default-parsers)
20
21
  - [Parser options](#parser-options)
@@ -70,6 +71,38 @@ rails g decanter Trip name:string start_date:date end_date:date
70
71
  rails g parser TruncatedString
71
72
  ```
72
73
 
74
+ ### Decanting Collections
75
+
76
+ Decanter can decant a collection of a resource, applying the patterns used in the [fast JSON API gem](https://github.com/Netflix/fast_jsonapi#collection-serialization):
77
+
78
+ ```rb
79
+ # app/controllers/trips_controller.rb
80
+
81
+ def create
82
+ trip_params = {
83
+ trips: [
84
+ { name: 'Disney World', start_date: '12/24/2018', end_date: '12/28/2018' },
85
+ { name: 'Yosemite', start_date: '5/1/2017', end_date: '5/4/2017' }
86
+ ]
87
+ }
88
+ decanted_trip_params = TripDecanter.decant(trip_params[:trips])
89
+ Trip.create(decanted_trip_params) # bulk create trips with decanted params
90
+ end
91
+ ```
92
+
93
+ #### Control Over Decanting Collections
94
+
95
+ You can use the `is_collection` option for explicit control over decanting collections.
96
+
97
+ `decanted_trip_params = TripDecanter.decant(trip_params[:trips], is_collection: true)`
98
+
99
+ 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.
100
+
101
+ - `nil` or not provided: will try to autodetect single vs collection
102
+ - `true` will always treat the incoming params args as *collection*
103
+ - `false` will always treat incoming params args as *single object*
104
+ - `truthy` will raise an error
105
+
73
106
  ### Nested resources
74
107
 
75
108
  Decanters can declare relationships using `ActiveRecord`-style declarators:
@@ -112,7 +145,11 @@ input :start_date, :date, parse_format: '%Y-%m-%d'
112
145
 
113
146
  ### Exceptions
114
147
 
115
- 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
116
153
 
117
154
  ```ruby
118
155
  class TripDecanter < Decanter::Base
data/decanter.gemspec CHANGED
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
28
28
  spec.require_paths = ['lib']
29
29
 
30
30
  spec.add_dependency 'actionpack', '>= 4.2.10'
31
- spec.add_dependency 'activesupport', '~> 5.2'
31
+ spec.add_dependency 'activesupport'
32
32
  spec.add_dependency 'rails-html-sanitizer', '>= 1.0.4'
33
33
 
34
34
  spec.add_development_dependency 'bundler', '~> 1.9'
data/lib/decanter.rb CHANGED
@@ -13,7 +13,7 @@ module Decanter
13
13
  klass_or_sym.to_s.singularize.camelize
14
14
  else
15
15
  raise ArgumentError.new("cannot lookup decanter for #{klass_or_sym} with class #{klass_or_sym.class}")
16
- end.concat('Decanter')
16
+ end + 'Decanter'
17
17
  begin
18
18
  decanter_name.constantize
19
19
  rescue
@@ -57,9 +57,8 @@ 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'
64
63
  require 'decanter/parser'
65
- require 'decanter/railtie' if defined?(::Rails)
64
+ require 'decanter/railtie' if defined?(::Rails)
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
data/lib/decanter/core.rb CHANGED
@@ -8,7 +8,6 @@ module Decanter
8
8
  end
9
9
 
10
10
  module ClassMethods
11
-
12
11
  def input(name, parsers=nil, **options)
13
12
  # Convert all input names to symbols to correctly calculate handled vs. unhandled keys
14
13
  input_names = [name].flatten.map(&:to_sym)
@@ -51,7 +50,7 @@ module Decanter
51
50
  end
52
51
 
53
52
  def strict(mode)
54
- 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
55
54
  @strict_mode = mode
56
55
  end
57
56
 
@@ -72,8 +71,9 @@ module Decanter
72
71
  .map { |input| [input[:key], input[:options][DEFAULT_VALUE_KEY]] }
73
72
  .to_h
74
73
 
75
- # parse default values
76
- handled_keys(default_result)
74
+ # parse handled default values, including keys
75
+ # with defaults not already managed by handled_keys
76
+ default_result.merge(handled_keys(default_result))
77
77
  end
78
78
 
79
79
  def default_value_inputs
@@ -122,14 +122,21 @@ module Decanter
122
122
  .map { |handler| "#{handler[:name]}_attributes".to_sym }
123
123
 
124
124
  return {} unless unhandled_keys.any?
125
- raise(UnhandledKeysError, "#{self.name} received unhandled keys: #{unhandled_keys.join(', ')}.") if strict_mode
126
- args.select { |key| unhandled_keys.include? key.to_sym }
125
+
126
+ case strict_mode
127
+ when :ignore
128
+ p "#{self.name} ignoring unhandled keys: #{unhandled_keys.join(', ')}."
129
+ {}
130
+ when true
131
+ raise(UnhandledKeysError, "#{self.name} received unhandled keys: #{unhandled_keys.join(', ')}.")
132
+ else
133
+ args.select { |key| unhandled_keys.include? key.to_sym }
134
+ end
127
135
  end
128
136
 
129
137
  def handled_keys(args)
130
138
  arg_keys = args.keys.map(&:to_sym)
131
139
  inputs, assocs = handlers.values.partition { |handler| handler[:type] == :input }
132
-
133
140
  {}.merge(
134
141
  # Inputs
135
142
  inputs.select { |handler| (arg_keys & handler[:name]).any? }
@@ -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
@@ -20,7 +19,6 @@ module Decanter
20
19
  end
21
20
 
22
21
  module ClassMethods
23
-
24
22
  def decant_create(args, **options)
25
23
  self.new(decant(args, options))
26
24
  .save(context: options[:context])
@@ -35,8 +33,8 @@ module Decanter
35
33
  .save!(context: options[:context])
36
34
  end
37
35
 
38
- def decant(args, options={})
39
- if specified_decanter = options[:decanter]
36
+ def decant(args, options = {})
37
+ if (specified_decanter = options[:decanter])
40
38
  Decanter.decanter_from(specified_decanter)
41
39
  else
42
40
  Decanter.decanter_for(self)
@@ -1,3 +1,3 @@
1
1
  module Decanter
2
- VERSION = '3.3.0'.freeze
2
+ VERSION = '3.5.1'.freeze
3
3
  end
@@ -2,12 +2,12 @@ module Rails
2
2
  module Generators
3
3
  class DecanterGenerator < NamedBase
4
4
  source_root File.expand_path('../templates', __FILE__)
5
- check_class_collision :suffix => 'Decanter'
5
+ check_class_collision suffix: 'Decanter'
6
6
  ASSOCIATION_TYPES = [:has_many, :has_one, :belongs_to]
7
7
 
8
- argument :attributes, :type => :array, :default => [], :banner => 'field:type field:type'
8
+ argument :attributes, type: :array, default: [], banner: 'field:type field:type'
9
9
 
10
- class_option :parent, :type => :string, :desc => 'The parent class for the generated decanter'
10
+ class_option :parent, type: :string, desc: 'The parent class for the generated decanter'
11
11
 
12
12
  def create_decanter_file
13
13
  template 'decanter.rb.erb', File.join('app/decanters', class_path, "#{file_name}_decanter.rb")
@@ -2,9 +2,9 @@ module Rails
2
2
  module Generators
3
3
  class ParserGenerator < NamedBase
4
4
  source_root File.expand_path('../templates', __FILE__)
5
- check_class_collision :suffix => 'Parser'
5
+ check_class_collision suffix: 'Parser'
6
6
 
7
- class_option :parent, :type => :string, :desc => 'The parent class for the generated parser'
7
+ class_option :parent, type: :string, desc: 'The parent class for the generated parser'
8
8
 
9
9
  def create_parser_file
10
10
  template 'parser.rb.erb', File.join('lib/decanter/parsers', class_path, "#{file_name}_parser.rb")
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.3.0
4
+ version: 3.5.1
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: 2020-11-03 00:00:00.000000000 Z
12
+ date: 2021-04-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: actionpack
@@ -29,16 +29,16 @@ dependencies:
29
29
  name: activesupport
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
- - - "~>"
32
+ - - ">="
33
33
  - !ruby/object:Gem::Version
34
- version: '5.2'
34
+ version: '0'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
- - - "~>"
39
+ - - ">="
40
40
  - !ruby/object:Gem::Version
41
- version: '5.2'
41
+ version: '0'
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: rails-html-sanitizer
44
44
  requirement: !ruby/object:Gem::Requirement
@@ -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