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
data/lib/stockboy/dsl.rb
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module Stockboy
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# @api private
|
|
5
|
+
#
|
|
6
|
+
class ConfiguratorBlock
|
|
7
|
+
|
|
8
|
+
# Initialize a DSL context around an instance
|
|
9
|
+
#
|
|
10
|
+
def initialize(instance)
|
|
11
|
+
@instance = instance
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Mixin for defining DSL methods
|
|
17
|
+
#
|
|
18
|
+
module DSL
|
|
19
|
+
|
|
20
|
+
# Define ambiguous attr reader/writers for DSL readability
|
|
21
|
+
#
|
|
22
|
+
# @example
|
|
23
|
+
# dsl.some_option = "new value" # => some_option = "new value"
|
|
24
|
+
# dsl.some_option "new value" # => some_option = "new value"
|
|
25
|
+
# dsl.some_option # => some_option
|
|
26
|
+
#
|
|
27
|
+
# @visibility private
|
|
28
|
+
# @scope class
|
|
29
|
+
#
|
|
30
|
+
def dsl_attr(attr, options={})
|
|
31
|
+
if options.fetch(:attr_accessor, true)
|
|
32
|
+
attr_reader attr if options.fetch(:attr_reader, true)
|
|
33
|
+
attr_writer attr if options.fetch(:attr_writer, true)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class_eval <<-___, __FILE__, __LINE__
|
|
37
|
+
class DSL < Stockboy::ConfiguratorBlock
|
|
38
|
+
def #{attr}(*arg)
|
|
39
|
+
if arg.empty?
|
|
40
|
+
@instance.#{attr}
|
|
41
|
+
else
|
|
42
|
+
@instance.#{attr} = arg.first
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
def #{attr}=(arg)
|
|
46
|
+
@instance.#{attr} = arg
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
___
|
|
50
|
+
|
|
51
|
+
if attr_alias = options[:alias]
|
|
52
|
+
alias_method attr_alias, attr
|
|
53
|
+
alias_method :"#{attr_alias}=", :"#{attr}="
|
|
54
|
+
|
|
55
|
+
class_eval <<-___, __FILE__, __LINE__
|
|
56
|
+
class DSL < Stockboy::ConfiguratorBlock
|
|
57
|
+
alias_method :#{attr_alias}, :#{attr}
|
|
58
|
+
alias_method :#{attr_alias}=, :#{attr}=
|
|
59
|
+
end
|
|
60
|
+
___
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
attr
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require 'stockboy/exceptions'
|
|
2
|
+
|
|
3
|
+
module Stockboy
|
|
4
|
+
|
|
5
|
+
# Filters can be any callable object that returns true or false. This
|
|
6
|
+
# abstract class is a helpful way to define a commonly used filter pattern.
|
|
7
|
+
#
|
|
8
|
+
# == Interface
|
|
9
|
+
#
|
|
10
|
+
# Filter subclasses must define a +filter+ method that returns true or false
|
|
11
|
+
# when called with the record context.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# class Bouncer < Stockboy::Filter
|
|
15
|
+
# def initialize(age)
|
|
16
|
+
# @age = age
|
|
17
|
+
# end
|
|
18
|
+
# def filter(input_context, output_context)
|
|
19
|
+
# input_context["RawAge"].empty? or output_context.age < @age
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# Stockboy::Filters.register(:bouncer, Bouncer.new(19))
|
|
24
|
+
# filter :under_age, :bouncer # in job template
|
|
25
|
+
#
|
|
26
|
+
# Stockboy::Filters.register(:check_id, Bouncer)
|
|
27
|
+
# filter :under_age, :bouncer, 19 # in job template
|
|
28
|
+
#
|
|
29
|
+
# @abstract
|
|
30
|
+
#
|
|
31
|
+
class Filter
|
|
32
|
+
|
|
33
|
+
# Return true to capture a filtered record, false to pass it on
|
|
34
|
+
#
|
|
35
|
+
# @param [SourceRecord] raw_context
|
|
36
|
+
# Unmapped source fields with Hash-like access field names (e.g.
|
|
37
|
+
# <tt>input["RawField"]</tt>) or raw values on mapped attributes as
|
|
38
|
+
# methods (e.g. <tt>input.email</tt>)
|
|
39
|
+
# @param [MappedRecord] translated_context
|
|
40
|
+
# Mapped and translated fields with access to attributes
|
|
41
|
+
# as methods (<tt>output.email</tt>)
|
|
42
|
+
# @return [Boolean]
|
|
43
|
+
#
|
|
44
|
+
def call(raw_context, translated_context)
|
|
45
|
+
return !!filter(raw_context, translated_context)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# @abstract
|
|
51
|
+
#
|
|
52
|
+
def filter(raw_context, translated_context)
|
|
53
|
+
raise NoMethodError, "#{self.class}#filter must be implemented"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Stockboy
|
|
2
|
+
|
|
3
|
+
# A hash for executing items in order with callbacks
|
|
4
|
+
#
|
|
5
|
+
class FilterChain < Hash
|
|
6
|
+
|
|
7
|
+
# Initialize a new FilterChain with a hash of filters
|
|
8
|
+
#
|
|
9
|
+
# @param [Hash{Symbol=>Filter}] hash
|
|
10
|
+
#
|
|
11
|
+
def self.new(hash=nil)
|
|
12
|
+
super().replace(hash || {})
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Add filters to the front of the chain
|
|
16
|
+
#
|
|
17
|
+
# @param [Hash{Symbol=>Filter}] hash Filters to add
|
|
18
|
+
#
|
|
19
|
+
def prepend(hash)
|
|
20
|
+
replace hash.merge(self)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Call the reset callback on all filters that respond to it
|
|
24
|
+
#
|
|
25
|
+
# @return [Hash{Symbol=>Array}] Filter keys point to empty arrays
|
|
26
|
+
#
|
|
27
|
+
def reset
|
|
28
|
+
each do |key, filter|
|
|
29
|
+
filter.reset if filter.respond_to? :reset
|
|
30
|
+
end
|
|
31
|
+
keys_to_arrays
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @return [Hash{Symbol=>Array}] Filter keys point to empty arrays
|
|
35
|
+
#
|
|
36
|
+
def keys_to_arrays
|
|
37
|
+
Hash[keys.map { |k| [k, []] }]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'stockboy/filter'
|
|
2
|
+
|
|
3
|
+
module Stockboy::Filters
|
|
4
|
+
|
|
5
|
+
# Very loose matching to pre-screen missing emails.
|
|
6
|
+
#
|
|
7
|
+
# Only checks if there is a potential email-like string in the output value,
|
|
8
|
+
# and does not do any format checking for validity.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# filter = Stockboy::Filters::MissingEmail.new(:addr)
|
|
12
|
+
# model.email = ""
|
|
13
|
+
# filter.call(_, model) # => false
|
|
14
|
+
# model.email = "@"
|
|
15
|
+
# filter.call(_, model) # => true
|
|
16
|
+
#
|
|
17
|
+
class MissingEmail < Stockboy::Filter
|
|
18
|
+
|
|
19
|
+
# Initialize a new filter for a missing email attribute
|
|
20
|
+
#
|
|
21
|
+
# @param [Symbol] attr
|
|
22
|
+
# Name of the email attribute to examine on the mapped output record
|
|
23
|
+
#
|
|
24
|
+
def initialize(attr)
|
|
25
|
+
@attr = attr
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def filter(raw,output)
|
|
31
|
+
value = output.send(@attr)
|
|
32
|
+
return true if value.blank?
|
|
33
|
+
return true unless value =~ /\w@\w/
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/stockboy/job.rb
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
require 'stockboy/configuration'
|
|
2
|
+
require 'stockboy/exceptions'
|
|
3
|
+
require 'stockboy/configurator'
|
|
4
|
+
require 'stockboy/template_file'
|
|
5
|
+
require 'stockboy/filter_chain'
|
|
6
|
+
require 'stockboy/candidate_record'
|
|
7
|
+
|
|
8
|
+
module Stockboy
|
|
9
|
+
|
|
10
|
+
# This class wraps up the main interface for the process of fetching,
|
|
11
|
+
# parsing and sorting data. When used with a predefined template file, you
|
|
12
|
+
# can pass the name of the template to define it. This is the common way
|
|
13
|
+
# to use Stockboy:
|
|
14
|
+
#
|
|
15
|
+
# job = Stockboy::Job.define('my_template')
|
|
16
|
+
# if job.process
|
|
17
|
+
# job.records[:update].each do |r|
|
|
18
|
+
# # ...
|
|
19
|
+
# end
|
|
20
|
+
# job.records[:cancel].each do |r|
|
|
21
|
+
# # ...
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
class Job
|
|
26
|
+
|
|
27
|
+
# Defines the data source for receiving data
|
|
28
|
+
#
|
|
29
|
+
# @return [Provider]
|
|
30
|
+
#
|
|
31
|
+
attr_accessor :provider
|
|
32
|
+
|
|
33
|
+
# Defines the format for parsing received data
|
|
34
|
+
#
|
|
35
|
+
# @return [Reader]
|
|
36
|
+
#
|
|
37
|
+
attr_accessor :reader
|
|
38
|
+
|
|
39
|
+
# Configures the mapping & translation of raw data fields
|
|
40
|
+
#
|
|
41
|
+
# @return [AttributeMap]
|
|
42
|
+
#
|
|
43
|
+
attr_reader :attributes
|
|
44
|
+
|
|
45
|
+
# List of filters for sorting processed records
|
|
46
|
+
#
|
|
47
|
+
# @return [FilterChain]
|
|
48
|
+
#
|
|
49
|
+
# Filters are applied in order, first match will capture the record.
|
|
50
|
+
# Records that don't match any
|
|
51
|
+
#
|
|
52
|
+
attr_reader :filters
|
|
53
|
+
|
|
54
|
+
attr_reader :triggers
|
|
55
|
+
|
|
56
|
+
# Lists of records grouped by filter key
|
|
57
|
+
#
|
|
58
|
+
# @return [Hash{Symbol=>Array}]
|
|
59
|
+
#
|
|
60
|
+
attr_reader :records
|
|
61
|
+
|
|
62
|
+
# List of records not matched by any filter
|
|
63
|
+
#
|
|
64
|
+
# @return [Array<CandidateRecord>]
|
|
65
|
+
#
|
|
66
|
+
attr_reader :unfiltered_records
|
|
67
|
+
|
|
68
|
+
# List of all records, filtered or not
|
|
69
|
+
#
|
|
70
|
+
# @return [Array<CandidateRecord>]
|
|
71
|
+
#
|
|
72
|
+
attr_reader :all_records
|
|
73
|
+
|
|
74
|
+
# Initialize a new job
|
|
75
|
+
#
|
|
76
|
+
# @param [Hash] params
|
|
77
|
+
# @option params [Provider] :provider
|
|
78
|
+
# @option params [Reader] :reader
|
|
79
|
+
# @option params [AttributeMap] :attributes
|
|
80
|
+
# @option params [Array,FilterChain] :filters
|
|
81
|
+
# @yield instance for further configuration or processing
|
|
82
|
+
#
|
|
83
|
+
def initialize(params={}, &block)
|
|
84
|
+
@provider = params[:provider]
|
|
85
|
+
@reader = params[:reader]
|
|
86
|
+
@attributes = params[:attributes]
|
|
87
|
+
@filters = FilterChain.new params[:filters]
|
|
88
|
+
@triggers = Hash.new { |h,k| h[k] = [] }
|
|
89
|
+
@triggers.replace params[:triggers] if params[:triggers]
|
|
90
|
+
yield self if block_given?
|
|
91
|
+
reset
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Instantiate a job configured by DSL template file
|
|
95
|
+
#
|
|
96
|
+
# @param template_name [String] File basename from template load path
|
|
97
|
+
# @yield instance for further configuration or processing
|
|
98
|
+
# @see Configuration#template_load_paths
|
|
99
|
+
#
|
|
100
|
+
def self.define(template_name)
|
|
101
|
+
return nil unless template = TemplateFile.read(template_name)
|
|
102
|
+
job = Configurator.new(template, TemplateFile.find(template_name)).to_job
|
|
103
|
+
yield job if block_given?
|
|
104
|
+
job
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Fetch data and process it into groups of filtered records
|
|
108
|
+
#
|
|
109
|
+
# @return [Boolean] Success or failure
|
|
110
|
+
#
|
|
111
|
+
def process
|
|
112
|
+
reset
|
|
113
|
+
with_query_caching do
|
|
114
|
+
load_records
|
|
115
|
+
yield @records if block_given?
|
|
116
|
+
end
|
|
117
|
+
provider.errors.empty?
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Count of all processed records
|
|
121
|
+
#
|
|
122
|
+
# @!attribute [r] total_records
|
|
123
|
+
# @return [Fixnum]
|
|
124
|
+
#
|
|
125
|
+
def total_records
|
|
126
|
+
@all_records.size
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Counts of processed records grouped by filter key
|
|
130
|
+
#
|
|
131
|
+
# @return [Hash{Symbol=>Fixnum}]
|
|
132
|
+
#
|
|
133
|
+
def record_counts
|
|
134
|
+
@records.reduce(Hash.new) { |a, (k,v)| a[k] = v.size; a }
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def triggers=(new_triggers)
|
|
138
|
+
@triggers.replace new_triggers
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def trigger(key, *args)
|
|
142
|
+
return nil unless triggers.key?(key)
|
|
143
|
+
triggers[key].each do |c|
|
|
144
|
+
c.call(self, *args)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def method_missing(name, *args)
|
|
149
|
+
if triggers.key?(name)
|
|
150
|
+
trigger(name, *args)
|
|
151
|
+
else
|
|
152
|
+
super
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Replace existing filters
|
|
157
|
+
#
|
|
158
|
+
# @param new_filters [Array]
|
|
159
|
+
# @return [Stockboy::FilterChain]
|
|
160
|
+
#
|
|
161
|
+
def filters=(new_filters)
|
|
162
|
+
@filters.replace new_filters
|
|
163
|
+
reset
|
|
164
|
+
@filters
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Replace existing attribute map
|
|
168
|
+
#
|
|
169
|
+
# @param new_attributes [Stockboy::AttributeMap]
|
|
170
|
+
# @return [Stockboy::AttributeMap]
|
|
171
|
+
#
|
|
172
|
+
def attributes=(new_attributes)
|
|
173
|
+
@attributes = new_attributes
|
|
174
|
+
reset
|
|
175
|
+
@attributes
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Has the job been processed successfully?
|
|
179
|
+
#
|
|
180
|
+
# @return [Boolean]
|
|
181
|
+
#
|
|
182
|
+
def processed?
|
|
183
|
+
!!@processed
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Overview of the job configuration; tries to be less noisy by hiding
|
|
187
|
+
# sub-element details.
|
|
188
|
+
#
|
|
189
|
+
# @return [String]
|
|
190
|
+
#
|
|
191
|
+
def inspect
|
|
192
|
+
prov = "provider=#{(Stockboy::Providers.all.key(provider.class) || provider.class.to_s).inspect}"
|
|
193
|
+
read = "reader=#{(Stockboy::Readers.all.key(reader.class) || reader.class.to_s).inspect}"
|
|
194
|
+
attr = "attributes=#{attributes.map(&:to)}"
|
|
195
|
+
filt = "filters=#{filters.keys}"
|
|
196
|
+
cnts = "record_counts=#{record_counts}"
|
|
197
|
+
"#<#{self.class}:#{self.object_id} #{[prov, read, attr, filt, cnts].join(', ')}>"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
private
|
|
201
|
+
|
|
202
|
+
def reset
|
|
203
|
+
@records = filters.reset
|
|
204
|
+
@all_records = []
|
|
205
|
+
@unfiltered_records = []
|
|
206
|
+
@processed = false
|
|
207
|
+
true
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def load_records
|
|
211
|
+
return unless provider.data
|
|
212
|
+
|
|
213
|
+
@all_records = reader.parse(provider.data).map do |row|
|
|
214
|
+
CandidateRecord.new(row, @attributes)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
@all_records.each do |record|
|
|
218
|
+
record_partition(record) << record
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
@processed = true
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def record_partition(record)
|
|
225
|
+
if key = record.partition(filters)
|
|
226
|
+
@records[key]
|
|
227
|
+
else
|
|
228
|
+
@unfiltered_records
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def with_query_caching(&block)
|
|
233
|
+
if defined? ActiveRecord
|
|
234
|
+
ActiveRecord::Base.cache(&block)
|
|
235
|
+
else
|
|
236
|
+
yield
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
end
|
|
241
|
+
end
|