decanter 0.7.2 → 0.8.0

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: fcecd146883e6b44658dcee94a7271f552c695cf
4
- data.tar.gz: 1362e8a5b04500354b60f7530916c487fed7f6c6
3
+ metadata.gz: 284c077381d45c71768a4e7523843a6970ad386c
4
+ data.tar.gz: 09fc5570e94021864b353ae4f928aea669e1507c
5
5
  SHA512:
6
- metadata.gz: f95eee6b059ac8f0ec596fe74889dfeae1e30392a86d76a9159c07f895b5016300068f945d5e49d30bd9feace6257b28d3ab9f6ff29ec105fc923ff14a147ccf
7
- data.tar.gz: bcc79760fca3ebfb1fc1d4ce4aa4ae04993d0acff6422be01e0a40a7de4c7556d7269d636fff739a8e90f022d0873c0a83cad0b3fd51ec4931adbda8ec4fe843
6
+ metadata.gz: a058f4fc3a206341742af0e39d62abc663d7a8665b244f2ab29bc8f9df73cd810955fa19502bc8943b32f8662e1dcf254c4ceeac6ce20eed997a9b51a48e9317
7
+ data.tar.gz: 19442b1a6b19562768fa4a87a125f661199660bac14eebf4c19ef78bfbc64d4fbab11d6f181d4e97ddb8e9db16fb08bcb3de3f28cd95db9ca3386aad99f247ea
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- decanter (0.7.1)
4
+ decanter (0.7.2)
5
5
  activesupport
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -378,6 +378,45 @@ class SquashDateParser < Decanter::Parser::ValueParser
378
378
  end
379
379
  ```
380
380
 
381
+ Chaining Parsers
382
+ ---
383
+
384
+ Parsers are composable! Suppose you want a parser that takes an incoming percentage like "50.3%" and converts it into a float for your database like .503. You could implement this with:
385
+
386
+ ```ruby
387
+ class PercentParser < ValueParser
388
+ REGEX = /(\d|[.])/
389
+
390
+ parser do |val, options|
391
+ my_float = val.scan(REGEX).join.try(:to_f)
392
+ my_float / 100 if my_float
393
+ end
394
+ end
395
+ ```
396
+
397
+ This works, but it duplicates logic that already exists in `FloatParser`. Instead, you can specify a parser that should always run before your parsing logic, then you can assume that your parser receives a float:
398
+
399
+ ```ruby
400
+ class SmartPercentParser < ValueParser
401
+
402
+ pre :float
403
+
404
+ parser do |val, options|
405
+ val / 100
406
+ end
407
+ end
408
+ ```
409
+
410
+ If a preparser returns nil or an empty string, subsequent parsers will not be called, just like normal!
411
+
412
+ This can also be achieved by providing multiple parsers in your decanter:
413
+
414
+ ```ruby
415
+ class SomeDecanter < Decanter::Base
416
+ input :some_percent, [:float, :percent]
417
+ end
418
+ ```
419
+
381
420
  No Need for Strong Params
382
421
  ---
383
422
 
@@ -403,6 +442,8 @@ class TripDecanter < Decanter::Base
403
442
  end
404
443
  ```
405
444
 
445
+ In addition, if you provide the option `:required` for an input in your decanter, an exception will be thrown if the parameters is nil or an empty string.
446
+
406
447
  Configuration
407
448
  ---
408
449
 
data/lib/decanter/core.rb CHANGED
@@ -7,11 +7,11 @@ module Decanter
7
7
 
8
8
  module ClassMethods
9
9
 
10
- def input(name, parser=nil, **options)
10
+ def input(name, parsers=nil, **options)
11
11
 
12
12
  _name = [name].flatten
13
13
 
14
- if _name.length > 1 && parser.blank?
14
+ if _name.length > 1 && parsers.blank?
15
15
  raise ArgumentError.new("#{self.name} no parser specified for input with multiple values.")
16
16
  end
17
17
 
@@ -19,7 +19,7 @@ module Decanter
19
19
  key: options.fetch(:key, _name.first),
20
20
  name: _name,
21
21
  options: options,
22
- parser: parser,
22
+ parsers: parsers,
23
23
  type: :input
24
24
  }
25
25
  end
@@ -102,7 +102,7 @@ module Decanter
102
102
  def handle_input(handler, args)
103
103
  values = args.values_at(*handler[:name])
104
104
  values = values.length == 1 ? values.first : values
105
- parse(handler[:key], handler[:parser], values, handler[:options])
105
+ parse(handler[:key], handler[:parsers], values, handler[:options])
106
106
  end
107
107
 
108
108
  def handle_association(handler, args)
@@ -154,12 +154,18 @@ module Decanter
154
154
  end
155
155
  end
156
156
 
157
- def parse(key, parser, values, options)
158
- parser ?
159
- Parser.parser_for(parser)
160
- .parse(key, values, options)
161
- :
157
+ def parse(key, parsers, values, options)
158
+ case
159
+ when !parsers
162
160
  { key => values }
161
+ when options[:required] == true && Array.wrap(values).all? { |value| value.nil? || value == "" }
162
+ raise ArgumentError.new("No value for required argument: #{key}")
163
+ else
164
+ Parser.parsers_for(parsers)
165
+ .reduce({key => values}) do |vals_hash, parser|
166
+ vals_hash.keys.reduce({}) { |acc, k| acc.merge(parser.parse(k, vals_hash[k], options)) }
167
+ end
168
+ end
163
169
  end
164
170
 
165
171
  def handlers
@@ -2,8 +2,17 @@ module Decanter
2
2
  module Parser
3
3
 
4
4
  class << self
5
- def parser_for(klass_or_sym)
6
- parser_str = case klass_or_sym
5
+ def parsers_for(klass_or_syms)
6
+ Array.wrap(klass_or_syms)
7
+ .map { |klass_or_sym| klass_or_sym_to_str(klass_or_sym) }
8
+ .map { |parser_str| parser_constantize(parser_str) }
9
+ .map { |parser| expand(parser) }
10
+ .flatten
11
+ end
12
+
13
+ # convert from a class or symbol to a string and concat 'Parser'
14
+ def klass_or_sym_to_str(klass_or_sym)
15
+ case klass_or_sym
7
16
  when Class
8
17
  klass_or_sym.name
9
18
  when Symbol
@@ -11,10 +20,18 @@ module Decanter
11
20
  else
12
21
  raise ArgumentError.new("cannot lookup parser for #{klass_or_sym} with class #{klass_or_sym.class}")
13
22
  end.concat('Parser')
23
+ end
24
+
25
+ # convert from a string to a constant
26
+ def parser_constantize(parser_str)
27
+ parser_str.safe_constantize ||
28
+ "Decanter::Parser::".concat(parser_str).safe_constantize ||
29
+ raise(NameError.new("cannot find parser #{parser_str}"))
30
+ end
14
31
 
15
- parser = parser_str.safe_constantize || "Decanter::Parser::".concat(parser_str).safe_constantize
16
- raise NameError.new("cannot find parser #{parser_str}") unless parser
17
- parser
32
+ # expand to include preparsers
33
+ def expand(parser)
34
+ Parser.parsers_for(parser.preparsers).push(parser)
18
35
  end
19
36
  end
20
37
  end
@@ -8,38 +8,42 @@ module Decanter
8
8
 
9
9
  module ClassMethods
10
10
 
11
- # Meant to be overriden and called by child parser
11
+ # Check if allowed, parse if not
12
12
  def parse(name, values, options={})
13
-
14
- value_ary = values.is_a?(Array) ? values : [values]
15
-
16
- # want to treat 'false' as an actual value
17
- if value_ary.all? { |value| value.nil? || value == "" }
18
- if options[:required]
19
- raise ArgumentError.new("No value for required argument: #{name}")
20
- else
21
- return { name => nil }
22
- end
23
- end
24
-
25
- if @allowed && value_ary.all? { |value| @allowed.any? { |allowed| value.is_a? allowed } }
26
- return { name => values }
13
+ case
14
+ when allowed?(values)
15
+ { name => values }
16
+ else
17
+ _parse(name, values, options)
27
18
  end
28
-
29
- unless @parser
30
- raise ArgumentError.new("No parser for argument: #{name} with types: #{value_ary.map(&:class).join(', ')}")
31
- end
32
-
33
- _parse(name, values, options)
34
19
  end
35
20
 
21
+ # Define parser
36
22
  def parser(&block)
37
23
  @parser = block
38
24
  end
39
25
 
26
+ # Set allowed classes
40
27
  def allow(*args)
41
28
  @allowed = args
42
29
  end
30
+
31
+ # Set preparsers
32
+ def pre(*parsers)
33
+ @pre = parsers
34
+ end
35
+
36
+ # Get prepareer
37
+ def preparsers
38
+ @pre || []
39
+ end
40
+
41
+ # Check for allowed classes
42
+ def allowed?(values)
43
+ @allowed && Array.wrap(values).all? do |value|
44
+ @allowed.any? { |allowed| value.is_a? allowed }
45
+ end
46
+ end
43
47
  end
44
48
  end
45
49
  end
@@ -1,3 +1,3 @@
1
1
  module Decanter
2
- VERSION = '0.7.2'
2
+ VERSION = '0.8.0'
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.2
4
+ version: 0.8.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: 2016-04-26 00:00:00.000000000 Z
12
+ date: 2016-05-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport