processor 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MzUxNDQ3NmM4YzZhOTk5Njc0MjVmNThiNDJhYTlhZWQ0NjhkNjJlOQ==
5
+ data.tar.gz: !binary |-
6
+ MWExYTAxNjk0MDU3MGRiMDc3NmE3NzA0MzNkZjMzMTYxZTEwY2I3OA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NzcxYTA0Y2UyYTU4OGNiYTA0YjkxMDdlODE5YTQ2ZjRhZDA3ZGY0ODM0MWY5
10
+ N2Y5NzY3MzI4MTZiN2UyMDY0NDgwYzQ1N2E1NDIxNmU2NmM4Y2I0ZmE5YjI1
11
+ NGU2ZjIwZTU1OWVmNjRmM2FhODY1ODFiOTBmODI5ZWI0MWExNGU=
12
+ data.tar.gz: !binary |-
13
+ MWQ3MWIyZGIzZGVhYzA1ZWJkYjlhOWM5NzE0MDIzZDRhNjE2NmI0MGE5OTk5
14
+ N2FhZTBiMzViNDY0MTM1NWMyNGNkZDI1ZTNjZDcxYjA5OTgzMzBjZThkMTNk
15
+ NzNmOWE2YjdhNDMwZmE1YmM5MjU3N2Q4ZmZiZWRlNjAzM2MwZjI=
data/.travis.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
+ - 2.1.4
4
5
  - ruby-head
5
6
  - rbx-19mode
6
7
  - jruby-19mode
data/README.md CHANGED
@@ -5,18 +5,18 @@ Processor
5
5
  [![Coverage Status](https://coveralls.io/repos/AlexParamonov/processor/badge.png?branch=master)](https://coveralls.io/r/AlexParamonov/processor?branch=master)
6
6
 
7
7
 
8
- Processor is a tool that helps to iterate over collection and
8
+ Processor is a tool that helps to iterate over a collection and
9
9
  perform complex actions on a result. It is extremely useful in data
10
10
  migrations, report generation, etc.
11
11
 
12
12
  Collection could be iteratively fetched by parts but for processor
13
13
  it will looks like an endless collection. There are a lot such tiny
14
14
  goodness that makes usage of processor pleasant. Need logging,
15
- exception processing, post/pre processing a result - no problems, all
15
+ exception processing, post/pre processing a result? No problem - all
16
16
  included and easily extended.
17
17
 
18
- Use the processor to DRY your migrations, report and stop mess with
19
- logging and post processing.
18
+ Use the processor to DRY your migrations, reports and to stop mess
19
+ with logging and post processing.
20
20
 
21
21
  Did I mentioned you can run in threads as easy as say
22
22
  `processor.run_in_threads 10`?
@@ -36,8 +36,8 @@ Contents
36
36
  1. Data processors
37
37
  1. Subroutines
38
38
  1. Run modes
39
- 1. Processor Thread
40
39
  1. Observers
40
+ 1. Processor Thread
41
41
  1. Contacts
42
42
  1. Compatibility
43
43
  1. Contributing
@@ -45,17 +45,20 @@ Contents
45
45
 
46
46
  Installation
47
47
  ------------
48
- Add this line to your application's Gemfile:
48
+ If on Rails, add this line to your application's Gemfile:
49
+
49
50
  ``` ruby
50
51
  gem 'processor'
51
52
  ```
52
53
 
53
54
  And then execute:
55
+
54
56
  ``` sh
55
57
  bundle
56
58
  ```
57
59
 
58
- Or install it yourself as:
60
+ Or install it yourself:
61
+
59
62
  ``` sh
60
63
  gem install processor
61
64
  ```
@@ -148,6 +151,7 @@ It runs `process` one by one for each found record returned by
148
151
  `records` method.
149
152
 
150
153
  Call it using a `Processor::Thread`:
154
+
151
155
  ``` ruby
152
156
  Processor::Thread.new(migration).run_successive
153
157
  ```
@@ -158,15 +162,20 @@ not waiting for previous `process` to finish.
158
162
 
159
163
  Possible to specify number of threads used by passing a number to
160
164
  constructor:
165
+
161
166
  ``` ruby
162
167
  Processor::ProcessRunner::Threads.new 5
163
168
  ```
164
169
 
165
170
  Call it using a `Processor::Thread`:
171
+
166
172
  ``` ruby
167
173
  Processor::Thread.new(migration).run_in_threads 5
168
174
  ```
169
175
 
176
+ Note: on MRI ruby you could expect perfomance gain using threads if
177
+ your application has resource consuming IO operations
178
+
170
179
 
171
180
  ### Observers
172
181
  Processor support unlimited number of observers that are watching
@@ -191,16 +200,19 @@ Read below section Processor Thread to see how to use observers in runner.
191
200
  Processor classes and provides __stable__ interface.
192
201
 
193
202
  Creating a new Thread:
203
+
194
204
  ``` ruby
195
205
  Processor::Thread.new data_processor
196
206
  ```
197
207
 
198
208
  You may provide optional observers:
209
+
199
210
  ``` ruby
200
211
  Processor::Thread.new data_processor, observer1, observer2, ...
201
212
  ```
202
213
 
203
214
  Instance have a `run_as` method that accepts a block:
215
+
204
216
  ``` ruby
205
217
  thread = Processor::Thread.new @migration
206
218
  thread.run_as do |processor|
@@ -211,6 +223,7 @@ end
211
223
  ```
212
224
 
213
225
  Instance have a `run_successive` method:
226
+
214
227
  ``` ruby
215
228
  data_processor = UserLocationMigration.new
216
229
  thread = Processor::Thread.new data_processor
@@ -218,6 +231,7 @@ thread.run_successive
218
231
  ```
219
232
 
220
233
  And `run_in_threads` method:
234
+
221
235
  ``` ruby
222
236
  data_processor = UserCsvImport.new csv_file
223
237
  thread = Processor::Thread.new data_processor
@@ -228,6 +242,7 @@ See `spec/processor/thread_spec.rb` and `spec/example_spec.rb` and
228
242
  `example` directory for other usage examples.
229
243
 
230
244
  It is recommended to wrap Processor::Thread by classes named like:
245
+
231
246
  ``` ruby
232
247
  WeeklyReport
233
248
  TaxonomyMigration
@@ -236,6 +251,7 @@ UserDataImport
236
251
 
237
252
  The point is to hide configuration of observers and use (if you wish)
238
253
  your own API to run reports or migrations:
254
+
239
255
  ``` ruby
240
256
  weekly_report.create_and_deliver
241
257
  user_data_import.from_csv(file)
@@ -244,6 +260,7 @@ etc.
244
260
 
245
261
  It is possible to use it raw, but please don't fear to add a wrapper
246
262
  class like `CsvUserImport` for this:
263
+
247
264
  ``` ruby
248
265
  csv_data_processor = Processor::Data::CsvProcessor.new file
249
266
  stdout_notifier = Processor::Observer::Logger.new(Logger.new(STDOUT))
@@ -257,6 +274,7 @@ Processor::Thread.new(
257
274
  ```
258
275
 
259
276
  More documentation could be found by running
277
+
260
278
  ``` sh
261
279
  rspec
262
280
  ```
@@ -274,6 +292,7 @@ Compatibility
274
292
  tested with Ruby
275
293
 
276
294
  * 1.9.3
295
+ * 2.1.4
277
296
  * rbx-19mode
278
297
  * ruby-head
279
298
 
@@ -2,6 +2,8 @@ require_relative 'null_processor'
2
2
 
3
3
  module Processor
4
4
  module Data
5
+ # TODO: Change it to be useful.
6
+ # Usually the external iterator is provided and #results are mapped to it
5
7
  class BatchProcessor < NullProcessor
6
8
  def initialize(batch_size = 10)
7
9
  @batch_size = batch_size
@@ -9,9 +11,7 @@ module Processor
9
11
 
10
12
  def records
11
13
  Enumerator.new do |result|
12
- loop do
13
- batch = fetch_batch
14
- break if batch.count < 1
14
+ while (batch = fetch_batch).any?
15
15
  batch.each do |record|
16
16
  result << record
17
17
  end
@@ -21,7 +21,10 @@ module Processor
21
21
 
22
22
  def fetch_batch
23
23
  @fetcher ||= query.each_slice(batch_size)
24
+ # TODO get rid of .next enumeration here
24
25
  @fetcher.next
26
+ rescue StopIteration
27
+ []
25
28
  end
26
29
 
27
30
  def total_records
@@ -7,7 +7,7 @@ module Processor
7
7
  raise NotImplementedError
8
8
  end
9
9
 
10
- def query(requested_page)
10
+ def query(requested_page, per_page = batch_size)
11
11
  raise NotImplementedError
12
12
  end
13
13
 
@@ -8,10 +8,16 @@ module Processor
8
8
  module Observer
9
9
  class Logger < NullObserver
10
10
  def initialize(logger = nil, options = {})
11
- @logger_source = logger
11
+ if logger.is_a? Hash
12
+ @log_level = logger.fetch :level, ::Logger::INFO
13
+ else
14
+ @logger_source = logger
15
+ end
16
+
12
17
  @messages = options.fetch :messages, nil
13
18
  @messages = OpenStruct.new @messages if @messages.is_a? Hash
14
19
 
20
+
15
21
  super options
16
22
  end
17
23
 
@@ -33,11 +39,11 @@ module Processor
33
39
  messenger.message messages.finished
34
40
  end
35
41
 
36
- def after_record_error(result, record, exception)
37
- logger.error "Error processing #{id_for record}: #{exception}"
42
+ def before_record_error(record, exception)
43
+ logger.error "Error processing #{id_for record}: #{exception.inspect}"
38
44
  end
39
45
 
40
- def after_error(result, exception)
46
+ def before_error(exception)
41
47
  logger.fatal "Processing #{processor_name} FAILED: #{exception.backtrace}"
42
48
  end
43
49
 
@@ -47,7 +53,7 @@ module Processor
47
53
  @logger_source.call processor_name
48
54
  else
49
55
  @logger_source or ::Logger.new(create_log_filename(processor_name)).tap do |logger|
50
- logger.level = ::Logger::INFO
56
+ logger.level = log_level
51
57
  end
52
58
  end
53
59
  end
@@ -55,6 +61,10 @@ module Processor
55
61
 
56
62
  private
57
63
 
64
+ def log_level
65
+ @log_level ||= ::Logger::INFO
66
+ end
67
+
58
68
  def messages
59
69
  @messages ||= LoggerMessages.new logger
60
70
  end
@@ -85,15 +95,7 @@ module Processor
85
95
  end
86
96
 
87
97
  def id_for record
88
- [:uid, :id, :to_token, :token, :to_sym].each do |method|
89
- return record.public_send method if record.respond_to? method
90
- end
91
-
92
- [:uid, :id, :token, :sym, :UID, :ID, :TOKEN, :SYM].each do |method|
93
- return record[method] if record.key? method
94
- return record[method.to_s] if record.key? method.to_s
95
- end if record.respond_to?(:key?) && record.respond_to?(:[])
96
-
98
+ return processor.record_id record if processor.respond_to? :record_id
97
99
  record.to_s.strip
98
100
  end
99
101
  end
@@ -7,7 +7,7 @@ module Processor
7
7
  attr_reader :processor
8
8
 
9
9
  def initialize(options = {})
10
- @messenger = options.fetch :messenger, Processor::Messenger.new(:info, STDOUT, self.class.name)
10
+ @messenger = options.fetch :messenger, Processor::Messenger.new(:info, STDERR, self.class.name)
11
11
  @processor = options.fetch :processor, nil
12
12
  end
13
13
 
@@ -17,7 +17,13 @@ module Processor
17
17
  begin
18
18
  thread_data_processor.process(thread_record)
19
19
  rescue StandardError => exception
20
- thread_data_processor.record_error thread_record, exception
20
+ command = catch(:command) do
21
+ thread_data_processor.record_error thread_record, exception
22
+ end
23
+
24
+ # NOTE: redo can not be moved into a method or block
25
+ # to break from records loop, use Exception
26
+ redo if command == :redo
21
27
  end
22
28
  end
23
29
 
@@ -2,7 +2,6 @@ require 'processor/runner'
2
2
  require 'processor/event_processor'
3
3
  require 'processor/process_runner/successive'
4
4
  require 'processor/process_runner/threads'
5
- require 'processor/subroutine/recursion'
6
5
 
7
6
  module Processor
8
7
  class Thread
@@ -1,3 +1,3 @@
1
1
  module Processor
2
- VERSION = "2.1.0"
2
+ VERSION = "2.2.0"
3
3
  end
data/lib/processor.rb CHANGED
@@ -13,6 +13,10 @@ require "processor/observer/null_observer"
13
13
  require "processor/process_runner/successive"
14
14
  require "processor/process_runner/threads"
15
15
 
16
+ require "processor/subroutine/counter"
17
+ require "processor/subroutine/recursion"
18
+ require "processor/subroutine/name"
19
+
16
20
  require "processor/runner"
17
21
  require "processor/thread"
18
22
 
data/processor.gemspec CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |gem|
9
9
  gem.version = Processor::VERSION
10
10
  gem.authors = ["Alexander Paramonov"]
11
11
  gem.email = ["alexander.n.paramonov@gmail.com"]
12
- gem.summary = %q{Universal processor for iteration over a collection with threads, logging and post processing}
12
+ gem.summary = %q{Universal processor for threaded collection iteration, logging and post processing}
13
13
  gem.description = %q{Processor is a tool that helps to iterate over collection and perform complex actions on a result. It is extremely useful in data migrations, report generation, etc. }
14
14
  gem.homepage = "http://github.com/AlexParamonov/processor"
15
15
  gem.license = "MIT"
@@ -20,7 +20,7 @@ Gem::Specification.new do |gem|
20
20
  gem.require_paths = ["lib"]
21
21
 
22
22
  gem.add_development_dependency "rake"
23
- gem.add_development_dependency "rspec", ">= 2.6"
23
+ gem.add_development_dependency "rspec", "~> 2.6"
24
24
  gem.add_development_dependency "pry" unless Processor::RUNNING_ON_CI
25
25
  gem.add_development_dependency "pry-plus" if "ruby" == RUBY_ENGINE && false == Processor::RUNNING_ON_CI
26
26
  gem.add_development_dependency "coveralls" if Processor::RUNNING_ON_CI
@@ -6,26 +6,30 @@ describe Processor::Data::BatchProcessor do
6
6
  it "should create and process records by batch" do
7
7
  processor = Processor::Data::BatchProcessor.new 2
8
8
 
9
- watcher = mock
10
- 5.times do
11
- watcher.should_receive(:created).ordered
12
- watcher.should_receive(:created).ordered
13
- watcher.should_receive(:processed).ordered
14
- watcher.should_receive(:processed).ordered
15
- end
9
+ records = (1..10).each_slice(2).map do |first_record, second_record|
10
+ record1 = mock("record_#{first_record}")
11
+ record2 = mock("record_#{second_record}")
12
+
13
+ record1.should_receive(:created).ordered
14
+ record2.should_receive(:created).ordered
15
+
16
+ record1.should_receive(:processed).ordered
17
+ record2.should_receive(:processed).ordered
18
+
19
+ [ record1, record2 ]
20
+ end.flatten
16
21
 
17
22
  query = Enumerator.new do |y|
18
- a = 1
19
- until a > 10
20
- watcher.created
21
- y << a
22
- a += 1
23
+ while records.any?
24
+ record = records.shift
25
+ record.created
26
+ y << record
23
27
  end
24
28
  end
25
29
 
26
30
  processor.stub(query: query)
27
31
  processor.records.each do |record|
28
- watcher.processed
32
+ record.processed
29
33
  end
30
34
  end
31
35
 
@@ -9,6 +9,15 @@ describe Processor::Observer::Logger do
9
9
  let(:messenger) { ::Logger.new("/dev/null") }
10
10
  let(:logger) { ::Logger.new("/dev/null") }
11
11
 
12
+ describe "record_id" do
13
+ it "uses processor.record_id if possible" do
14
+ record = double :record
15
+
16
+ expect(processor).to receive(:record_id).with(record).and_return 1
17
+ subject.send(:id_for, record).should eq 1
18
+ end
19
+ end
20
+
12
21
  describe "logger" do
13
22
  describe "as proc" do
14
23
  let(:external_logger) { stub }
@@ -37,6 +46,28 @@ describe Processor::Observer::Logger do
37
46
  ::Logger.should_receive(:new).and_return(ruby_logger)
38
47
  observer.logger.should eq ruby_logger
39
48
  end
49
+
50
+ it "sets log level to info by default" do
51
+ observer = subject
52
+ ::Logger.any_instance.should_receive(:level=).with(::Logger::INFO)
53
+ observer.logger
54
+ end
55
+
56
+ it "sets log level to user specified value" do
57
+ observer = described_class.new level: ::Logger::DEBUG
58
+ ::Logger.any_instance.should_receive(:level=).with(::Logger::DEBUG)
59
+ observer.logger
60
+ end
61
+ end
62
+
63
+ describe "hash" do
64
+ let(:logger) { { level: ::Logger::DEBUG } }
65
+
66
+ it "applies options to default logger" do
67
+ observer = subject
68
+ ::Logger.any_instance.should_receive(:level=).with(::Logger::DEBUG)
69
+ observer.logger
70
+ end
40
71
  end
41
72
  end
42
73
 
@@ -14,9 +14,9 @@ shared_examples_for "a records processor" do
14
14
  end
15
15
 
16
16
  describe "exception handling" do
17
- describe "processing a record raised StandardError" do
18
- let(:records) { 1..3 }
17
+ let(:records) { 1..3 }
19
18
 
19
+ describe "processing a record raised StandardError" do
20
20
  it "should continue processing" do
21
21
  processor.should_receive(:process).exactly(3).times.and_raise(StandardError)
22
22
  expect { process_runner.call processor }.to_not raise_error
@@ -35,5 +35,16 @@ shared_examples_for "a records processor" do
35
35
  expect { process_runner.call processor }.to raise_error(Exception)
36
36
  end
37
37
  end
38
+
39
+ describe "processing a record have requested a command run" do
40
+ it "should run redo command" do
41
+ processor.should_receive(:process).with(1).once.and_raise(StandardError)
42
+ processor.should_receive(:process).with(1).once
43
+ processor.should_receive(:process).exactly(2).times
44
+
45
+ processor.should_receive(:record_error).once.and_throw(:command, :redo)
46
+ process_runner.call processor
47
+ end
48
+ end
38
49
  end
39
50
  end
metadata CHANGED
@@ -1,20 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: processor
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
5
- prerelease:
4
+ version: 2.2.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Alexander Paramonov
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-06-08 00:00:00.000000000 Z
11
+ date: 2014-11-11 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: rake
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
17
  - - ! '>='
20
18
  - !ruby/object:Gem::Version
@@ -22,7 +20,6 @@ dependencies:
22
20
  type: :development
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
24
  - - ! '>='
28
25
  - !ruby/object:Gem::Version
@@ -30,23 +27,20 @@ dependencies:
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: rspec
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ! '>='
31
+ - - ~>
36
32
  - !ruby/object:Gem::Version
37
33
  version: '2.6'
38
34
  type: :development
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ! '>='
38
+ - - ~>
44
39
  - !ruby/object:Gem::Version
45
40
  version: '2.6'
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: pry
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
45
  - - ! '>='
52
46
  - !ruby/object:Gem::Version
@@ -54,7 +48,6 @@ dependencies:
54
48
  type: :development
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
52
  - - ! '>='
60
53
  - !ruby/object:Gem::Version
@@ -62,7 +55,6 @@ dependencies:
62
55
  - !ruby/object:Gem::Dependency
63
56
  name: pry-plus
64
57
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
58
  requirements:
67
59
  - - ! '>='
68
60
  - !ruby/object:Gem::Version
@@ -70,7 +62,6 @@ dependencies:
70
62
  type: :development
71
63
  prerelease: false
72
64
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
65
  requirements:
75
66
  - - ! '>='
76
67
  - !ruby/object:Gem::Version
@@ -140,35 +131,27 @@ files:
140
131
  homepage: http://github.com/AlexParamonov/processor
141
132
  licenses:
142
133
  - MIT
134
+ metadata: {}
143
135
  post_install_message:
144
136
  rdoc_options: []
145
137
  require_paths:
146
138
  - lib
147
139
  required_ruby_version: !ruby/object:Gem::Requirement
148
- none: false
149
140
  requirements:
150
141
  - - ! '>='
151
142
  - !ruby/object:Gem::Version
152
143
  version: '0'
153
- segments:
154
- - 0
155
- hash: 148591874566411659
156
144
  required_rubygems_version: !ruby/object:Gem::Requirement
157
- none: false
158
145
  requirements:
159
146
  - - ! '>='
160
147
  - !ruby/object:Gem::Version
161
148
  version: '0'
162
- segments:
163
- - 0
164
- hash: 148591874566411659
165
149
  requirements: []
166
150
  rubyforge_project:
167
- rubygems_version: 1.8.25
151
+ rubygems_version: 2.2.2
168
152
  signing_key:
169
- specification_version: 3
170
- summary: Universal processor for iteration over a collection with threads, logging
171
- and post processing
153
+ specification_version: 4
154
+ summary: Universal processor for threaded collection iteration, logging and post processing
172
155
  test_files:
173
156
  - spec/example_spec.rb
174
157
  - spec/processor/data/array_processor_spec.rb