decanter 3.2.0 → 3.4.2

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: bb1e68fce23331df62b4e65bbdb40368a80bf077dd0b7685aa7486be180da948
4
- data.tar.gz: 01b15f9d9c510b58575045fa1a726519a8729db05cf330dfc4f100de57a2b3f0
3
+ metadata.gz: c49220d0b25ab5f09d58aded4f247e71793d1f98129efebe336651c1e58303dd
4
+ data.tar.gz: b28b971df1dba14570abfcb9cb6116e13512afcb3412656be1c169af1ecef69a
5
5
  SHA512:
6
- metadata.gz: c1a36d91c8ba49a573c0b40caf11a45a7f2ea7c8b2891e69d314cee7564f463fe165db76acc9a0e8e77e1f5850e01e06c7f8153ac7dd48e41c1513acabe1e9af
7
- data.tar.gz: c57d191795ed8fe7209b058b526aee19d0441360475448245e57119d56177681e3c0e351be2101ecce37d1ad0e4b3e6f33b5a60af86df253cefe902292851da0
6
+ metadata.gz: 911b7de4eaf39749fbb1029016e00da3d72a7b0f46d828d6c1d9f9f49f945c51ab91bbefc4ef2fd45b1e69e2d488ec07b09358c29a5009d7222122047cf24785
7
+ data.tar.gz: 6acdf1923faec6984bb42884c47afb0574f94b65a12c1275953eb583c61a77b860074b3ef7b03bd157f3fa37e10fea8abf6bd358872fdce3dc614f3d16626323
@@ -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 CHANGED
@@ -1,4 +1,4 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in goaltender.gemspec
3
+ # Specify your gem's dependencies in decanter.gemspec
4
4
  gemspec
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- decanter (3.2.0)
4
+ decanter (3.4.2)
5
5
  actionpack (>= 4.2.10)
6
6
  activesupport
7
7
  rails-html-sanitizer (>= 1.0.4)
@@ -9,82 +9,82 @@ PATH
9
9
  GEM
10
10
  remote: https://rubygems.org/
11
11
  specs:
12
- actionpack (5.1.4)
13
- actionview (= 5.1.4)
14
- activesupport (= 5.1.4)
15
- rack (~> 2.0)
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)
16
16
  rack-test (>= 0.6.3)
17
17
  rails-dom-testing (~> 2.0)
18
18
  rails-html-sanitizer (~> 1.0, >= 1.0.2)
19
- actionview (5.1.4)
20
- activesupport (= 5.1.4)
19
+ actionview (5.2.4.4)
20
+ activesupport (= 5.2.4.4)
21
21
  builder (~> 3.1)
22
22
  erubi (~> 1.4)
23
23
  rails-dom-testing (~> 2.0)
24
24
  rails-html-sanitizer (~> 1.0, >= 1.0.3)
25
- activesupport (5.1.4)
25
+ activesupport (5.2.4.4)
26
26
  concurrent-ruby (~> 1.0, >= 1.0.2)
27
- i18n (~> 0.7)
27
+ i18n (>= 0.7, < 2)
28
28
  minitest (~> 5.1)
29
29
  tzinfo (~> 1.1)
30
- builder (3.2.3)
31
- concurrent-ruby (1.0.5)
32
- crass (1.0.4)
33
- diff-lcs (1.3)
30
+ builder (3.2.4)
31
+ concurrent-ruby (1.1.7)
32
+ crass (1.0.6)
33
+ diff-lcs (1.4.4)
34
34
  docile (1.1.5)
35
35
  dotenv (2.2.1)
36
- erubi (1.7.0)
37
- i18n (0.9.3)
36
+ erubi (1.9.0)
37
+ i18n (1.8.5)
38
38
  concurrent-ruby (~> 1.0)
39
- json (2.1.0)
40
- loofah (2.2.2)
39
+ json (2.3.0)
40
+ loofah (2.7.0)
41
41
  crass (~> 1.0.2)
42
42
  nokogiri (>= 1.5.9)
43
- method_source (0.9.0)
44
- mini_portile2 (2.3.0)
45
- minitest (5.11.3)
46
- nokogiri (1.8.5)
47
- mini_portile2 (~> 2.3.0)
48
- rack (2.0.4)
49
- rack-test (0.8.2)
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
50
  rack (>= 1.0, < 3)
51
51
  rails-dom-testing (2.0.3)
52
52
  activesupport (>= 4.2.0)
53
53
  nokogiri (>= 1.6)
54
- rails-html-sanitizer (1.0.4)
55
- loofah (~> 2.2, >= 2.2.2)
56
- railties (5.1.4)
57
- actionpack (= 5.1.4)
58
- activesupport (= 5.1.4)
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
59
  method_source
60
60
  rake (>= 0.8.7)
61
- thor (>= 0.18.1, < 2.0)
62
- rake (10.5.0)
63
- rspec-core (3.7.1)
64
- rspec-support (~> 3.7.0)
65
- rspec-expectations (3.7.0)
61
+ thor (>= 0.19.0, < 2.0)
62
+ rake (12.3.3)
63
+ rspec-core (3.9.3)
64
+ rspec-support (~> 3.9.3)
65
+ rspec-expectations (3.9.3)
66
66
  diff-lcs (>= 1.2.0, < 2.0)
67
- rspec-support (~> 3.7.0)
68
- rspec-mocks (3.7.0)
67
+ rspec-support (~> 3.9.0)
68
+ rspec-mocks (3.9.1)
69
69
  diff-lcs (>= 1.2.0, < 2.0)
70
- rspec-support (~> 3.7.0)
71
- rspec-rails (3.7.2)
70
+ rspec-support (~> 3.9.0)
71
+ rspec-rails (3.9.1)
72
72
  actionpack (>= 3.0)
73
73
  activesupport (>= 3.0)
74
74
  railties (>= 3.0)
75
- rspec-core (~> 3.7.0)
76
- rspec-expectations (~> 3.7.0)
77
- rspec-mocks (~> 3.7.0)
78
- rspec-support (~> 3.7.0)
79
- rspec-support (3.7.1)
75
+ rspec-core (~> 3.9.0)
76
+ rspec-expectations (~> 3.9.0)
77
+ rspec-mocks (~> 3.9.0)
78
+ rspec-support (~> 3.9.0)
79
+ rspec-support (3.9.4)
80
80
  simplecov (0.15.1)
81
81
  docile (~> 1.1.0)
82
82
  json (>= 1.8, < 3)
83
83
  simplecov-html (~> 0.10.0)
84
84
  simplecov-html (0.10.2)
85
- thor (0.20.0)
85
+ thor (1.0.1)
86
86
  thread_safe (0.3.6)
87
- tzinfo (1.2.5)
87
+ tzinfo (1.2.7)
88
88
  thread_safe (~> 0.1)
89
89
 
90
90
  PLATFORMS
@@ -94,8 +94,8 @@ DEPENDENCIES
94
94
  bundler (~> 1.9)
95
95
  decanter!
96
96
  dotenv
97
- rake (~> 10.0)
98
- rspec-rails
97
+ rake (~> 12.0)
98
+ rspec-rails (~> 3.9)
99
99
  simplecov (~> 0.15.1)
100
100
 
101
101
  BUNDLED WITH
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,37 @@ 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
+
73
105
  ### Nested resources
74
106
 
75
107
  Decanters can declare relationships using `ActiveRecord`-style declarators:
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
33
33
 
34
34
  spec.add_development_dependency 'bundler', '~> 1.9'
35
35
  spec.add_development_dependency 'dotenv'
36
- spec.add_development_dependency 'rake', '~> 10.0'
37
- spec.add_development_dependency 'rspec-rails'
36
+ spec.add_development_dependency 'rake', '~> 12.0'
37
+ spec.add_development_dependency 'rspec-rails', '~> 3.9'
38
38
  spec.add_development_dependency 'simplecov', '~> 0.15.1'
39
39
  end
@@ -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
@@ -62,4 +62,4 @@ require 'decanter/base'
62
62
  require 'decanter/extensions'
63
63
  require 'decanter/exceptions'
64
64
  require 'decanter/parser'
65
- require 'decanter/railtie' if defined?(::Rails)
65
+ require 'decanter/railtie' if defined?(::Rails)
@@ -1,24 +1,24 @@
1
1
  module Decanter
2
2
  module Core
3
3
  DEFAULT_VALUE_KEY = :default_value
4
+ ACTION_CONTROLLER_PARAMETERS_CLASS_NAME = 'ActionController::Parameters'
4
5
 
5
6
  def self.included(base)
6
7
  base.extend(ClassMethods)
7
8
  end
8
9
 
9
10
  module ClassMethods
10
-
11
11
  def input(name, parsers=nil, **options)
12
+ # Convert all input names to symbols to correctly calculate handled vs. unhandled keys
13
+ input_names = [name].flatten.map(&:to_sym)
12
14
 
13
- _name = [name].flatten
14
-
15
- if _name.length > 1 && parsers.blank?
15
+ if input_names.length > 1 && parsers.blank?
16
16
  raise ArgumentError.new("#{self.name} no parser specified for input with multiple values.")
17
17
  end
18
18
 
19
- handlers[_name] = {
20
- key: options.fetch(:key, _name.first),
21
- name: _name,
19
+ handlers[input_names] = {
20
+ key: options.fetch(:key, input_names.first),
21
+ name: input_names,
22
22
  options: options,
23
23
  parsers: parsers,
24
24
  type: :input
@@ -46,7 +46,7 @@ module Decanter
46
46
  end
47
47
 
48
48
  def ignore(*args)
49
- keys_to_ignore.push(*args)
49
+ keys_to_ignore.push(*args).map!(&:to_sym)
50
50
  end
51
51
 
52
52
  def strict(mode)
@@ -57,10 +57,12 @@ module Decanter
57
57
  def decant(args)
58
58
  return handle_empty_args if args.blank?
59
59
  return empty_required_input_error unless required_input_keys_present?(args)
60
- args = args.to_unsafe_h.with_indifferent_access if args.class.name == 'ActionController::Parameters'
60
+
61
+ # Convert all params passed to a decanter to a hash with indifferent access to mitigate accessor ambiguity
62
+ accessible_args = to_indifferent_hash(args)
61
63
  {}.merge( default_keys )
62
- .merge( unhandled_keys(args) )
63
- .merge( handled_keys(args) )
64
+ .merge( unhandled_keys(accessible_args) )
65
+ .merge( handled_keys(accessible_args) )
64
66
  end
65
67
 
66
68
  def default_keys
@@ -120,7 +122,7 @@ module Decanter
120
122
 
121
123
  return {} unless unhandled_keys.any?
122
124
  raise(UnhandledKeysError, "#{self.name} received unhandled keys: #{unhandled_keys.join(', ')}.") if strict_mode
123
- args.select { |key| unhandled_keys.include? key }
125
+ args.select { |key| unhandled_keys.include? key.to_sym }
124
126
  end
125
127
 
126
128
  def handled_keys(args)
@@ -227,6 +229,10 @@ module Decanter
227
229
  value.nil? || value == ""
228
230
  end
229
231
 
232
+ def to_indifferent_hash(args)
233
+ return args.to_unsafe_h if args.class.name == ACTION_CONTROLLER_PARAMETERS_CLASS_NAME
234
+ args.to_h.with_indifferent_access
235
+ end
230
236
  end
231
237
  end
232
238
  end
@@ -20,7 +20,6 @@ module Decanter
20
20
  end
21
21
 
22
22
  module ClassMethods
23
-
24
23
  def decant_create(args, **options)
25
24
  self.new(decant(args, options))
26
25
  .save(context: options[:context])
@@ -36,12 +35,28 @@ module Decanter
36
35
  end
37
36
 
38
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)
39
46
  if specified_decanter = options[:decanter]
40
47
  Decanter.decanter_from(specified_decanter)
41
48
  else
42
49
  Decanter.decanter_for(self)
43
50
  end.decant(args)
44
51
  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
45
60
  end
46
61
 
47
62
  module ActiveRecordExtensions
@@ -1,3 +1,3 @@
1
1
  module Decanter
2
- VERSION = '3.2.0'.freeze
2
+ VERSION = '3.4.2'.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.2.0
4
+ version: 3.4.2
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-10-05 00:00:00.000000000 Z
12
+ date: 2021-01-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: actionpack
@@ -87,28 +87,28 @@ dependencies:
87
87
  requirements:
88
88
  - - "~>"
89
89
  - !ruby/object:Gem::Version
90
- version: '10.0'
90
+ version: '12.0'
91
91
  type: :development
92
92
  prerelease: false
93
93
  version_requirements: !ruby/object:Gem::Requirement
94
94
  requirements:
95
95
  - - "~>"
96
96
  - !ruby/object:Gem::Version
97
- version: '10.0'
97
+ version: '12.0'
98
98
  - !ruby/object:Gem::Dependency
99
99
  name: rspec-rails
100
100
  requirement: !ruby/object:Gem::Requirement
101
101
  requirements:
102
- - - ">="
102
+ - - "~>"
103
103
  - !ruby/object:Gem::Version
104
- version: '0'
104
+ version: '3.9'
105
105
  type: :development
106
106
  prerelease: false
107
107
  version_requirements: !ruby/object:Gem::Requirement
108
108
  requirements:
109
- - - ">="
109
+ - - "~>"
110
110
  - !ruby/object:Gem::Version
111
- version: '0'
111
+ version: '3.9'
112
112
  - !ruby/object:Gem::Dependency
113
113
  name: simplecov
114
114
  requirement: !ruby/object:Gem::Requirement