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,11 @@
|
|
|
1
|
+
module Stockboy
|
|
2
|
+
|
|
3
|
+
# Struct-like value object for holding mapping & translation details from
|
|
4
|
+
# input data fields
|
|
5
|
+
#
|
|
6
|
+
class Attribute < Struct.new(:to, :from, :translators)
|
|
7
|
+
def inspect
|
|
8
|
+
"#<Stockboy::Attribute to=#{to.inspect}, from=#{from.inspect}, translators=#{translators}>"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
require 'stockboy/attribute'
|
|
2
|
+
require 'stockboy/translations'
|
|
3
|
+
|
|
4
|
+
module Stockboy
|
|
5
|
+
|
|
6
|
+
# Table of attributes for finding corresponding field/attribute translations
|
|
7
|
+
#
|
|
8
|
+
class AttributeMap
|
|
9
|
+
|
|
10
|
+
include Enumerable
|
|
11
|
+
|
|
12
|
+
# @visibility private
|
|
13
|
+
#
|
|
14
|
+
class DSL
|
|
15
|
+
def initialize(instance)
|
|
16
|
+
@instance = instance
|
|
17
|
+
@map = @instance.instance_variable_get(:@map)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def method_missing(attr, *args)
|
|
21
|
+
opts = args.first || {}
|
|
22
|
+
to = attr.to_sym
|
|
23
|
+
from = opts.fetch(:from, attr)
|
|
24
|
+
from = from.to_s.freeze if from.is_a? Symbol
|
|
25
|
+
translators = Array(opts[:as]).map { |t| Translations.translator_for(to, t) }
|
|
26
|
+
@map[attr] = Attribute.new(to, from, translators)
|
|
27
|
+
define_attribute_method(attr)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def define_attribute_method(attr)
|
|
31
|
+
(class << @instance; self end).send(:define_method, attr) { @map[attr] }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Initialize a new attribute map
|
|
36
|
+
#
|
|
37
|
+
def initialize(rows={}, &block)
|
|
38
|
+
@map = rows
|
|
39
|
+
@unmapped = Hash.new
|
|
40
|
+
if block_given?
|
|
41
|
+
DSL.new(self).instance_eval(&block)
|
|
42
|
+
end
|
|
43
|
+
freeze
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Retrieve an attribute by symbolic name
|
|
47
|
+
#
|
|
48
|
+
# @param [Symbol] key
|
|
49
|
+
# @return [Attribute]
|
|
50
|
+
#
|
|
51
|
+
def [](key)
|
|
52
|
+
@map[key]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Fetch the attribute corresponding to the source field name
|
|
56
|
+
#
|
|
57
|
+
# @param [String] key
|
|
58
|
+
# @return [Attribute]
|
|
59
|
+
#
|
|
60
|
+
def attribute_from(key)
|
|
61
|
+
find { |a| a.from == key } or @unmapped[key] ||= Attribute.new(nil, key, nil)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Enumerate over attributes
|
|
65
|
+
#
|
|
66
|
+
# @return [Enumerator]
|
|
67
|
+
# @yieldparam [Attribute]
|
|
68
|
+
#
|
|
69
|
+
def each(*args, &block)
|
|
70
|
+
@map.values.each(*args, &block)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
require 'stockboy/attribute_map'
|
|
2
|
+
require 'stockboy/mapped_record'
|
|
3
|
+
require 'stockboy/source_record'
|
|
4
|
+
require 'stockboy/translations'
|
|
5
|
+
|
|
6
|
+
module Stockboy
|
|
7
|
+
|
|
8
|
+
# Joins the raw data values to an attribute mapping to allow comparison of
|
|
9
|
+
# input/output values, conversion, and filtering
|
|
10
|
+
#
|
|
11
|
+
class CandidateRecord
|
|
12
|
+
|
|
13
|
+
# Initialize a new candidate record
|
|
14
|
+
#
|
|
15
|
+
# @param [Hash] attrs Raw key-values from source data
|
|
16
|
+
# @param [AttributeMap] map Mapping and translations
|
|
17
|
+
#
|
|
18
|
+
def initialize(attrs, map)
|
|
19
|
+
@map = map
|
|
20
|
+
@table = use_frozen_keys(attrs, map)
|
|
21
|
+
@tr_table = Hash.new
|
|
22
|
+
freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Convert the mapped output to a hash
|
|
26
|
+
#
|
|
27
|
+
# @return [Hash]
|
|
28
|
+
#
|
|
29
|
+
def to_hash
|
|
30
|
+
Hash.new.tap do |out|
|
|
31
|
+
@map.each { |col| out[col.to] = translate(col) }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
alias_method :attributes, :to_hash
|
|
35
|
+
|
|
36
|
+
# Return the original values mapped to attribute keys
|
|
37
|
+
#
|
|
38
|
+
# @return [Hash]
|
|
39
|
+
#
|
|
40
|
+
def raw_hash
|
|
41
|
+
Hash.new.tap do |out|
|
|
42
|
+
@map.each { |col| out[col.to] = @table[col.from] }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
alias_method :raw_attributes, :raw_hash
|
|
46
|
+
|
|
47
|
+
# Wrap the mapped attributes in a new ActiveModel or ActiveRecord object
|
|
48
|
+
#
|
|
49
|
+
# @param [Class] model ActiveModel class
|
|
50
|
+
# @return [Class] ActiveModel class
|
|
51
|
+
#
|
|
52
|
+
def to_model(model)
|
|
53
|
+
model.new(attributes)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Find the filter key that captures this record
|
|
57
|
+
#
|
|
58
|
+
# @param [FilterChain] filters List of filters to apply
|
|
59
|
+
# @return [Symbol] Name of the matched filter
|
|
60
|
+
#
|
|
61
|
+
def partition(filters={})
|
|
62
|
+
input, output = self.input, self.output
|
|
63
|
+
filters.each_pair do |filter_key, f|
|
|
64
|
+
if f.call(input, output)
|
|
65
|
+
return filter_key
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Data structure representing the record's raw input values
|
|
72
|
+
#
|
|
73
|
+
# Values can be accessed like hash keys, or attribute names that correspond
|
|
74
|
+
# to a +:from+ attribute mapping option
|
|
75
|
+
#
|
|
76
|
+
# @return [SourceRecord]
|
|
77
|
+
# @example
|
|
78
|
+
# input = candidate.input
|
|
79
|
+
# input["RawEmail"] # => "ME@EXAMPLE.COM "
|
|
80
|
+
# input.email # => "ME@EXAMPLE.COM "
|
|
81
|
+
#
|
|
82
|
+
def input
|
|
83
|
+
SourceRecord.new(self.raw_hash, @table)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Data structure representing the record's mapped & translated output values
|
|
87
|
+
#
|
|
88
|
+
# @return [MappedRecord]
|
|
89
|
+
# @example
|
|
90
|
+
# input = candidate.output
|
|
91
|
+
# output.email # => "me@example.com"
|
|
92
|
+
#
|
|
93
|
+
def output
|
|
94
|
+
MappedRecord.new(self.to_hash)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def translate(col)
|
|
100
|
+
return sanitize(@table[col.from]) if col.translators.empty?
|
|
101
|
+
return @tr_table[col.to] if @tr_table.has_key? col.to
|
|
102
|
+
fields = self.raw_hash.dup
|
|
103
|
+
translated = col.translators.inject(input) do |m,t|
|
|
104
|
+
begin
|
|
105
|
+
new_value = t.call(m)
|
|
106
|
+
rescue
|
|
107
|
+
fields[col.to] = nil
|
|
108
|
+
break SourceRecord.new(fields, @table)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
fields[col.to] = new_value
|
|
112
|
+
SourceRecord.new(fields, @table)
|
|
113
|
+
end
|
|
114
|
+
@tr_table[col.to] = translated.public_send(col.to)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def sanitize(value)
|
|
118
|
+
value.is_a?(String) ? value.to_s : value
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def use_frozen_keys(attrs, map)
|
|
122
|
+
attrs.reduce(Hash.new) do |new_hash, (field, value)|
|
|
123
|
+
key = map.attribute_from(field).from
|
|
124
|
+
new_hash[key] = value
|
|
125
|
+
new_hash
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Stockboy
|
|
2
|
+
|
|
3
|
+
# Global Stockboy configuration options
|
|
4
|
+
#
|
|
5
|
+
class Configuration
|
|
6
|
+
|
|
7
|
+
# Directories where Stockboy job template files can be found.
|
|
8
|
+
#
|
|
9
|
+
# Needs to be configured with your own paths if running standalone.
|
|
10
|
+
# When running with Rails, includes +config/stockboy_jobs+ by default.
|
|
11
|
+
#
|
|
12
|
+
# @return [Array<String>]
|
|
13
|
+
#
|
|
14
|
+
attr_accessor :template_load_paths
|
|
15
|
+
|
|
16
|
+
# Path for storing tempfiles during processing
|
|
17
|
+
#
|
|
18
|
+
# @return [String]
|
|
19
|
+
#
|
|
20
|
+
attr_accessor :tmp_dir
|
|
21
|
+
|
|
22
|
+
# Default logger
|
|
23
|
+
#
|
|
24
|
+
# @return [Logger]
|
|
25
|
+
#
|
|
26
|
+
attr_accessor :logger
|
|
27
|
+
|
|
28
|
+
# Initialize a set of global configuration options
|
|
29
|
+
#
|
|
30
|
+
# @yield self for configuration
|
|
31
|
+
#
|
|
32
|
+
def initialize
|
|
33
|
+
@template_load_paths = []
|
|
34
|
+
@logger = Logger.new(STDOUT)
|
|
35
|
+
@tmp_dir = Dir.tmpdir
|
|
36
|
+
yield self if block_given?
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class << self
|
|
41
|
+
|
|
42
|
+
# Stockboy configuration block
|
|
43
|
+
#
|
|
44
|
+
# @example
|
|
45
|
+
# Stockboy.configure do |config|
|
|
46
|
+
# config.template_load_paths << "config/my_templates"
|
|
47
|
+
# end
|
|
48
|
+
#
|
|
49
|
+
# @scope class
|
|
50
|
+
# @yield self for configuration
|
|
51
|
+
# @return [Configuration]
|
|
52
|
+
#
|
|
53
|
+
def configure
|
|
54
|
+
@configuration ||= Configuration.new
|
|
55
|
+
yield @configuration if block_given?
|
|
56
|
+
@configuration
|
|
57
|
+
end
|
|
58
|
+
alias_method :configuration, :configure
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
require 'stockboy/job'
|
|
2
|
+
require 'stockboy/providers'
|
|
3
|
+
require 'stockboy/readers'
|
|
4
|
+
require 'stockboy/filters'
|
|
5
|
+
require 'stockboy/attribute_map'
|
|
6
|
+
|
|
7
|
+
module Stockboy
|
|
8
|
+
|
|
9
|
+
# Context for evaluating DSL templates and capturing job options for
|
|
10
|
+
# initializing a job.
|
|
11
|
+
#
|
|
12
|
+
# Wraps up the DSL methods called in job templates and handles the construction
|
|
13
|
+
# of the job's +provider+, +reader+, +attributes+, and +filters+.
|
|
14
|
+
#
|
|
15
|
+
class Configurator
|
|
16
|
+
|
|
17
|
+
# Captured job configuration options
|
|
18
|
+
#
|
|
19
|
+
# @return [Hash]
|
|
20
|
+
#
|
|
21
|
+
attr_reader :config
|
|
22
|
+
|
|
23
|
+
# Evaluate DSL and capture configuration for building a job
|
|
24
|
+
#
|
|
25
|
+
# @overload new(dsl, file=__FILE__)
|
|
26
|
+
# Evaluate DSL from a string
|
|
27
|
+
# @param [String] dsl job template language for evaluation
|
|
28
|
+
# @param [String] file path to original file for reporting errors
|
|
29
|
+
# @overload new(&block)
|
|
30
|
+
# Evaluate DSL in a block
|
|
31
|
+
#
|
|
32
|
+
def initialize(dsl='', file=__FILE__, &block)
|
|
33
|
+
@config = {}
|
|
34
|
+
@config[:triggers] = Hash.new { |hash, key| hash[key] = [] }
|
|
35
|
+
@config[:filters] = {}
|
|
36
|
+
if block_given?
|
|
37
|
+
instance_eval(&block)
|
|
38
|
+
else
|
|
39
|
+
instance_eval(dsl, file)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# DSL method for configuring the provider
|
|
44
|
+
#
|
|
45
|
+
# The optional block is evaluated in the provider's own DSL context.
|
|
46
|
+
#
|
|
47
|
+
# @param [Symbol, Class, Provider] provider_class
|
|
48
|
+
# The registered symbol name for the provider, or actual provider
|
|
49
|
+
# @param [Hash] opts
|
|
50
|
+
# Provider-specific options passed to the provider initializer
|
|
51
|
+
#
|
|
52
|
+
# @example
|
|
53
|
+
# provider :file, file_dir: "/downloads/@client" do
|
|
54
|
+
# file_name "example.csv"
|
|
55
|
+
# end
|
|
56
|
+
#
|
|
57
|
+
# @return [Provider]
|
|
58
|
+
#
|
|
59
|
+
def provider(provider_class, opts={}, &block)
|
|
60
|
+
raise ArgumentError unless provider_class
|
|
61
|
+
|
|
62
|
+
@config[:provider] = case provider_class
|
|
63
|
+
when Symbol
|
|
64
|
+
Providers.find(provider_class).new(opts, &block)
|
|
65
|
+
when Class
|
|
66
|
+
provider_class.new(opts, &block)
|
|
67
|
+
else
|
|
68
|
+
provider_class
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
alias_method :connection, :provider
|
|
72
|
+
|
|
73
|
+
# DSL method for configuring the reader
|
|
74
|
+
#
|
|
75
|
+
# @param [Symbol, Class, Reader] reader_class
|
|
76
|
+
# The registered symbol name for the reader, or actual reader
|
|
77
|
+
# @param [Hash] opts
|
|
78
|
+
# Provider-specific options passed to the provider initializer
|
|
79
|
+
#
|
|
80
|
+
# @example
|
|
81
|
+
# reader :csv do
|
|
82
|
+
# col_sep "|"
|
|
83
|
+
# end
|
|
84
|
+
#
|
|
85
|
+
# @return [Reader]
|
|
86
|
+
#
|
|
87
|
+
def reader(reader_class, opts={}, &block)
|
|
88
|
+
raise ArgumentError unless reader_class
|
|
89
|
+
|
|
90
|
+
@config[:reader] = case reader_class
|
|
91
|
+
when Symbol
|
|
92
|
+
Readers.find(reader_class).new(opts, &block)
|
|
93
|
+
when Class
|
|
94
|
+
reader_class.new(opts, &block)
|
|
95
|
+
else
|
|
96
|
+
reader_class
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
alias_method :format, :reader
|
|
100
|
+
|
|
101
|
+
# DSL method for configuring the attribute map in a block
|
|
102
|
+
#
|
|
103
|
+
# @example
|
|
104
|
+
# attributes do
|
|
105
|
+
# first_name as: ->(raw){ raw["FullName"].split(" ").first }
|
|
106
|
+
# email from: "RawEmail", as: [:string]
|
|
107
|
+
# check_in from: "RawCheckIn", as: [:date]
|
|
108
|
+
# end
|
|
109
|
+
#
|
|
110
|
+
def attributes(&block)
|
|
111
|
+
raise ArgumentError unless block_given?
|
|
112
|
+
|
|
113
|
+
@config[:attributes] = AttributeMap.new(&block)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# DSL method to add a filter to the filter chain
|
|
117
|
+
#
|
|
118
|
+
# * Must be called with either a callable argument (proc) or a block.
|
|
119
|
+
# * Must be called in the order that filters should be applied.
|
|
120
|
+
#
|
|
121
|
+
# @example
|
|
122
|
+
# filter :missing_email do |raw, out|
|
|
123
|
+
# raw["RawEmail"].empty?
|
|
124
|
+
# end
|
|
125
|
+
# filter :past_due do |raw, out|
|
|
126
|
+
# out.check_in < Date.today
|
|
127
|
+
# end
|
|
128
|
+
# filter :under_age, :check_id
|
|
129
|
+
# filter :update, proc{ true } # capture all remaining items
|
|
130
|
+
#
|
|
131
|
+
def filter(key, callable=nil, *args, &block)
|
|
132
|
+
raise ArgumentError unless key
|
|
133
|
+
if callable.is_a?(Symbol)
|
|
134
|
+
callable = Filters.find(callable)
|
|
135
|
+
callable = callable.new(*args) if callable.is_a? Class
|
|
136
|
+
end
|
|
137
|
+
raise ArgumentError unless callable.respond_to?(:call) ^ block_given?
|
|
138
|
+
|
|
139
|
+
@config[:filters][key] = block || callable
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# DSL method to register a trigger to notify the job of an event
|
|
143
|
+
#
|
|
144
|
+
# Useful for adding generic control over the job's resources from your app.
|
|
145
|
+
# For example, if you need to record stats or clean up data after your
|
|
146
|
+
# application has successfully processed the records, these actions can be
|
|
147
|
+
# defined within the context of each job template.
|
|
148
|
+
#
|
|
149
|
+
# @param [Symbol] key Name of the trigger
|
|
150
|
+
# @param [Trigger, Proc, #call] trigger_class
|
|
151
|
+
#
|
|
152
|
+
# @example
|
|
153
|
+
# trigger :cleanup do |job, *args|
|
|
154
|
+
# job.provider.delete_data
|
|
155
|
+
# end
|
|
156
|
+
#
|
|
157
|
+
# # elsewhere:
|
|
158
|
+
# if MyProjects.find(123).import_records(job.records[:valid])
|
|
159
|
+
# job.cleanup
|
|
160
|
+
# end
|
|
161
|
+
#
|
|
162
|
+
def on(key, &block)
|
|
163
|
+
raise(ArgumentError, "no block given") unless block_given?
|
|
164
|
+
@config[:triggers][key] << block
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Initialize a new job with the captured options
|
|
168
|
+
#
|
|
169
|
+
# @return [Job]
|
|
170
|
+
#
|
|
171
|
+
def to_job
|
|
172
|
+
Job.new(@config)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
end
|
|
176
|
+
end
|