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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +41 -0
- data/lib/decanter/core.rb +15 -9
- data/lib/decanter/parser.rb +22 -5
- data/lib/decanter/parser/core.rb +25 -21
- data/lib/decanter/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 284c077381d45c71768a4e7523843a6970ad386c
|
4
|
+
data.tar.gz: 09fc5570e94021864b353ae4f928aea669e1507c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a058f4fc3a206341742af0e39d62abc663d7a8665b244f2ab29bc8f9df73cd810955fa19502bc8943b32f8662e1dcf254c4ceeac6ce20eed997a9b51a48e9317
|
7
|
+
data.tar.gz: 19442b1a6b19562768fa4a87a125f661199660bac14eebf4c19ef78bfbc64d4fbab11d6f181d4e97ddb8e9db16fb08bcb3de3f28cd95db9ca3386aad99f247ea
|
data/Gemfile.lock
CHANGED
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,
|
10
|
+
def input(name, parsers=nil, **options)
|
11
11
|
|
12
12
|
_name = [name].flatten
|
13
13
|
|
14
|
-
if _name.length > 1 &&
|
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
|
-
|
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[:
|
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,
|
158
|
-
|
159
|
-
|
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
|
data/lib/decanter/parser.rb
CHANGED
@@ -2,8 +2,17 @@ module Decanter
|
|
2
2
|
module Parser
|
3
3
|
|
4
4
|
class << self
|
5
|
-
def
|
6
|
-
|
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
|
-
|
16
|
-
|
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
|
data/lib/decanter/parser/core.rb
CHANGED
@@ -8,38 +8,42 @@ module Decanter
|
|
8
8
|
|
9
9
|
module ClassMethods
|
10
10
|
|
11
|
-
#
|
11
|
+
# Check if allowed, parse if not
|
12
12
|
def parse(name, values, options={})
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
data/lib/decanter/version.rb
CHANGED
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.
|
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-
|
12
|
+
date: 2016-05-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|