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 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