processor 1.0.0.alpha → 1.0.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.
- 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
|
[](http://travis-ci.org/AlexParamonov/processor)
|
4
4
|
[](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
|