stockboy 0.5.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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +5 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +24 -0
- data/Gemfile +12 -0
- data/Guardfile +10 -0
- data/LICENSE +21 -0
- data/README.md +293 -0
- data/Rakefile +30 -0
- data/lib/stockboy.rb +80 -0
- data/lib/stockboy/attribute.rb +11 -0
- data/lib/stockboy/attribute_map.rb +74 -0
- data/lib/stockboy/candidate_record.rb +130 -0
- data/lib/stockboy/configuration.rb +62 -0
- data/lib/stockboy/configurator.rb +176 -0
- data/lib/stockboy/dsl.rb +68 -0
- data/lib/stockboy/exceptions.rb +3 -0
- data/lib/stockboy/filter.rb +58 -0
- data/lib/stockboy/filter_chain.rb +41 -0
- data/lib/stockboy/filters.rb +11 -0
- data/lib/stockboy/filters/missing_email.rb +37 -0
- data/lib/stockboy/job.rb +241 -0
- data/lib/stockboy/mapped_record.rb +59 -0
- data/lib/stockboy/provider.rb +238 -0
- data/lib/stockboy/providers.rb +11 -0
- data/lib/stockboy/providers/file.rb +135 -0
- data/lib/stockboy/providers/ftp.rb +205 -0
- data/lib/stockboy/providers/http.rb +123 -0
- data/lib/stockboy/providers/imap.rb +290 -0
- data/lib/stockboy/providers/soap.rb +120 -0
- data/lib/stockboy/railtie.rb +28 -0
- data/lib/stockboy/reader.rb +59 -0
- data/lib/stockboy/readers.rb +11 -0
- data/lib/stockboy/readers/csv.rb +115 -0
- data/lib/stockboy/readers/fixed_width.rb +121 -0
- data/lib/stockboy/readers/spreadsheet.rb +144 -0
- data/lib/stockboy/readers/xml.rb +155 -0
- data/lib/stockboy/registry.rb +42 -0
- data/lib/stockboy/source_record.rb +43 -0
- data/lib/stockboy/string_pool.rb +35 -0
- data/lib/stockboy/template_file.rb +44 -0
- data/lib/stockboy/translations.rb +70 -0
- data/lib/stockboy/translations/boolean.rb +58 -0
- data/lib/stockboy/translations/date.rb +41 -0
- data/lib/stockboy/translations/decimal.rb +33 -0
- data/lib/stockboy/translations/default_empty_string.rb +38 -0
- data/lib/stockboy/translations/default_false.rb +41 -0
- data/lib/stockboy/translations/default_nil.rb +38 -0
- data/lib/stockboy/translations/default_true.rb +41 -0
- data/lib/stockboy/translations/default_zero.rb +41 -0
- data/lib/stockboy/translations/integer.rb +33 -0
- data/lib/stockboy/translations/string.rb +33 -0
- data/lib/stockboy/translations/time.rb +41 -0
- data/lib/stockboy/translations/uk_date.rb +51 -0
- data/lib/stockboy/translations/us_date.rb +51 -0
- data/lib/stockboy/translator.rb +66 -0
- data/lib/stockboy/version.rb +3 -0
- data/spec/fixtures/.gitkeep +0 -0
- data/spec/fixtures/files/a_garbage.csv +1 -0
- data/spec/fixtures/files/test_data-20120101.csv +1 -0
- data/spec/fixtures/files/test_data-20120202.csv +1 -0
- data/spec/fixtures/files/z_garbage.csv +1 -0
- data/spec/fixtures/jobs/test_job.rb +1 -0
- data/spec/fixtures/soap/get_list/fault.xml +8 -0
- data/spec/fixtures/soap/get_list/success.xml +18 -0
- data/spec/fixtures/spreadsheets/test_data.xls +0 -0
- data/spec/fixtures/spreadsheets/test_row_options.xls +0 -0
- data/spec/fixtures/xml/body.xml +14 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/stockboy/attribute_map_spec.rb +59 -0
- data/spec/stockboy/attribute_spec.rb +11 -0
- data/spec/stockboy/candidate_record_spec.rb +150 -0
- data/spec/stockboy/configuration_spec.rb +28 -0
- data/spec/stockboy/configurator_spec.rb +127 -0
- data/spec/stockboy/filter_chain_spec.rb +40 -0
- data/spec/stockboy/filter_spec.rb +41 -0
- data/spec/stockboy/filters/missing_email_spec.rb +26 -0
- data/spec/stockboy/filters_spec.rb +38 -0
- data/spec/stockboy/job_spec.rb +238 -0
- data/spec/stockboy/mapped_record_spec.rb +30 -0
- data/spec/stockboy/provider_spec.rb +34 -0
- data/spec/stockboy/providers/file_spec.rb +116 -0
- data/spec/stockboy/providers/ftp_spec.rb +143 -0
- data/spec/stockboy/providers/http_spec.rb +94 -0
- data/spec/stockboy/providers/imap_spec.rb +76 -0
- data/spec/stockboy/providers/soap_spec.rb +107 -0
- data/spec/stockboy/providers_spec.rb +38 -0
- data/spec/stockboy/readers/csv_spec.rb +68 -0
- data/spec/stockboy/readers/fixed_width_spec.rb +52 -0
- data/spec/stockboy/readers/spreadsheet_spec.rb +121 -0
- data/spec/stockboy/readers/xml_spec.rb +94 -0
- data/spec/stockboy/readers_spec.rb +30 -0
- data/spec/stockboy/source_record_spec.rb +19 -0
- data/spec/stockboy/template_file_spec.rb +30 -0
- data/spec/stockboy/translations/boolean_spec.rb +48 -0
- data/spec/stockboy/translations/date_spec.rb +38 -0
- data/spec/stockboy/translations/decimal_spec.rb +23 -0
- data/spec/stockboy/translations/default_empty_string_spec.rb +32 -0
- data/spec/stockboy/translations/default_false_spec.rb +25 -0
- data/spec/stockboy/translations/default_nil_spec.rb +32 -0
- data/spec/stockboy/translations/default_true_spec.rb +25 -0
- data/spec/stockboy/translations/default_zero_spec.rb +32 -0
- data/spec/stockboy/translations/integer_spec.rb +22 -0
- data/spec/stockboy/translations/string_spec.rb +22 -0
- data/spec/stockboy/translations/time_spec.rb +27 -0
- data/spec/stockboy/translations/uk_date_spec.rb +37 -0
- data/spec/stockboy/translations/us_date_spec.rb +37 -0
- data/spec/stockboy/translations_spec.rb +55 -0
- data/spec/stockboy/translator_spec.rb +27 -0
- data/stockboy.gemspec +32 -0
- metadata +305 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
require 'stockboy/reader'
|
|
2
|
+
require 'stockboy/string_pool'
|
|
3
|
+
|
|
4
|
+
module Stockboy::Readers
|
|
5
|
+
|
|
6
|
+
# Extract data from XML
|
|
7
|
+
#
|
|
8
|
+
# This works great with SOAP, probably not fully-featured yet for various XML
|
|
9
|
+
# formats. The SOAP provider returns a hash, because it takes care of
|
|
10
|
+
# extracting the envelope body already, so this reader supports options for
|
|
11
|
+
# reading elements from a nested hash too.
|
|
12
|
+
#
|
|
13
|
+
# Backed by the Nori gem from Savon, see nori for full options.
|
|
14
|
+
#
|
|
15
|
+
class XML < Stockboy::Reader
|
|
16
|
+
include Stockboy::StringPool
|
|
17
|
+
|
|
18
|
+
# Override source encoding
|
|
19
|
+
#
|
|
20
|
+
# @!attribute [rw] encoding
|
|
21
|
+
# @return [String]
|
|
22
|
+
#
|
|
23
|
+
dsl_attr :encoding
|
|
24
|
+
|
|
25
|
+
# Element nesting to traverse, the last one should represent the record
|
|
26
|
+
# instances that contain tags for each attribute.
|
|
27
|
+
#
|
|
28
|
+
# @!attribute [rw] elements
|
|
29
|
+
# @return [Array]
|
|
30
|
+
# @example
|
|
31
|
+
# elements ["allItemsResponse", "itemList", "recordItem"]
|
|
32
|
+
#
|
|
33
|
+
dsl_attr :elements, attr_accessor: false
|
|
34
|
+
|
|
35
|
+
# Removes namespace prefixes from tag names, default true.
|
|
36
|
+
#
|
|
37
|
+
# @!attribute [rw] strip_namespaces
|
|
38
|
+
# @return [Boolean]
|
|
39
|
+
#
|
|
40
|
+
dsl_attr :strip_namespaces, attr_accessor: false
|
|
41
|
+
|
|
42
|
+
# Change tag formatting, e.g. underscore if it happens to match your actual
|
|
43
|
+
# record attributes
|
|
44
|
+
#
|
|
45
|
+
# @!attribute [rw] convert_tags_to
|
|
46
|
+
# @return [Proc]
|
|
47
|
+
# @example
|
|
48
|
+
# convert_tags_to ->(tag) { tag.underscore }
|
|
49
|
+
#
|
|
50
|
+
dsl_attr :convert_tags_to, attr_accessor: false
|
|
51
|
+
|
|
52
|
+
# Detects input tag types and tries to extract dates, times, etc. from the data.
|
|
53
|
+
# Normally this is handled by the attribute map.
|
|
54
|
+
#
|
|
55
|
+
# @!attribute [rw] advanced_typecasting
|
|
56
|
+
# @return [Boolean]
|
|
57
|
+
#
|
|
58
|
+
dsl_attr :advanced_typecasting, attr_accessor: false
|
|
59
|
+
|
|
60
|
+
# Defaults to Nokogiri. Why would you change it?
|
|
61
|
+
#
|
|
62
|
+
# @!attribute [rw] parser
|
|
63
|
+
# @return [Symbol]
|
|
64
|
+
#
|
|
65
|
+
dsl_attr :parser, attr_accessor: false
|
|
66
|
+
|
|
67
|
+
[:strip_namespaces, :convert_tags_to, :advanced_typecasting, :parser].each do |opt|
|
|
68
|
+
define_method(opt) { @xml_options[opt] }
|
|
69
|
+
define_method(:"#{opt}=") { |value| @xml_options[opt] = value }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def elements
|
|
73
|
+
convert_tags_to ? @elements.map(&convert_tags_to) : @elements
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def elements=(schema)
|
|
77
|
+
return @elements = [] unless schema
|
|
78
|
+
raise(ArgumentError, "expected an array of XML tag strings") unless schema.is_a? Array
|
|
79
|
+
@elements = schema.map(&:to_s)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @!endgroup
|
|
83
|
+
|
|
84
|
+
# Initialize a new XML reader
|
|
85
|
+
#
|
|
86
|
+
def initialize(opts={}, &block)
|
|
87
|
+
super
|
|
88
|
+
self.elements = opts.delete(:elements)
|
|
89
|
+
@xml_options = opts
|
|
90
|
+
DSL.new(self).instance_eval(&block) if block_given?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# XML options passed to the underlying Nori instance
|
|
94
|
+
#
|
|
95
|
+
# @!attribute [r] options
|
|
96
|
+
# @return [Hash]
|
|
97
|
+
#
|
|
98
|
+
def options
|
|
99
|
+
@xml_options
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def parse(data)
|
|
103
|
+
hash = if data.is_a? Hash
|
|
104
|
+
data
|
|
105
|
+
else
|
|
106
|
+
if data.respond_to? :to_xml
|
|
107
|
+
data.to_xml("UTF-8")
|
|
108
|
+
nori.parse(data)
|
|
109
|
+
elsif data.respond_to? :to_hash
|
|
110
|
+
data.to_hash
|
|
111
|
+
else
|
|
112
|
+
data.encode!("UTF-8", encoding) if encoding
|
|
113
|
+
nori.parse(data)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
with_string_pool do
|
|
118
|
+
remap_keys hash
|
|
119
|
+
extract hash
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
private
|
|
124
|
+
|
|
125
|
+
def nori
|
|
126
|
+
@nori ||= Nori.new(options)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def extract(hash)
|
|
130
|
+
result = elements.inject hash do |memo, key|
|
|
131
|
+
return [] if memo[key].nil?
|
|
132
|
+
memo[key]
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
result = [result] unless result.is_a? Array
|
|
136
|
+
result.compact!
|
|
137
|
+
result
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def remap_keys(node)
|
|
141
|
+
mapper = convert_tags_to || ->(tag) { tag }
|
|
142
|
+
case node
|
|
143
|
+
when Hash
|
|
144
|
+
node.keys.each do |k|
|
|
145
|
+
tag = string_pool(mapper.call(k))
|
|
146
|
+
node[tag] = remap_keys(node.delete(k))
|
|
147
|
+
end
|
|
148
|
+
when Array
|
|
149
|
+
node.each { |value| remap_keys(value) }
|
|
150
|
+
end
|
|
151
|
+
node
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Stockboy
|
|
2
|
+
|
|
3
|
+
# Holds a collection of registered classes for convenient reference by
|
|
4
|
+
# symbolic name
|
|
5
|
+
#
|
|
6
|
+
module Registry
|
|
7
|
+
|
|
8
|
+
def self.extended(base)
|
|
9
|
+
base.class_eval do
|
|
10
|
+
@registry = {}
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Register a class under a convenient symbolic name
|
|
15
|
+
#
|
|
16
|
+
# @param [Symbol] key Symbolic name of the class
|
|
17
|
+
# @param [Class] provider Class to be returned when requested
|
|
18
|
+
#
|
|
19
|
+
def register(key, provider)
|
|
20
|
+
@registry[key] = provider
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Look up a class and return it by symbolic name
|
|
24
|
+
#
|
|
25
|
+
# @param [Symbol] key
|
|
26
|
+
# @return [Class]
|
|
27
|
+
#
|
|
28
|
+
def find(key)
|
|
29
|
+
@registry[key]
|
|
30
|
+
end
|
|
31
|
+
alias_method :[], :find
|
|
32
|
+
|
|
33
|
+
# List all named classes in the registry
|
|
34
|
+
#
|
|
35
|
+
# @return [Hash]
|
|
36
|
+
#
|
|
37
|
+
def all
|
|
38
|
+
@registry
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require 'stockboy/mapped_record'
|
|
2
|
+
|
|
3
|
+
module Stockboy
|
|
4
|
+
|
|
5
|
+
# This represents the raw "input" side of a {CandidateRecord}
|
|
6
|
+
#
|
|
7
|
+
# It provides access to the original field values before mapping or
|
|
8
|
+
# translation as hash keys.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# input = SourceRecord.new(
|
|
12
|
+
# {check_in: "2012-12-12"},
|
|
13
|
+
# {"RawCheckIn" => "2012-12-12"})
|
|
14
|
+
#
|
|
15
|
+
# input["RawCheckIn"] # => "2012-12-12"
|
|
16
|
+
# input.check_in # => "2012-12-12"
|
|
17
|
+
#
|
|
18
|
+
class SourceRecord < MappedRecord
|
|
19
|
+
|
|
20
|
+
# Initialize a new instance
|
|
21
|
+
#
|
|
22
|
+
# @param [Hash{Symbol=>Object}] mapped_fields
|
|
23
|
+
# Represents the raw values mapped to the final attribute names
|
|
24
|
+
# @param [Hash] data_fields
|
|
25
|
+
# The raw input fields with original key values
|
|
26
|
+
#
|
|
27
|
+
def initialize(mapped_fields, data_fields)
|
|
28
|
+
@data_fields = data_fields
|
|
29
|
+
super(mapped_fields)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Access a raw field value by the original input field name
|
|
33
|
+
#
|
|
34
|
+
# @param [String] key
|
|
35
|
+
#
|
|
36
|
+
def [](key)
|
|
37
|
+
key = key.to_s if key.is_a? Symbol
|
|
38
|
+
@data_fields[key]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Stockboy
|
|
2
|
+
|
|
3
|
+
# Holds frozen strings for shared lookup between different object instances
|
|
4
|
+
#
|
|
5
|
+
# @visibility private
|
|
6
|
+
#
|
|
7
|
+
module StringPool
|
|
8
|
+
|
|
9
|
+
# Pass a block to yield a new string pool context around a group of
|
|
10
|
+
# actions that should share the same string key instances
|
|
11
|
+
#
|
|
12
|
+
# @yield
|
|
13
|
+
#
|
|
14
|
+
def with_string_pool
|
|
15
|
+
@string_pool = []
|
|
16
|
+
result = yield
|
|
17
|
+
@string_pool = []
|
|
18
|
+
result
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Look up duplicate strings and return the shared frozen string
|
|
22
|
+
#
|
|
23
|
+
# @return [String]
|
|
24
|
+
#
|
|
25
|
+
def string_pool(name)
|
|
26
|
+
if i = @string_pool.index(name)
|
|
27
|
+
@string_pool[i]
|
|
28
|
+
else
|
|
29
|
+
@string_pool << name.freeze
|
|
30
|
+
name
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require 'stockboy/configuration'
|
|
2
|
+
|
|
3
|
+
module Stockboy
|
|
4
|
+
|
|
5
|
+
# Find and read template files from the configured load paths
|
|
6
|
+
#
|
|
7
|
+
module TemplateFile
|
|
8
|
+
|
|
9
|
+
# Read template file contents for defining a new job
|
|
10
|
+
#
|
|
11
|
+
# @param [String] template_name
|
|
12
|
+
# The file basename of a predefined template
|
|
13
|
+
# @return [String] Job template DSL or nil if nothing is found
|
|
14
|
+
#
|
|
15
|
+
def self.read(template_name)
|
|
16
|
+
return template_name.read if template_name.is_a? File
|
|
17
|
+
return unless path = find(template_name)
|
|
18
|
+
|
|
19
|
+
File.read(path)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Find a named DSL template from configuration.template_load_paths
|
|
23
|
+
#
|
|
24
|
+
# @param [String] filename Template basename to be searched from load paths
|
|
25
|
+
# @return [String] The full path to the first matched filename if found
|
|
26
|
+
#
|
|
27
|
+
def self.find(filename)
|
|
28
|
+
sources = template_file_paths(filename)
|
|
29
|
+
Dir.glob(sources).first
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Potential locations for finding a template file
|
|
33
|
+
#
|
|
34
|
+
# @param [String] filename Template basename
|
|
35
|
+
# @return [Array] filename on each possible load path
|
|
36
|
+
#
|
|
37
|
+
def self.template_file_paths(filename)
|
|
38
|
+
filename = "#{filename}.rb" unless filename =~ /\.rb$/
|
|
39
|
+
load_paths = Array(Stockboy.configuration.template_load_paths)
|
|
40
|
+
load_paths.map { |d| File.join(d, filename) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require 'stockboy/exceptions'
|
|
2
|
+
require 'stockboy/translator'
|
|
3
|
+
|
|
4
|
+
module Stockboy
|
|
5
|
+
|
|
6
|
+
# Registry of available {Translator} classes for lookup by symbolic name in the
|
|
7
|
+
# job template DSL.
|
|
8
|
+
#
|
|
9
|
+
module Translations
|
|
10
|
+
|
|
11
|
+
@registry ||= {}
|
|
12
|
+
|
|
13
|
+
# Register a translator under a convenient symbolic name
|
|
14
|
+
#
|
|
15
|
+
# @param [Symbol] name
|
|
16
|
+
# Symbolic name of the class
|
|
17
|
+
# @param [Translator, #call] callable
|
|
18
|
+
# Translator class or any callable object
|
|
19
|
+
#
|
|
20
|
+
def self.register(name, callable)
|
|
21
|
+
if callable.respond_to?(:call) or callable < Stockboy::Translator
|
|
22
|
+
@registry[name.to_sym] = callable
|
|
23
|
+
else
|
|
24
|
+
raise ArgumentError, "Registered translators must be callable"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Calls a named translator for the raw value
|
|
29
|
+
#
|
|
30
|
+
# @param [Symbol, Translator, #call] func_name
|
|
31
|
+
# Symbol representing a registered translator, or an actual translator
|
|
32
|
+
# @param [SourceRecord, MappedRecord, Hash, String] context
|
|
33
|
+
# Collection of fields or the raw value to which the translation is applied
|
|
34
|
+
#
|
|
35
|
+
def self.translate(func_name, context)
|
|
36
|
+
translator_for(:value, func_name).call(context)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Prepare a translator for a given attribute
|
|
40
|
+
#
|
|
41
|
+
# @param [Symbol] attr
|
|
42
|
+
# Name of the mapped record attribute to address for translation
|
|
43
|
+
# @param [Symbol, #call] lookup
|
|
44
|
+
# Symbolic translator name or callable object
|
|
45
|
+
# @return [Translator] instance
|
|
46
|
+
#
|
|
47
|
+
def self.translator_for(attr, lookup)
|
|
48
|
+
if lookup.respond_to?(:call)
|
|
49
|
+
lookup
|
|
50
|
+
elsif tr = self[lookup]
|
|
51
|
+
tr.is_a?(Class) && tr < Stockboy::Translator ? tr.new(attr) : tr
|
|
52
|
+
else
|
|
53
|
+
->(context) { context.public_send attr } # no-op
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Look up a translation and return it by symbolic name
|
|
58
|
+
#
|
|
59
|
+
# @param [Symbol] func_name
|
|
60
|
+
# @return [Translator]
|
|
61
|
+
#
|
|
62
|
+
def self.find(func_name)
|
|
63
|
+
@registry[func_name]
|
|
64
|
+
end
|
|
65
|
+
class << self
|
|
66
|
+
alias_method :[], :find
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require 'stockboy/translator'
|
|
2
|
+
|
|
3
|
+
module Stockboy::Translations
|
|
4
|
+
|
|
5
|
+
# Convert common false-like and true-like values to proper boolean +true+ or
|
|
6
|
+
# +false+.
|
|
7
|
+
#
|
|
8
|
+
# Returns nil for indeterminate values. This should be chained with a
|
|
9
|
+
# default value translator like [DefaultFalse] or [DefaultTrue].
|
|
10
|
+
#
|
|
11
|
+
# == Job template DSL
|
|
12
|
+
#
|
|
13
|
+
# Registered as +:boolean+. Use with:
|
|
14
|
+
#
|
|
15
|
+
# attributes do
|
|
16
|
+
# active as: :boolean
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @example
|
|
20
|
+
# bool = Stockboy::Translator::Boolean.new
|
|
21
|
+
#
|
|
22
|
+
# record.active = 't'
|
|
23
|
+
# bool.translate(record, :active) # => true
|
|
24
|
+
#
|
|
25
|
+
# record.active = 'f'
|
|
26
|
+
# bool.translate(record, :active) # => false
|
|
27
|
+
#
|
|
28
|
+
# record.active = '1'
|
|
29
|
+
# bool.translate(record, :active) # => true
|
|
30
|
+
#
|
|
31
|
+
# record.active = '0'
|
|
32
|
+
# bool.translate(record, :active) # => false
|
|
33
|
+
#
|
|
34
|
+
# record.active = 'y'
|
|
35
|
+
# bool.translate(record, :active) # => true
|
|
36
|
+
#
|
|
37
|
+
# record.active = 'n'
|
|
38
|
+
# bool.translate(record, :active) # => false
|
|
39
|
+
#
|
|
40
|
+
# record.active = '?'
|
|
41
|
+
# bool.translate(record, :active) # => nil
|
|
42
|
+
#
|
|
43
|
+
class Boolean < Stockboy::Translator
|
|
44
|
+
TRUTHY_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'y', 'Y', 'yes', 'YES']
|
|
45
|
+
FALSY_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'n', 'N', 'no', 'NO']
|
|
46
|
+
|
|
47
|
+
# @return [Boolean]
|
|
48
|
+
#
|
|
49
|
+
def translate(context)
|
|
50
|
+
value = field_value(context, field_key)
|
|
51
|
+
|
|
52
|
+
return true if TRUTHY_VALUES.include?(value)
|
|
53
|
+
return false if FALSY_VALUES.include?(value)
|
|
54
|
+
return nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
end
|
|
58
|
+
end
|