decanter 0.7.2 → 0.8.0

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