decanter 0.7.0 → 0.7.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
  SHA1:
3
- metadata.gz: 20aabb357aad0ae6ac3571bdc051640eee091e0a
4
- data.tar.gz: 7e1015a86d9e56d0f647bd2c561b40795b471a2d
3
+ metadata.gz: a1cea76ec8e01a183736aeb145f1b58cb8f00298
4
+ data.tar.gz: 370986b00b379fc188bc327a76e4411038f0c4b9
5
5
  SHA512:
6
- metadata.gz: ee971b831f97166213985551f11c36f22f6b4eddb89acb8e1559ab0344172ac08a7ddb88cc10ead3a744add6d950d0d127e79ee3129e2e0e4984b62529f53cdb
7
- data.tar.gz: 394c5b2fe99e781fc393b1b27580269a202189261044d57b30a370e1ef2cc2d06c298238726d0a09f2988ca39d29b8649403a4dec3658f811e3e15890a8c7d1e
6
+ metadata.gz: 0e17c3aba3e202f20638f80993d73f9a7773b6890eb46006c470a972a8f7cf7a0ce87fd07713097a40fa48482430dfc3b897a9126e14f0c49297ca1e49fa941a
7
+ data.tar.gz: af705b120764f79bff09350351830f8908b2fcb73cd74463bde2bd66dc898c63673c30fd361ff71fbbe528d512bace6d2eef4d08d6ee954f60780807ce381507
@@ -25,7 +25,7 @@ engines:
25
25
  csslint:
26
26
  enabled: true
27
27
  brakeman:
28
- enabled: true
28
+ enabled: false
29
29
  bundler-audit:
30
30
  enabled: true
31
31
  ratings:
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- decanter (0.6.2)
4
+ decanter (0.7.0)
5
5
  activesupport
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Decanter
2
2
  ===
3
3
 
4
- [![Code Climate](https://codeclimate.com/github/LaunchPadLab/decanter/badges/gpa.svg)](https://codeclimate.com/github/LaunchPadLab/decanter) [![Test Coverage](https://codeclimate.com/github/LaunchPadLab/decanter/badges/coverage.svg)](https://codeclimate.com/github/LaunchPadLab/decanter/coverage)
4
+ [![Code Climate](https://codeclimate.com/github/LaunchPadLab/decanter/badges/gpa.svg?ignore=me)](https://codeclimate.com/github/LaunchPadLab/decanter) [![Test Coverage](https://codeclimate.com/github/LaunchPadLab/decanter/badges/coverage.svg?ignore=me)](https://codeclimate.com/github/LaunchPadLab/decanter/coverage)
5
5
  ---
6
6
 
7
7
 
@@ -2,18 +2,36 @@ require 'active_support/all'
2
2
 
3
3
  module Decanter
4
4
 
5
- @@decanters = {}
5
+ class << self
6
6
 
7
- def self.register(decanter)
8
- @@decanters[decanter.name.demodulize] = decanter
9
- end
7
+ def decanter_for(klass_or_sym)
8
+ case klass_or_sym
9
+ when Class
10
+ klass_or_sym.name
11
+ when Symbol
12
+ klass_or_sym.to_s.singularize.camelize
13
+ else
14
+ raise ArgumentError.new("cannot lookup decanter for #{klass_or_sym} with class #{klass_or_sym.class}")
15
+ end.concat('Decanter').constantize
16
+ end
17
+
18
+ def decanter_from(klass_or_string)
19
+ constant =
20
+ case klass_or_string
21
+ when Class
22
+ klass_or_string
23
+ when String
24
+ klass_or_string.constantize
25
+ else
26
+ raise ArgumentError.new("cannot find decanter from #{klass_or_string} with class #{klass_or_string.class}")
27
+ end
28
+
29
+ unless constant.ancestors.include? Decanter::Base
30
+ raise ArgumentError.new("#{constant.name} is not a decanter")
31
+ end
10
32
 
11
- def self.decanter_for(klass_or_sym)
12
- name = klass_or_sym.is_a?(Class) ?
13
- klass_or_sym.name :
14
- klass_or_sym.to_s.singularize.camelize
15
- full_name = name.include?('Decanter') ? name : "#{name}Decanter"
16
- @@decanters[full_name] || (raise NameError.new("unknown decanter #{name}Decanter"))
33
+ constant
34
+ end
17
35
  end
18
36
 
19
37
  ActiveSupport.run_load_hooks(:decanter, self)
@@ -3,8 +3,5 @@ require 'decanter/core'
3
3
  module Decanter
4
4
  class Base
5
5
  include Core
6
- def self.inherited(subclass)
7
- Decanter.register(subclass)
8
- end
9
6
  end
10
7
  end
@@ -25,22 +25,20 @@ module Decanter
25
25
  end
26
26
 
27
27
  def has_many(assoc, **options)
28
- name = ["#{assoc}_attributes".to_sym]
29
- handlers[name] = {
28
+ handlers[assoc] = {
30
29
  assoc: assoc,
31
- key: options.fetch(:key, name.first),
32
- name: name,
30
+ key: options.fetch(:key, assoc),
31
+ name: assoc,
33
32
  options: options,
34
33
  type: :has_many
35
34
  }
36
35
  end
37
36
 
38
37
  def has_one(assoc, **options)
39
- name = ["#{assoc}_attributes".to_sym]
40
- handlers[name] = {
38
+ handlers[assoc] = {
41
39
  assoc: assoc,
42
- key: options.fetch(:key, name.first),
43
- name: name,
40
+ key: options.fetch(:key, assoc),
41
+ name: assoc,
44
42
  options: options,
45
43
  type: :has_one
46
44
  }
@@ -59,80 +57,118 @@ module Decanter
59
57
 
60
58
  # protected
61
59
 
62
- def unhandled_keys(args)
63
- unhandled_keys = args.keys.map(&:to_sym) - handlers.keys.flatten.uniq
64
-
65
- if unhandled_keys.any?
66
- case strict_mode
67
- when true
68
- p "#{self.name} ignoring unhandled keys: #{unhandled_keys.join(', ')}."
69
- {}
70
- when :with_exception
71
- raise ArgumentError.new("#{self.name} received unhandled keys: #{unhandled_keys.join(', ')}.")
72
- else
73
- args.select { |key| unhandled_keys.include? key }
74
- end
75
- else
60
+ def unhandled_keys(args)
61
+ unhandled_keys = args.keys.map(&:to_sym) -
62
+ handlers.keys.flatten.uniq -
63
+ handlers.values
64
+ .select { |handler| handler[:type] != :input }
65
+ .map { |handler| "#{handler[:name]}_attributes".to_sym }
66
+
67
+ if unhandled_keys.any?
68
+ case strict_mode
69
+ when true
70
+ p "#{self.name} ignoring unhandled keys: #{unhandled_keys.join(', ')}."
76
71
  {}
72
+ when :with_exception
73
+ raise ArgumentError.new("#{self.name} received unhandled keys: #{unhandled_keys.join(', ')}.")
74
+ else
75
+ args.select { |key| unhandled_keys.include? key }
77
76
  end
77
+ else
78
+ {}
78
79
  end
80
+ end
79
81
 
80
- def handled_keys(args)
81
- handlers.values
82
- .select { |handler| (args.keys.map(&:to_sym) & handler[:name]).any? }
83
- .reduce({}) { |memo, handler| memo.merge handle(handler, args) }
84
- end
82
+ def handled_keys(args)
83
+ arg_keys = args.keys.map(&:to_sym)
84
+ inputs, assocs = handlers.values.partition { |handler| handler[:type] == :input }
85
+
86
+ {}.merge(
87
+ # Inputs
88
+ inputs.select { |handler| (arg_keys & handler[:name]).any? }
89
+ .reduce({}) { |memo, handler| memo.merge handle_input(handler, args) }
90
+ ).merge(
91
+ # Associations
92
+ assocs.reduce({}) { |memo, handler| memo.merge handle_association(handler, args) }
93
+ )
94
+ end
85
95
 
86
- def handle(handler, args)
87
- values = args.values_at(*handler[:name])
88
- values = values.length == 1 ? values.first : values
89
- self.send("handle_#{handler[:type]}", handler, values)
90
- end
96
+ def handle(handler, args)
97
+ values = args.values_at(*handler[:name])
98
+ values = values.length == 1 ? values.first : values
99
+ self.send("handle_#{handler[:type]}", handler, values)
100
+ end
101
+
102
+ def handle_input(handler, args)
103
+ values = args.values_at(*handler[:name])
104
+ values = values.length == 1 ? values.first : values
105
+ parse(handler[:key], handler[:parser], values, handler[:options])
106
+ end
91
107
 
92
- def handle_input(handler, values)
93
- parse(handler[:key], handler[:parser], values, handler[:options])
108
+ def handle_association(handler, args)
109
+ assoc_handlers = [
110
+ handler,
111
+ handler.merge({
112
+ key: handler[:options].fetch(:key, "#{handler[:name]}_attributes").to_sym,
113
+ name: "#{handler[:name]}_attributes".to_sym
114
+ })
115
+ ]
116
+
117
+ assoc_handler_names = assoc_handlers.map { |_handler| _handler[:name] }
118
+
119
+ case args.values_at(*assoc_handler_names).compact.length
120
+ when 0
121
+ {}
122
+ when 1
123
+ _handler = assoc_handlers.detect { |_handler| args.has_key?(_handler[:name]) }
124
+ self.send("handle_#{_handler[:type]}", _handler, args[_handler[:name]])
125
+ else
126
+ raise ArgumentError.new("Handler #{handler[:name]} matches multiple keys: #{assoc_handler_names}.")
94
127
  end
128
+ end
95
129
 
96
- def handle_has_many(handler, values)
97
- decanter = decanter_for_handler(handler)
98
- if values.is_a?(Hash)
99
- parsed_values = values.map do |index, input_values|
100
- next if input_values.nil?
101
- decanter.decant(input_values)
102
- end
103
- return { handler[:key] => parsed_values }
104
- else
105
- {
106
- handler[:key] => values.compact.map { |value| decanter.decant(value) }
107
- }
130
+ def handle_has_many(handler, values)
131
+ decanter = decanter_for_handler(handler)
132
+ if values.is_a?(Hash)
133
+ parsed_values = values.map do |index, input_values|
134
+ next if input_values.nil?
135
+ decanter.decant(input_values)
108
136
  end
137
+ return { handler[:key] => parsed_values }
138
+ else
139
+ {
140
+ handler[:key] => values.compact.map { |value| decanter.decant(value) }
141
+ }
109
142
  end
143
+ end
110
144
 
111
- def handle_has_one(handler, values)
112
- {
113
- handler[:key] => decanter_for_handler(handler).decant(values)
114
- }
115
- end
145
+ def handle_has_one(handler, values)
146
+ { handler[:key] => decanter_for_handler(handler).decant(values) }
147
+ end
116
148
 
117
- def decanter_for_handler(handler)
118
- Decanter::decanter_for(handler[:options][:decanter] || handler[:assoc])
149
+ def decanter_for_handler(handler)
150
+ if specified_decanter = handler[:options][:decanter]
151
+ Decanter::decanter_from(specified_decanter)
152
+ else
153
+ Decanter::decanter_for(handler[:assoc])
119
154
  end
155
+ end
120
156
 
121
- def parse(key, parser, values, options)
122
- parser ?
123
- ValueParser.value_parser_for(parser)
124
- .parse(key, values, options)
125
- :
126
- { key => values }
127
- end
157
+ def parse(key, parser, values, options)
158
+ parser ?
159
+ ValueParser.value_parser_for(parser)
160
+ .parse(key, values, options)
161
+ :
162
+ { key => values }
163
+ end
128
164
 
129
- def handlers
130
- @handlers ||= {}
131
- end
165
+ def handlers
166
+ @handlers ||= {}
167
+ end
132
168
 
133
- def strict_mode
134
- @strict_mode ||= {}
135
- end
169
+ def strict_mode
170
+ @strict_mode ||= {}
171
+ end
136
172
  end
137
173
  end
138
174
  end
@@ -36,8 +36,11 @@ module Decanter
36
36
  end
37
37
 
38
38
  def decant(args, options={})
39
- options.fetch(:decanter, Decanter.decanter_for(self))
40
- .decant(args)
39
+ if specified_decanter = options[:decanter]
40
+ Decanter.decanter_from(specified_decanter)
41
+ else
42
+ Decanter.decanter_for(self)
43
+ end.decant(args)
41
44
  end
42
45
  end
43
46
 
@@ -11,7 +11,6 @@ class Decanter::Railtie < Rails::Railtie
11
11
 
12
12
  config.to_prepare do
13
13
  Dir[
14
- File.expand_path(Rails.root.join("app/decanters/*")),
15
14
  File.expand_path(Rails.root.join("lib/decanter/parsers/*"))
16
15
  ].each { |file| require file }
17
16
  end
@@ -1,3 +1,3 @@
1
1
  module Decanter
2
- VERSION = '0.7.0'
2
+ VERSION = '0.7.1'
3
3
  end
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: 0.7.0
4
+ version: 0.7.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: 2016-02-26 00:00:00.000000000 Z
12
+ date: 2016-03-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport