stockboy 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|