processor 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.coveralls.yml +1 -0
- data/.gitignore +1 -0
- data/README.md +68 -41
- data/example/migrator.rb +2 -0
- data/lib/processor/data/array_processor.rb +4 -0
- data/lib/processor/data/null_processor.rb +7 -10
- data/lib/processor/environment.rb +3 -0
- data/lib/processor/event_processor.rb +25 -0
- data/lib/processor/logger_messages.rb +56 -0
- data/lib/processor/messenger.rb +16 -3
- data/lib/processor/observer/logger.rb +43 -34
- data/lib/processor/observer/null_observer.rb +8 -3
- data/lib/processor/process_runner/threads.rb +3 -8
- data/lib/processor/runner.rb +8 -23
- data/lib/processor/subroutine/counter.rb +25 -0
- data/lib/processor/subroutine/name.rb +28 -0
- data/lib/processor/subroutine/recursion.rb +29 -0
- data/lib/processor/thread.rb +5 -1
- data/lib/processor/version.rb +1 -1
- data/lib/processor.rb +2 -1
- data/processor.gemspec +6 -4
- data/spec/processor/data/array_processor_spec.rb +1 -2
- data/spec/processor/data/null_processor_spec.rb +27 -2
- data/spec/processor/event_processor_spec.rb +58 -0
- data/spec/processor/logger_messages_spec.rb +69 -0
- data/spec/processor/messenger_spec.rb +15 -0
- data/spec/processor/observer/logger_spec.rb +47 -16
- data/spec/processor/observer/null_observer_spec.rb +33 -0
- data/spec/processor/process_runner/specs.rb +12 -23
- data/spec/processor/process_runner/threads_spec.rb +1 -3
- data/spec/processor/runner_spec.rb +28 -54
- data/spec/processor/subroutine/counter_spec.rb +44 -0
- data/spec/processor/subroutine/name_spec.rb +23 -0
- data/spec/processor/subroutine/recursion_spec.rb +23 -0
- data/spec/processor/thread_spec.rb +1 -1
- data/spec/spec_helper_lite.rb +7 -2
- data/spec/support/dummy_file +0 -0
- metadata +47 -12
- data/lib/processor/data/solr_processor.rb +0 -23
- data/lib/processor/events_registrator.rb +0 -16
- data/spec/processor/events_registrator_spec.rb +0 -15
data/.coveralls.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
service_name: travis-ci
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,15 +1,31 @@
|
|
1
1
|
Processor
|
2
2
|
==========
|
3
3
|
[![Build Status](https://travis-ci.org/AlexParamonov/processor.png?branch=master)](http://travis-ci.org/AlexParamonov/processor)
|
4
|
-
[![Gemnasium Build Status](https://gemnasium.com/AlexParamonov/processor.png)](http://gemnasium.com/AlexParamonov/processor)
|
4
|
+
[![Gemnasium Build Status](https://gemnasium.com/AlexParamonov/processor.png)](http://gemnasium.com/AlexParamonov/processor)
|
5
|
+
[![Coverage Status](https://coveralls.io/repos/AlexParamonov/processor/badge.png?branch=master)](https://coveralls.io/r/AlexParamonov/processor?branch=master)
|
5
6
|
|
6
|
-
Processor could execute any `DataProcessor` you specify and log entire
|
7
|
-
process using any number of loggers you need.
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
Processor is a tool that helps to iterate over collection and
|
9
|
+
perform complex actions on a result. It is extremely useful in data
|
10
|
+
migrations, report generation, etc.
|
11
11
|
|
12
|
-
|
12
|
+
Collection could be iteratively fetched by parts but for processor
|
13
|
+
it will looks like an endless collection. There are a lot such tiny
|
14
|
+
goodness that makes usage of processor pleasant. Need logging,
|
15
|
+
exception processing, post/pre processing a result - no problems, all
|
16
|
+
included and easily extended.
|
17
|
+
|
18
|
+
Use the processor to DRY your migrations, report and stop mess with
|
19
|
+
logging and post processing.
|
20
|
+
|
21
|
+
Did I mentioned you can run in threads as easy as say
|
22
|
+
`processor.run_in_threads 10`?
|
23
|
+
|
24
|
+
Processor could execute any `DataProcessor` you specify and log
|
25
|
+
entire process using any number of loggers you need. You may add own
|
26
|
+
observers for monitoring background tasks on even send an email to
|
27
|
+
business with generated report. Processor provide customisation for
|
28
|
+
almost every part of it.
|
13
29
|
|
14
30
|
|
15
31
|
Contents
|
@@ -21,6 +37,7 @@ Contents
|
|
21
37
|
1. Run modes
|
22
38
|
1. Processor Thread
|
23
39
|
1. Observers
|
40
|
+
1. Contacts
|
24
41
|
1. Compatibility
|
25
42
|
1. Contributing
|
26
43
|
1. Copyright
|
@@ -51,20 +68,29 @@ Usage
|
|
51
68
|
------------
|
52
69
|
|
53
70
|
### Data processors
|
54
|
-
|
55
|
-
This processor should implement in general 2 methods:
|
71
|
+
Working with a data is responsibility of a `DataProcessor`.
|
56
72
|
|
57
|
-
|
58
|
-
|
73
|
+
`DataProcessor` should obtain records to process by its `records`
|
74
|
+
method and process a record by `process` method. If some post/pre
|
75
|
+
action is needed, it could be performed inside `start` and `finish`
|
76
|
+
methods. In case of exceptions there are `error(exception)` and
|
77
|
+
`record_error(record, exception)` methods. `error` method is called if
|
78
|
+
unprocessed errors happened during processing and `record_error` if
|
79
|
+
processing current record raised. `finalize` method will run in any
|
80
|
+
case allowing you to gracefully finalize processing.
|
59
81
|
|
60
|
-
|
61
|
-
|
62
|
-
NullProcessor to get default behavior out of the box.
|
82
|
+
To add new `DataProcessor` it is recommended to inherit from
|
83
|
+
`NullProcessor` and implement methods that are needed only.
|
63
84
|
|
64
|
-
|
85
|
+
`Processor` provides several data processors:
|
65
86
|
|
66
|
-
|
87
|
+
1. NullProcessor [[code](https://github.com/AlexParamonov/processor/blob/master/lib/processor/data/null_processor.rb), [specs](https://github.com/AlexParamonov/processor/blob/master/spec/processor/data/null_processor_spec.rb)]
|
88
|
+
1. ArrayProcessor [[code](https://github.com/AlexParamonov/processor/blob/master/lib/processor/data/array_processor.rb), [specs](https://github.com/AlexParamonov/processor/blob/master/spec/processor/data/array_processor_spec.rb)]
|
89
|
+
1. BatchProcessor [[code](https://github.com/AlexParamonov/processor/blob/master/lib/processor/data/batch_processor.rb), [specs](https://github.com/AlexParamonov/processor/blob/master/spec/processor/data/batch_processor_spec.rb)]
|
90
|
+
1. CsvProcessor
|
91
|
+
1. SolrPagesProcessor
|
67
92
|
|
93
|
+
The last two are more as example, your probably would change them.
|
68
94
|
|
69
95
|
#### ArrayProcessor
|
70
96
|
The simplest one: `process` and `records` methods should be implemented.
|
@@ -73,13 +99,12 @@ The simplest one: `process` and `records` methods should be implemented.
|
|
73
99
|
#### BatchProcessor
|
74
100
|
Allows to fetch records by batches of defined size.
|
75
101
|
|
76
|
-
It is based on `query` method that
|
77
|
-
database.
|
102
|
+
It is based on `query` method that is supposed to run a query method on
|
103
|
+
a database.
|
78
104
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
`data/solr_processor.rb` for example.
|
105
|
+
Recommended to override `fetch_batch` method to get real reason to use
|
106
|
+
batch processing. `fetch_batch` could be `query.page(next_page)` for
|
107
|
+
example. See `data/solr_pages_processor.rb`.
|
83
108
|
|
84
109
|
|
85
110
|
#### Other
|
@@ -94,7 +119,7 @@ Currently 2 run modes are supported:
|
|
94
119
|
It runs `process` one by one for each found record returned by
|
95
120
|
`records` method.
|
96
121
|
|
97
|
-
|
122
|
+
Call it using a `Processor::Thread`:
|
98
123
|
``` ruby
|
99
124
|
Processor::Thread.new(migration).run_successive
|
100
125
|
```
|
@@ -109,19 +134,20 @@ constructor:
|
|
109
134
|
Processor::ProcessRunner::Threads.new 5
|
110
135
|
```
|
111
136
|
|
112
|
-
|
137
|
+
Call it using a `Processor::Thread`:
|
113
138
|
``` ruby
|
114
139
|
Processor::Thread.new(migration).run_in_threads 5
|
115
140
|
```
|
116
141
|
|
117
142
|
|
118
143
|
### Observers
|
119
|
-
Processor support unlimited number of observers
|
144
|
+
Processor support unlimited number of observers that are watching
|
145
|
+
processing.
|
120
146
|
|
121
|
-
|
122
|
-
file
|
123
|
-
|
124
|
-
|
147
|
+
They could monitor `Data Processor`s and output to logs, console
|
148
|
+
or file. Or they can show a progress bar on the console. Or pack a
|
149
|
+
generated report to archive and send it by email to the business on
|
150
|
+
success or notify developers on failure.
|
125
151
|
|
126
152
|
|
127
153
|
This observers should respond to `update` method. But if you inherit
|
@@ -134,7 +160,7 @@ Read below section Processor Thread to see how to use observers in runner.
|
|
134
160
|
|
135
161
|
### Processor Thread
|
136
162
|
`Processor::Thread` is a Facade pattern. It simplifies access to all
|
137
|
-
Processor classes and
|
163
|
+
Processor classes and provides __stable__ interface.
|
138
164
|
|
139
165
|
Creating a new Thread:
|
140
166
|
``` ruby
|
@@ -149,20 +175,13 @@ Processor::Thread.new data_processor, observer1, observer2, ...
|
|
149
175
|
Instance have a `run_as` method that accepts a block:
|
150
176
|
``` ruby
|
151
177
|
thread = Processor::Thread.new @migration
|
152
|
-
thread.run_as do |processor
|
178
|
+
thread.run_as do |processor|
|
153
179
|
processor.records.each do |record|
|
154
180
|
processor.process record
|
155
181
|
end
|
156
182
|
end
|
157
183
|
```
|
158
184
|
|
159
|
-
Block could accept next arguments: `processor`, `events`,
|
160
|
-
`recursion_preventer` method. Last one could be called to prevent
|
161
|
-
recurtion:
|
162
|
-
``` ruby
|
163
|
-
recursion_preventer.call
|
164
|
-
```
|
165
|
-
|
166
185
|
Instance have a `run_successive` method:
|
167
186
|
``` ruby
|
168
187
|
data_processor = UserLocationMigration.new
|
@@ -180,7 +199,7 @@ thread.run_in_threads 10
|
|
180
199
|
See `spec/processor/thread_spec.rb` and `spec/example_spec.rb` and
|
181
200
|
`example` directory for other usage examples.
|
182
201
|
|
183
|
-
It is
|
202
|
+
It is recommended to wrap Processor::Thread by classes named like:
|
184
203
|
``` ruby
|
185
204
|
WeeklyReport
|
186
205
|
TaxonomyMigration
|
@@ -191,11 +210,11 @@ The point is to hide configuration of observers and use (if you wish)
|
|
191
210
|
your own API to run reports or migrations:
|
192
211
|
``` ruby
|
193
212
|
weekly_report.create_and_deliver
|
194
|
-
user_data_import.
|
213
|
+
user_data_import.from_csv(file)
|
195
214
|
etc.
|
196
215
|
```
|
197
216
|
|
198
|
-
It is possible to use it raw, but please
|
217
|
+
It is possible to use it raw, but please don't fear to add a wrapper
|
199
218
|
class like `CsvUserImport` for this:
|
200
219
|
``` ruby
|
201
220
|
csv_data_processor = Processor::Data::CsvProcessor.new file
|
@@ -214,6 +233,14 @@ More documentation could be found by running
|
|
214
233
|
rspec
|
215
234
|
```
|
216
235
|
|
236
|
+
Find more examples under [example directory](https://github.com/AlexParamonov/processor/tree/master/example)
|
237
|
+
|
238
|
+
Contacts
|
239
|
+
-------------
|
240
|
+
Have questions or recommendations? Contact me via `alexander.n.paramonov@gmail.com`
|
241
|
+
Found a bug or have enhancement request? You are welcome at [Github bugtracker](https://github.com/AlexParamonov/processor/issues)
|
242
|
+
|
243
|
+
|
217
244
|
Compatibility
|
218
245
|
-------------
|
219
246
|
tested with Ruby
|
@@ -222,7 +249,7 @@ tested with Ruby
|
|
222
249
|
* rbx-19mode
|
223
250
|
* ruby-head
|
224
251
|
|
225
|
-
|
252
|
+
See [build history](http://travis-ci.org/#!/AlexParamonov/processor/builds)
|
226
253
|
|
227
254
|
Contributing
|
228
255
|
-------------
|
data/example/migrator.rb
CHANGED
@@ -43,6 +43,7 @@ module Processor
|
|
43
43
|
|
44
44
|
stdout_logger_debug = Processor::Observer::Logger.new(logger1, messenger: messenger)
|
45
45
|
stdout_logger_info = Processor::Observer::Logger.new(logger2)
|
46
|
+
# file_logger_info = Processor::Observer::Logger.new
|
46
47
|
your_custom_observer1 = Observer::NullObserver.new
|
47
48
|
your_custom_observer2 = Observer::NullObserver.new
|
48
49
|
|
@@ -50,6 +51,7 @@ module Processor
|
|
50
51
|
migration,
|
51
52
|
stdout_logger_debug,
|
52
53
|
stdout_logger_info,
|
54
|
+
# file_logger_info,
|
53
55
|
your_custom_observer1,
|
54
56
|
your_custom_observer2,
|
55
57
|
)
|
@@ -1,6 +1,12 @@
|
|
1
1
|
module Processor
|
2
2
|
module Data
|
3
3
|
class NullProcessor
|
4
|
+
def start; end
|
5
|
+
def finish; end
|
6
|
+
def finalize; end
|
7
|
+
def error(exception); end
|
8
|
+
def record_error(record, exception); end
|
9
|
+
|
4
10
|
def process(record)
|
5
11
|
# do nothing
|
6
12
|
end
|
@@ -10,16 +16,7 @@ module Processor
|
|
10
16
|
end
|
11
17
|
|
12
18
|
def total_records
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
|
-
def name
|
17
|
-
# underscore a class name
|
18
|
-
self.class.name.to_s.
|
19
|
-
gsub(/::/, '_').
|
20
|
-
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
21
|
-
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
22
|
-
downcase
|
19
|
+
0
|
23
20
|
end
|
24
21
|
end
|
25
22
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Processor
|
2
|
+
class EventProcessor
|
3
|
+
def initialize(processor, observers = [])
|
4
|
+
@observers = observers
|
5
|
+
@processor = processor
|
6
|
+
end
|
7
|
+
|
8
|
+
def register(event, *data)
|
9
|
+
observers.each do |observer|
|
10
|
+
observer.update event.to_sym, processor, *data
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def method_missing(method, *args)
|
15
|
+
register "before_#{method}", *args
|
16
|
+
result = processor.public_send method, *args
|
17
|
+
register "after_#{method}", result, *args
|
18
|
+
|
19
|
+
result
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
attr_reader :observers, :processor
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'delegate'
|
3
|
+
|
4
|
+
module Processor
|
5
|
+
class LoggerMessages < SimpleDelegator
|
6
|
+
def initialize(logger)
|
7
|
+
log_device = fetch_log_device logger
|
8
|
+
messages = case log_device
|
9
|
+
when File::NULL then NullMessages.new
|
10
|
+
when String then FileMessages.new log_device
|
11
|
+
when File then FileMessages.new log_device.path
|
12
|
+
when IO then IoMessages.new
|
13
|
+
else NullMessages.new
|
14
|
+
end
|
15
|
+
|
16
|
+
super messages
|
17
|
+
end
|
18
|
+
|
19
|
+
class NullMessages
|
20
|
+
def initialize(filename = "")
|
21
|
+
@filename = filename
|
22
|
+
end
|
23
|
+
def initialized; "" end
|
24
|
+
def finished; "" end
|
25
|
+
private
|
26
|
+
attr_reader :filename
|
27
|
+
end
|
28
|
+
|
29
|
+
class IoMessages < NullMessages
|
30
|
+
def initialized
|
31
|
+
"Proggress will be streaming to provided IO object"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class FileMessages < NullMessages
|
36
|
+
def initialized
|
37
|
+
<<-MESSAGE
|
38
|
+
Proggress will be saved to the log file. Run
|
39
|
+
tail -f #{filename}
|
40
|
+
to see log in realtime
|
41
|
+
MESSAGE
|
42
|
+
end
|
43
|
+
|
44
|
+
def finished
|
45
|
+
"Log file saved to #{filename}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def fetch_log_device logger
|
51
|
+
return unless logger.is_a? ::Logger
|
52
|
+
log_dev = logger.instance_variable_get "@logdev"
|
53
|
+
log_dev.filename or log_dev.dev
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/processor/messenger.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
require
|
1
|
+
require 'logger'
|
2
|
+
require 'delegate'
|
2
3
|
|
3
4
|
module Processor
|
4
5
|
class Messenger < SimpleDelegator
|
5
|
-
def initialize(level = :info, file = STDOUT)
|
6
|
+
def initialize(level = :info, file = STDOUT, sender = nil)
|
7
|
+
@sender = sender
|
6
8
|
if level == :null
|
7
9
|
file = "/dev/null"
|
8
10
|
level = :fatal
|
@@ -18,17 +20,28 @@ module Processor
|
|
18
20
|
super logger
|
19
21
|
end
|
20
22
|
|
23
|
+
%w[debug info warn error fatal unknown].each do |level|
|
24
|
+
define_method level do |message, &block|
|
25
|
+
return if message.nil? || message.empty?
|
26
|
+
add(Logger.const_get(level.upcase), nil, message, &block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
21
30
|
def message(*args)
|
22
31
|
self.info *args
|
23
32
|
end
|
24
33
|
|
25
34
|
private
|
35
|
+
attr_reader :sender
|
26
36
|
def format_message(severity, datetime, progname, message)
|
27
37
|
lines = message.split("\n").map do |line|
|
28
38
|
"> %s" % line.gsub(/^\s+/, '')
|
29
39
|
end.join("\n")
|
30
40
|
|
31
|
-
"\n#{severity} message
|
41
|
+
message = "\n#{severity} message"
|
42
|
+
message << " from #{sender}" if sender
|
43
|
+
message << " on #{datetime}"
|
44
|
+
message << ":\n#{lines}\n"
|
32
45
|
end
|
33
46
|
end
|
34
47
|
end
|
@@ -1,65 +1,78 @@
|
|
1
|
-
require_relative 'null_observer'
|
2
1
|
require 'logger'
|
2
|
+
require 'ostruct'
|
3
|
+
require_relative 'null_observer'
|
4
|
+
require 'processor/subroutine/name'
|
5
|
+
require 'processor/logger_messages'
|
3
6
|
|
4
7
|
module Processor
|
5
8
|
module Observer
|
6
9
|
class Logger < NullObserver
|
7
10
|
def initialize(logger = nil, options = {})
|
8
11
|
@logger_source = logger
|
12
|
+
@messages = options.fetch :messages, nil
|
13
|
+
@messages = OpenStruct.new @messages if @messages.is_a? Hash
|
14
|
+
|
9
15
|
super options
|
10
16
|
end
|
11
17
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
message = <<-MESSAGE
|
17
|
-
Proggress will be saved to the log file. Run
|
18
|
-
tail -f #{log_file_name}
|
19
|
-
to see log in realtime
|
20
|
-
MESSAGE
|
21
|
-
|
22
|
-
messenger.info message if use_log_file?
|
18
|
+
def after_start(result)
|
19
|
+
logger.info "Processing of #{processor_name} started."
|
20
|
+
messenger.info messages.initialized
|
23
21
|
end
|
24
22
|
|
25
|
-
def
|
23
|
+
def before_process(record)
|
26
24
|
logger.debug "Record #{id_for record} is going to be processed"
|
27
25
|
end
|
28
26
|
|
29
|
-
def
|
27
|
+
def after_process(result, record)
|
30
28
|
logger.info "Processed #{id_for record}: #{result}"
|
31
29
|
end
|
32
30
|
|
33
|
-
def
|
34
|
-
logger.info "Processing of #{
|
35
|
-
messenger.
|
31
|
+
def after_finalize(result)
|
32
|
+
logger.info "Processing of #{processor_name} finished."
|
33
|
+
messenger.message messages.finished
|
36
34
|
end
|
37
35
|
|
38
|
-
def
|
36
|
+
def after_record_error(result, record, exception)
|
39
37
|
logger.error "Error processing #{id_for record}: #{exception}"
|
40
38
|
end
|
41
39
|
|
42
|
-
def
|
43
|
-
logger.fatal "Processing #{
|
40
|
+
def after_error(result, exception)
|
41
|
+
logger.fatal "Processing #{processor_name} FAILED: #{exception.backtrace}"
|
44
42
|
end
|
45
43
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
@logger =
|
51
|
-
if @logger_source.is_a? Proc
|
52
|
-
@logger_source.call processor.name
|
44
|
+
def logger
|
45
|
+
@logger ||= begin
|
46
|
+
if @logger_source.is_a? Proc
|
47
|
+
@logger_source.call processor_name
|
53
48
|
else
|
54
|
-
@logger_source or ::Logger.new(create_log_filename(
|
49
|
+
@logger_source or ::Logger.new(create_log_filename(processor_name)).tap do |logger|
|
55
50
|
logger.level = ::Logger::INFO
|
56
51
|
end
|
57
52
|
end
|
58
|
-
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def messages
|
59
|
+
@messages ||= LoggerMessages.new logger
|
60
|
+
end
|
61
|
+
|
62
|
+
def processor_name
|
63
|
+
@processor_name ||=
|
64
|
+
begin
|
65
|
+
@processor = Subroutine::Name.new processor unless processor.respond_to? :name
|
66
|
+
processor.name
|
67
|
+
end
|
59
68
|
end
|
60
69
|
|
61
70
|
def create_log_filename(processor_name)
|
62
|
-
|
71
|
+
unless File.directory? log_directory
|
72
|
+
FileUtils.mkdir log_directory
|
73
|
+
messenger.warn "Created new directory for logs: #{File.absolute_path log_directory}"
|
74
|
+
end
|
75
|
+
|
63
76
|
@log_file_name = "#{log_directory}/#{processor_name}_on_#{current_time_string}.log"
|
64
77
|
end
|
65
78
|
|
@@ -67,10 +80,6 @@ module Processor
|
|
67
80
|
"log"
|
68
81
|
end
|
69
82
|
|
70
|
-
def use_log_file?
|
71
|
-
not log_file_name.nil?
|
72
|
-
end
|
73
|
-
|
74
83
|
def current_time_string
|
75
84
|
Time.now.gmtime.strftime "%Y-%m-%d_%H%M%S_UTC"
|
76
85
|
end
|
@@ -4,12 +4,17 @@ require 'processor/messenger'
|
|
4
4
|
module Processor
|
5
5
|
module Observer
|
6
6
|
class NullObserver
|
7
|
+
attr_reader :processor
|
8
|
+
|
7
9
|
def initialize(options = {})
|
8
|
-
@messenger = options.fetch :messenger, Processor::Messenger.new(:info)
|
10
|
+
@messenger = options.fetch :messenger, Processor::Messenger.new(:info, STDOUT, self.class.name)
|
11
|
+
@processor = options.fetch :processor, nil
|
9
12
|
end
|
10
13
|
|
11
|
-
def
|
12
|
-
|
14
|
+
def update(method_name, processor = nil, *args)
|
15
|
+
@processor ||= processor
|
16
|
+
send method_name, *args if respond_to? method_name
|
17
|
+
end
|
13
18
|
|
14
19
|
private
|
15
20
|
attr_reader :messenger
|
@@ -6,23 +6,18 @@ module Processor
|
|
6
6
|
@threads = []
|
7
7
|
end
|
8
8
|
|
9
|
-
def call(processor
|
9
|
+
def call(processor)
|
10
10
|
join_threads
|
11
11
|
|
12
12
|
begin
|
13
13
|
processor.records.each do |record|
|
14
|
-
recursion_preventer.call
|
15
14
|
if threads_created >= number_of_threads then join_threads end
|
16
15
|
|
17
16
|
new_thread(processor, record) do |thread_data_processor, thread_record|
|
18
17
|
begin
|
19
|
-
|
20
|
-
|
21
|
-
result = thread_data_processor.process(thread_record)
|
22
|
-
|
23
|
-
events.register :after_record_processing, thread_record, result
|
18
|
+
thread_data_processor.process(thread_record)
|
24
19
|
rescue StandardError => exception
|
25
|
-
|
20
|
+
thread_data_processor.record_error thread_record, exception
|
26
21
|
end
|
27
22
|
end
|
28
23
|
|
data/lib/processor/runner.rb
CHANGED
@@ -1,38 +1,23 @@
|
|
1
|
-
require_relative "events_registrator"
|
2
|
-
|
3
1
|
module Processor
|
4
2
|
class Runner
|
5
|
-
def initialize(processor
|
3
|
+
def initialize(processor)
|
6
4
|
@processor = processor
|
7
|
-
@events = events_registrator
|
8
5
|
end
|
9
6
|
|
10
7
|
def run(process_runner)
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
processor.start
|
9
|
+
process_runner.call processor
|
10
|
+
processor.finish
|
14
11
|
|
15
|
-
events.register :processing_finished, processor
|
16
12
|
rescue Exception => exception
|
17
|
-
|
13
|
+
processor.error exception
|
18
14
|
raise exception
|
19
|
-
end
|
20
15
|
|
21
|
-
|
22
|
-
|
23
|
-
def counter
|
24
|
-
@counter ||= 0
|
16
|
+
ensure
|
17
|
+
processor.finalize
|
25
18
|
end
|
26
19
|
|
27
20
|
private
|
28
|
-
attr_reader :
|
29
|
-
def recursion_preventer
|
30
|
-
self.counter += 1
|
31
|
-
raise Exception, "Processing fall into recursion. Check logs." if self.counter > max_records_to_process
|
32
|
-
end
|
33
|
-
|
34
|
-
def max_records_to_process
|
35
|
-
@max_records_to_process ||= (processor.total_records * 1.1).round + 10
|
36
|
-
end
|
21
|
+
attr_reader :processor
|
37
22
|
end
|
38
23
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module Processor
|
4
|
+
module Subroutine
|
5
|
+
class Counter < ::SimpleDelegator
|
6
|
+
def process(*)
|
7
|
+
super
|
8
|
+
record_processed
|
9
|
+
end
|
10
|
+
|
11
|
+
def remaining_records_count
|
12
|
+
[ total_records - processed_records_count, 0 ].max
|
13
|
+
end
|
14
|
+
|
15
|
+
def processed_records_count
|
16
|
+
@processed_records_count ||= 0
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def record_processed
|
21
|
+
@processed_records_count = processed_records_count + 1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|