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 CHANGED
@@ -5,6 +5,7 @@
5
5
  Gemfile.lock
6
6
  /pkg
7
7
  /tmp
8
+ /log
8
9
  /tags
9
10
 
10
11
  ## generic files to ignore
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 process using any number of loggers you need.
7
- You may add own observers for monitoring background tasks on even send an email to bussiness with generated report.
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
- It is recomended to wrap Processor::Thread in your classes like:
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
- Sure, it is possible to use it raw, but please dont fear to add a
78
- wrapper class for it:
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
- ### Observers
93
- Observers should respond to `update` method but if you inherit from
94
- `Processor::Observers::NullObserver` you'll get a bunch of methods to
95
- use. See `Processor::Observers::Logger` for example.
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
- "debug\t< #{msg}\n"
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\t< #{msg}\n"
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 = ::Logger.new(STDOUT).tap do |logger|
40
- logger.formatter = -> _, _, _, msg do
41
- "message\t> #{msg}\n"
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)
@@ -10,7 +10,9 @@ module Processor
10
10
  def records
11
11
  Enumerator.new do |result|
12
12
  loop do
13
- fetch_batch.each do |record|
13
+ batch = fetch_batch
14
+ break if batch.count < 1
15
+ batch.each do |record|
14
16
  result << record
15
17
  end
16
18
  end
@@ -1,8 +1,8 @@
1
- require_relative 'batch_processor'
1
+ require_relative 'null_processor'
2
2
 
3
3
  module Processor
4
4
  module Data
5
- class CsvProcessor < BatchProcessor
5
+ class CsvProcessor < NullProcessor
6
6
  def initialize(file, csv_options = {})
7
7
  @file = file
8
8
  @separator = separator
@@ -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.gsub(/^\s+/, '')
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
- MESSAGE
21
- messenger.info message if use_log_file?
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
- @log_file_name = "log/#{processor_name}_on_#{current_time_string}.log"
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 do
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
@@ -1,3 +1,3 @@
1
1
  module Processor
2
- VERSION = "1.0.0.alpha"
2
+ VERSION = "1.0.0"
3
3
  end
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
- loop do
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.alpha
5
- prerelease: 6
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-15 00:00:00.000000000 Z
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: -1752503773457190967
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: 1.3.1
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