processor 1.0.0.alpha → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.md +144 -24
- data/example/migrator.rb +5 -7
- data/lib/processor/data/batch_processor.rb +3 -1
- data/lib/processor/data/csv_processor.rb +2 -2
- data/lib/processor/messenger.rb +34 -0
- data/lib/processor/observer/logger.rb +10 -4
- data/lib/processor/observer/null_observer.rb +2 -8
- data/lib/processor/version.rb +1 -1
- data/lib/processor.rb +2 -0
- data/spec/processor/data/batch_processor_spec.rb +6 -2
- data/spec/processor/messenger_spec.rb +53 -0
- metadata +12 -6
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -3,8 +3,12 @@ Processor
|
|
3
3
|
[![Build Status](https://travis-ci.org/AlexParamonov/processor.png?branch=master)](http://travis-ci.org/AlexParamonov/processor)
|
4
4
|
[![Gemnasium Build Status](https://gemnasium.com/AlexParamonov/processor.png)](http://gemnasium.com/AlexParamonov/processor)
|
5
5
|
|
6
|
-
Processor could execute any `DataProcessor` you specify and log entire
|
7
|
-
|
6
|
+
Processor could execute any `DataProcessor` you specify and log entire
|
7
|
+
process using any number of loggers you need.
|
8
|
+
|
9
|
+
You may add own observers for monitoring background tasks on even send
|
10
|
+
an email to bussiness with generated report.
|
11
|
+
|
8
12
|
Processor provide customisation for almost every part of it.
|
9
13
|
|
10
14
|
|
@@ -13,6 +17,10 @@ Contents
|
|
13
17
|
1. Installation
|
14
18
|
1. Requirements
|
15
19
|
1. Usage
|
20
|
+
1. Data processors
|
21
|
+
1. Run modes
|
22
|
+
1. Processor Thread
|
23
|
+
1. Observers
|
16
24
|
1. Compatibility
|
17
25
|
1. Contributing
|
18
26
|
1. Copyright
|
@@ -20,19 +28,16 @@ Contents
|
|
20
28
|
Installation
|
21
29
|
------------
|
22
30
|
Add this line to your application's Gemfile:
|
23
|
-
|
24
31
|
``` ruby
|
25
32
|
gem 'processor'
|
26
33
|
```
|
27
34
|
|
28
35
|
And then execute:
|
29
|
-
|
30
36
|
``` sh
|
31
37
|
bundle
|
32
38
|
```
|
33
39
|
|
34
40
|
Or install it yourself as:
|
35
|
-
|
36
41
|
``` sh
|
37
42
|
gem install processor
|
38
43
|
```
|
@@ -44,40 +49,155 @@ Requirements
|
|
44
49
|
|
45
50
|
Usage
|
46
51
|
------------
|
47
|
-
1. Implement a `DataProcessor`. See `Processor::Example::Migration`
|
48
|
-
and `processor/data` directory. CSV and Solr data processors are usabe
|
49
|
-
but not yet finished and tested.
|
50
|
-
1. Run your `DataProcessor`:
|
51
52
|
|
53
|
+
### Data processors
|
54
|
+
Actual processing is done by a Data Processor, provided by end user.
|
55
|
+
This processor should implement in general 2 methods:
|
56
|
+
|
57
|
+
1. `process(record)`
|
58
|
+
1. `records`
|
59
|
+
|
60
|
+
But it is recomended to implement a `name` method also, because it
|
61
|
+
is required by several observers. Inherit your Data Processor from
|
62
|
+
NullProcessor to get default behavior out of the box.
|
63
|
+
|
64
|
+
See `Processor::Example::Migration` for example (`example/migration.rb`).
|
65
|
+
|
66
|
+
There are several predefined data processors you can reuse:
|
67
|
+
|
68
|
+
|
69
|
+
#### ArrayProcessor
|
70
|
+
The simplest one: `process` and `records` methods should be implemented.
|
71
|
+
|
72
|
+
|
73
|
+
#### BatchProcessor
|
74
|
+
Allows to fetch records by batches of defined size.
|
75
|
+
|
76
|
+
It is based on `query` method that suppose to run a query method on
|
77
|
+
database.
|
78
|
+
|
79
|
+
Recomended to override `fetch_batch` method to get real reason to
|
80
|
+
use batch processing. `fetch_batch` could be `query.first(10)` or
|
81
|
+
`query.page(next_page)`. See `data/solr_pages_processor.rb` and
|
82
|
+
`data/solr_processor.rb` for example.
|
83
|
+
|
84
|
+
|
85
|
+
#### Other
|
86
|
+
see `data/csv_processor.rb` for running migration from CSV files.
|
87
|
+
|
88
|
+
|
89
|
+
### Run modes
|
90
|
+
Currently 2 run modes are supported:
|
91
|
+
|
92
|
+
|
93
|
+
#### Successive
|
94
|
+
It runs `process` one by one for each found record returned by
|
95
|
+
`records` method.
|
96
|
+
|
97
|
+
Recomended to call it using a `Processor::Thread`:
|
98
|
+
``` ruby
|
99
|
+
Processor::Thread.new(migration).run_successive
|
100
|
+
```
|
101
|
+
|
102
|
+
#### Threads
|
103
|
+
It runs `process` for each found record returned by `records` method
|
104
|
+
not waiting for previous `process` to finish.
|
105
|
+
|
106
|
+
Possible to specify number of threads used by passing a number to
|
107
|
+
constructor:
|
108
|
+
``` ruby
|
109
|
+
Processor::ProcessRunner::Threads.new 5
|
110
|
+
```
|
111
|
+
|
112
|
+
Recomended to call it using a Processor::Thread :
|
113
|
+
``` ruby
|
114
|
+
Processor::Thread.new(migration).run_in_threads 5
|
115
|
+
```
|
116
|
+
|
117
|
+
|
118
|
+
### Observers
|
119
|
+
Processor support unlimited number of observers, watching processing.
|
120
|
+
|
121
|
+
Thay could monitor running migrations and output to logs, console or
|
122
|
+
file usefull information. Or thay can show a progress bar to your
|
123
|
+
console. Or pack a generated report to archive and send by email to
|
124
|
+
bussiness on success or notify developers on failure.
|
125
|
+
|
126
|
+
|
127
|
+
This observers should respond to `update` method. But if you inherit
|
128
|
+
from `Processor::Observers::NullObserver` you'll get a bunch of
|
129
|
+
methods, such as before_ and after_ processing, error handling methods
|
130
|
+
to use. See `Processor::Observers::Logger` for example.
|
131
|
+
|
132
|
+
Read below section Processor Thread to see how to use observers in runner.
|
133
|
+
|
134
|
+
|
135
|
+
### Processor Thread
|
136
|
+
`Processor::Thread` is a Facade pattern. It simplifies access to all
|
137
|
+
Processor classes and provide __stable__ interface.
|
138
|
+
|
139
|
+
Creating a new Thread:
|
140
|
+
``` ruby
|
141
|
+
Processor::Thread.new data_processor
|
142
|
+
```
|
143
|
+
|
144
|
+
You may provide optional observers:
|
145
|
+
``` ruby
|
146
|
+
Processor::Thread.new data_processor, observer1, observer2, ...
|
147
|
+
```
|
148
|
+
|
149
|
+
Instance have a `run_as` method that accepts a block:
|
150
|
+
``` ruby
|
151
|
+
thread = Processor::Thread.new @migration
|
152
|
+
thread.run_as do |processor, *|
|
153
|
+
processor.records.each do |record|
|
154
|
+
processor.process record
|
155
|
+
end
|
156
|
+
end
|
157
|
+
```
|
158
|
+
|
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
|
+
Instance have a `run_successive` method:
|
52
167
|
``` ruby
|
53
168
|
data_processor = UserLocationMigration.new
|
54
169
|
thread = Processor::Thread.new data_processor
|
55
170
|
thread.run_successive
|
56
171
|
```
|
57
|
-
See `spec/processor/thread_spec.rb` and `spec/example_spec.rb` and
|
58
|
-
`example` directory for other usage examples.
|
59
172
|
|
173
|
+
And `run_in_threads` method:
|
174
|
+
``` ruby
|
175
|
+
data_processor = UserCsvImport.new csv_file
|
176
|
+
thread = Processor::Thread.new data_processor
|
177
|
+
thread.run_in_threads 10
|
178
|
+
```
|
60
179
|
|
61
|
-
|
180
|
+
See `spec/processor/thread_spec.rb` and `spec/example_spec.rb` and
|
181
|
+
`example` directory for other usage examples.
|
62
182
|
|
63
|
-
|
183
|
+
It is recomended to wrap Processor::Thread by classes named like:
|
184
|
+
``` ruby
|
64
185
|
WeeklyReport
|
65
186
|
TaxonomyMigration
|
66
187
|
UserDataImport
|
67
188
|
```
|
68
|
-
to hide configuration of observers or use your own API to run
|
69
|
-
migrations:
|
70
189
|
|
71
|
-
|
190
|
+
The point is to hide configuration of observers and use (if you wish)
|
191
|
+
your own API to run reports or migrations:
|
192
|
+
``` ruby
|
72
193
|
weekly_report.create_and_deliver
|
73
194
|
user_data_import.import_from_csv(file)
|
74
195
|
etc.
|
75
196
|
```
|
76
197
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
```
|
198
|
+
It is possible to use it raw, but please dont fear to add a wrapper
|
199
|
+
class like `CsvUserImport` for this:
|
200
|
+
``` ruby
|
81
201
|
csv_data_processor = Processor::Data::CsvProcessor.new file
|
82
202
|
stdout_notifier = Processor::Observer::Logger.new(Logger.new(STDOUT))
|
83
203
|
logger_observer = Processor::Observer::Logger.new
|
@@ -89,10 +209,10 @@ Processor::Thread.new(
|
|
89
209
|
).run_in_threads 5
|
90
210
|
```
|
91
211
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
212
|
+
More documentation could be found by running
|
213
|
+
``` sh
|
214
|
+
rspec
|
215
|
+
```
|
96
216
|
|
97
217
|
Compatibility
|
98
218
|
-------------
|
data/example/migrator.rb
CHANGED
@@ -13,7 +13,7 @@ module Processor
|
|
13
13
|
# logger could be an instance of Ruby Logger
|
14
14
|
logger1 = ::Logger.new(STDOUT).tap do |logger|
|
15
15
|
logger.formatter = -> _, _, _, msg do
|
16
|
-
"
|
16
|
+
"log < debug logger: #{msg}\n"
|
17
17
|
end
|
18
18
|
logger.level = ::Logger::DEBUG
|
19
19
|
end
|
@@ -23,7 +23,7 @@ module Processor
|
|
23
23
|
logger2 = -> name do
|
24
24
|
::Logger.new(STDOUT).tap do |logger|
|
25
25
|
logger.formatter = -> _, _, _, msg do
|
26
|
-
"info
|
26
|
+
"log < info logger: #{msg}\n"
|
27
27
|
end
|
28
28
|
logger.level = ::Logger::INFO
|
29
29
|
end
|
@@ -36,11 +36,9 @@ module Processor
|
|
36
36
|
# in this case logger will be initialized as Ruby Logger and write to log/name_of_processor_time_stamp.log
|
37
37
|
|
38
38
|
# You may customize a messenger:
|
39
|
-
messenger = ::
|
40
|
-
|
41
|
-
|
42
|
-
end
|
43
|
-
logger.level = ::Logger::DEBUG
|
39
|
+
messenger = Processor::Messenger.new :debug
|
40
|
+
messenger.formatter = -> _, _, _, msg do
|
41
|
+
"message\t> #{msg}\n"
|
44
42
|
end
|
45
43
|
|
46
44
|
stdout_logger_debug = Processor::Observer::Logger.new(logger1, messenger: messenger)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "logger"
|
2
|
+
|
3
|
+
module Processor
|
4
|
+
class Messenger < SimpleDelegator
|
5
|
+
def initialize(level = :info, file = STDOUT)
|
6
|
+
if level == :null
|
7
|
+
file = "/dev/null"
|
8
|
+
level = :fatal
|
9
|
+
end
|
10
|
+
|
11
|
+
log_level = Logger.const_get(level.upcase);
|
12
|
+
|
13
|
+
logger = ::Logger.new(file).tap do |logger|
|
14
|
+
logger.formatter = method(:format_message)
|
15
|
+
logger.level = log_level
|
16
|
+
end
|
17
|
+
|
18
|
+
super logger
|
19
|
+
end
|
20
|
+
|
21
|
+
def message(*args)
|
22
|
+
self.info *args
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def format_message(severity, datetime, progname, message)
|
27
|
+
lines = message.split("\n").map do |line|
|
28
|
+
"> %s" % line.gsub(/^\s+/, '')
|
29
|
+
end.join("\n")
|
30
|
+
|
31
|
+
"\n#{severity} message on #{datetime}:\n#{lines}\n\n"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -13,12 +13,13 @@ module Processor
|
|
13
13
|
initialize_logger(processor)
|
14
14
|
logger.info "Processing of #{processor.name} started."
|
15
15
|
|
16
|
-
message = <<-MESSAGE
|
16
|
+
message = <<-MESSAGE
|
17
17
|
Proggress will be saved to the log file. Run
|
18
18
|
tail -f #{log_file_name}
|
19
19
|
to see log in realtime
|
20
|
-
|
21
|
-
|
20
|
+
MESSAGE
|
21
|
+
|
22
|
+
messenger.info message if use_log_file?
|
22
23
|
end
|
23
24
|
|
24
25
|
def before_record_processing(record)
|
@@ -58,7 +59,12 @@ module Processor
|
|
58
59
|
end
|
59
60
|
|
60
61
|
def create_log_filename(processor_name)
|
61
|
-
|
62
|
+
FileUtils.mkdir log_directory unless File.directory? log_directory
|
63
|
+
@log_file_name = "#{log_directory}/#{processor_name}_on_#{current_time_string}.log"
|
64
|
+
end
|
65
|
+
|
66
|
+
def log_directory
|
67
|
+
"log"
|
62
68
|
end
|
63
69
|
|
64
70
|
def use_log_file?
|
@@ -1,17 +1,11 @@
|
|
1
1
|
require 'logger'
|
2
|
+
require 'processor/messenger'
|
2
3
|
|
3
4
|
module Processor
|
4
5
|
module Observer
|
5
6
|
class NullObserver
|
6
7
|
def initialize(options = {})
|
7
|
-
@messenger = options.fetch :messenger
|
8
|
-
::Logger.new(STDOUT).tap do |logger|
|
9
|
-
logger.formatter = -> _, _, _, msg do
|
10
|
-
"> #{msg}\n"
|
11
|
-
end
|
12
|
-
logger.level = ::Logger::INFO
|
13
|
-
end
|
14
|
-
end
|
8
|
+
@messenger = options.fetch :messenger, Processor::Messenger.new(:info)
|
15
9
|
end
|
16
10
|
|
17
11
|
def method_missing(*); end
|
data/lib/processor/version.rb
CHANGED
data/lib/processor.rb
CHANGED
@@ -3,6 +3,8 @@ require "processor/version"
|
|
3
3
|
require "processor/data/array_processor"
|
4
4
|
require "processor/data/batch_processor"
|
5
5
|
require "processor/data/null_processor"
|
6
|
+
require "processor/data/solr_processor"
|
7
|
+
require "processor/data/solr_pages_processor"
|
6
8
|
|
7
9
|
require "processor/observer/logger"
|
8
10
|
require "processor/observer/null_observer"
|
@@ -16,8 +16,7 @@ describe Processor::Data::BatchProcessor do
|
|
16
16
|
|
17
17
|
query = Enumerator.new do |y|
|
18
18
|
a = 1
|
19
|
-
|
20
|
-
break if a > 10
|
19
|
+
until a > 10
|
21
20
|
watcher.created
|
22
21
|
y << a
|
23
22
|
a += 1
|
@@ -35,4 +34,9 @@ describe Processor::Data::BatchProcessor do
|
|
35
34
|
subject.stub(query: query)
|
36
35
|
subject.total_records.should eq 5
|
37
36
|
end
|
37
|
+
|
38
|
+
it "should stop iteration if fetch_records returned empty result set" do
|
39
|
+
subject.stub(fetch_batch: [])
|
40
|
+
subject.records.to_a.should eq []
|
41
|
+
end
|
38
42
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper_lite'
|
2
|
+
require 'processor/messenger'
|
3
|
+
|
4
|
+
describe Processor::Messenger do
|
5
|
+
let(:io) { STDOUT }
|
6
|
+
|
7
|
+
it "should delegate to logger" do
|
8
|
+
io.should_receive(:write)
|
9
|
+
messenger = Processor::Messenger.new :debug, io
|
10
|
+
messenger.debug "debug message"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should accept symbol as messaging level" do
|
14
|
+
messenger = Processor::Messenger.new :info
|
15
|
+
messenger.level.should eq Logger::INFO
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should not output anything if :null level provided" do
|
19
|
+
io.should_not_receive(:write)
|
20
|
+
messenger = Processor::Messenger.new :null, io
|
21
|
+
messenger.fatal "fatal error"
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "messages" do
|
25
|
+
let(:messenger) { Processor::Messenger.new :debug, io }
|
26
|
+
%w[debug info error fatal].each do |message_level|
|
27
|
+
it "should send #{message_level} message" do
|
28
|
+
message = "#{message_level} message"
|
29
|
+
io.should_receive(:write).with /#{message}/
|
30
|
+
messenger.send message_level, message
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should send info message by .message method" do
|
36
|
+
messenger = Processor::Messenger.new :info, io
|
37
|
+
messenger.should_receive(:info).with("test message")
|
38
|
+
messenger.message "test message"
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "formatter" do
|
42
|
+
let(:messenger) { Processor::Messenger.new :info, io }
|
43
|
+
|
44
|
+
it "should accept formatter proc" do
|
45
|
+
io.should_receive(:write).with("test message")
|
46
|
+
messenger.formatter = -> _,_,_,message do
|
47
|
+
message
|
48
|
+
end
|
49
|
+
|
50
|
+
messenger.message "test message"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: processor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0
|
5
|
-
prerelease:
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Alexander Paramonov
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -88,6 +88,7 @@ files:
|
|
88
88
|
- lib/processor/data/solr_pages_processor.rb
|
89
89
|
- lib/processor/data/solr_processor.rb
|
90
90
|
- lib/processor/events_registrator.rb
|
91
|
+
- lib/processor/messenger.rb
|
91
92
|
- lib/processor/observer/logger.rb
|
92
93
|
- lib/processor/observer/null_observer.rb
|
93
94
|
- lib/processor/process_runner/successive.rb
|
@@ -101,6 +102,7 @@ files:
|
|
101
102
|
- spec/processor/data/batch_processor_spec.rb
|
102
103
|
- spec/processor/data/null_processor_spec.rb
|
103
104
|
- spec/processor/events_registrator_spec.rb
|
105
|
+
- spec/processor/messenger_spec.rb
|
104
106
|
- spec/processor/observer/logger_spec.rb
|
105
107
|
- spec/processor/process_runner/specs.rb
|
106
108
|
- spec/processor/process_runner/successive_spec.rb
|
@@ -123,13 +125,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
123
125
|
version: '0'
|
124
126
|
segments:
|
125
127
|
- 0
|
126
|
-
hash:
|
128
|
+
hash: 204713070122044710
|
127
129
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
130
|
none: false
|
129
131
|
requirements:
|
130
|
-
- - ! '
|
132
|
+
- - ! '>='
|
131
133
|
- !ruby/object:Gem::Version
|
132
|
-
version:
|
134
|
+
version: '0'
|
135
|
+
segments:
|
136
|
+
- 0
|
137
|
+
hash: 204713070122044710
|
133
138
|
requirements: []
|
134
139
|
rubyforge_project:
|
135
140
|
rubygems_version: 1.8.25
|
@@ -142,6 +147,7 @@ test_files:
|
|
142
147
|
- spec/processor/data/batch_processor_spec.rb
|
143
148
|
- spec/processor/data/null_processor_spec.rb
|
144
149
|
- spec/processor/events_registrator_spec.rb
|
150
|
+
- spec/processor/messenger_spec.rb
|
145
151
|
- spec/processor/observer/logger_spec.rb
|
146
152
|
- spec/processor/process_runner/specs.rb
|
147
153
|
- spec/processor/process_runner/successive_spec.rb
|