decanter 3.3.0 → 3.5.1

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: 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