processor 0.0.1 → 1.0.0.alpha

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.
Files changed (35) hide show
  1. data/.travis.yml +4 -0
  2. data/README.md +46 -24
  3. data/example/migration.rb +3 -14
  4. data/lib/processor/data/array_processor.rb +11 -0
  5. data/lib/processor/data/batch_processor.rb +37 -0
  6. data/lib/processor/data/csv_processor.rb +39 -0
  7. data/lib/processor/data/null_processor.rb +26 -0
  8. data/lib/processor/data/solr_pages_processor.rb +30 -0
  9. data/lib/processor/data/solr_processor.rb +23 -0
  10. data/lib/processor/observer/logger.rb +3 -3
  11. data/lib/processor/process_runner/successive.rb +12 -0
  12. data/lib/processor/process_runner/threads.rb +51 -0
  13. data/lib/processor/runner.rb +4 -5
  14. data/lib/processor/thread.rb +7 -7
  15. data/lib/processor/version.rb +1 -1
  16. data/lib/processor.rb +13 -0
  17. data/processor.gemspec +3 -2
  18. data/spec/processor/data/array_processor_spec.rb +11 -0
  19. data/spec/processor/data/batch_processor_spec.rb +38 -0
  20. data/spec/processor/data/null_processor_spec.rb +9 -0
  21. data/spec/processor/process_runner/specs.rb +50 -0
  22. data/spec/processor/process_runner/successive_spec.rb +7 -0
  23. data/spec/processor/process_runner/threads_spec.rb +25 -0
  24. data/spec/processor/runner_spec.rb +10 -41
  25. data/spec/processor/thread_spec.rb +3 -3
  26. metadata +30 -24
  27. data/example/solr_migration.rb +0 -31
  28. data/example/solr_pages_migration.rb +0 -48
  29. data/lib/processor/data_processor.rb +0 -28
  30. data/lib/processor/records_processor/successive.rb +0 -20
  31. data/lib/processor/records_processor/threads.rb +0 -27
  32. data/spec/processor/data_processor_spec.rb +0 -20
  33. data/spec/processor/records_processor/specs.rb +0 -38
  34. data/spec/processor/records_processor/successive_spec.rb +0 -7
  35. data/spec/processor/records_processor/threads_spec.rb +0 -8
data/.travis.yml CHANGED
@@ -3,11 +3,15 @@ rvm:
3
3
  - 1.9.3
4
4
  - ruby-head
5
5
  - rbx-19mode
6
+ - jruby-19mode
7
+ - jruby-head
6
8
 
7
9
  matrix:
8
10
  allow_failures:
9
11
  - rvm: ruby-head
10
12
  - rvm: rbx-19mode
13
+ - rvm: jruby-19mode
14
+ - rvm: jruby-head
11
15
 
12
16
  before_install: gem install bundler --pre
13
17
 
data/README.md CHANGED
@@ -3,7 +3,10 @@ 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
- Universal processor for data migration
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.
8
+ Processor provide customisation for almost every part of it.
9
+
7
10
 
8
11
  Contents
9
12
  ---------
@@ -41,42 +44,61 @@ Requirements
41
44
 
42
45
  Usage
43
46
  ------------
44
- 1. Implement a `DataProcessor`. See `Processor::Example::Migration`, `Processor::Example::SolrMigration`, `Processor::Example::SolrPagesMigration`
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.
45
50
  1. Run your `DataProcessor`:
46
51
 
47
52
  ``` ruby
53
+ data_processor = UserLocationMigration.new
48
54
  thread = Processor::Thread.new data_processor
49
55
  thread.run_successive
50
56
  ```
51
- See `spec/processor/thread_spec.rb` and `spec/example_spec.rb` and `example` directory for other usage examples.
52
- Below are specs for most valuable parts of the gem.
53
-
54
- ``` rspec
55
- Processor::Thread
56
- should run a migration using provided block
57
- should run a migration successive
58
- should run a migration in threads
59
-
60
- Processor::DataProcessor
61
- should have a name equals to underscored class name
62
- should be done when there are 0 records to process
63
- should respond to done?
64
- should respond to fetch_records
65
- should respond to total_records
66
- should respond to process
67
-
68
- Processor::Observer::Logger
69
- accepts logger builder as parameter
70
- accepts logger as parameter
71
- use ruby Logger if no external logger provided
57
+ See `spec/processor/thread_spec.rb` and `spec/example_spec.rb` and
58
+ `example` directory for other usage examples.
59
+
60
+
61
+ It is recomended to wrap Processor::Thread in your classes like:
62
+
63
+ ```
64
+ WeeklyReport
65
+ TaxonomyMigration
66
+ UserDataImport
67
+ ```
68
+ to hide configuration of observers or use your own API to run
69
+ migrations:
70
+
72
71
  ```
72
+ weekly_report.create_and_deliver
73
+ user_data_import.import_from_csv(file)
74
+ etc.
75
+ ```
76
+
77
+ Sure, it is possible to use it raw, but please dont fear to add a
78
+ wrapper class for it:
79
+
80
+ ```
81
+ csv_data_processor = Processor::Data::CsvProcessor.new file
82
+ stdout_notifier = Processor::Observer::Logger.new(Logger.new(STDOUT))
83
+ logger_observer = Processor::Observer::Logger.new
84
+ Processor::Thread.new(
85
+ csv_data_processor,
86
+ stdout_notifier,
87
+ logger_observer,
88
+ email_notification_observer
89
+ ).run_in_threads 5
90
+ ```
91
+
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.
73
96
 
74
97
  Compatibility
75
98
  -------------
76
99
  tested with Ruby
77
100
 
78
101
  * 1.9.3
79
- * jruby-19mode
80
102
  * rbx-19mode
81
103
  * ruby-head
82
104
 
data/example/migration.rb CHANGED
@@ -1,29 +1,18 @@
1
- require 'processor/data_processor'
1
+ require 'processor/data/array_processor'
2
2
 
3
3
  module Processor
4
4
  module Example
5
- class Migration < DataProcessor
5
+ class Migration < Data::ArrayProcessor
6
6
  attr_reader :records
7
+
7
8
  def initialize(records)
8
9
  @records = records
9
10
  end
10
11
 
11
- def done?(records)
12
- records.count < 1
13
- end
14
-
15
12
  def process(record)
16
13
  record.do_something
17
14
  "OK"
18
15
  end
19
-
20
- def fetch_records
21
- records.shift(2)
22
- end
23
-
24
- def total_records
25
- records.count
26
- end
27
16
  end
28
17
  end
29
18
  end
@@ -0,0 +1,11 @@
1
+ require_relative 'null_processor'
2
+
3
+ module Processor
4
+ module Data
5
+ class ArrayProcessor < NullProcessor
6
+ def records
7
+ raise NotImplementedError
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,37 @@
1
+ require_relative 'null_processor'
2
+
3
+ module Processor
4
+ module Data
5
+ class BatchProcessor < NullProcessor
6
+ def initialize(batch_size = 10)
7
+ @batch_size = batch_size
8
+ end
9
+
10
+ def records
11
+ Enumerator.new do |result|
12
+ loop do
13
+ fetch_batch.each do |record|
14
+ result << record
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ def fetch_batch
21
+ @fetcher ||= query.each_slice(batch_size)
22
+ @fetcher.next
23
+ end
24
+
25
+ def total_records
26
+ @total_records ||= query.count
27
+ end
28
+
29
+ def query
30
+ raise NotImplementedError
31
+ end
32
+
33
+ private
34
+ attr_reader :batch_size
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,39 @@
1
+ require_relative 'batch_processor'
2
+
3
+ module Processor
4
+ module Data
5
+ class CsvProcessor < BatchProcessor
6
+ def initialize(file, csv_options = {})
7
+ @file = file
8
+ @separator = separator
9
+ @csv_options = {
10
+ col_sep: ";",
11
+ headers: true,
12
+ }.merge csv_options
13
+ end
14
+
15
+ def process(row)
16
+ raise NotImplementedError
17
+ end
18
+
19
+ def records
20
+ Enumerator.new do |result|
21
+ ::CSV.foreach(file, csv_options) do |record|
22
+ result << record
23
+ end
24
+ end
25
+ end
26
+
27
+ def total_records
28
+ @total_records ||= File.new(file).readlines.size
29
+ end
30
+
31
+ private
32
+ attr_reader :file, :separator, :csv_options
33
+
34
+ def fetch_field(field_name, row)
35
+ row[field_name].to_s.strip
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,26 @@
1
+ module Processor
2
+ module Data
3
+ class NullProcessor
4
+ def process(record)
5
+ # do nothing
6
+ end
7
+
8
+ def records
9
+ []
10
+ end
11
+
12
+ def total_records
13
+ @total_records ||= records.count
14
+ end
15
+
16
+ def name
17
+ # underscore a class name
18
+ self.class.name.to_s.
19
+ gsub(/::/, '_').
20
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
21
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
22
+ downcase
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,30 @@
1
+ require_relative "batch_processor"
2
+
3
+ module Processor
4
+ module Data
5
+ class SolrPagesProcessor < BatchProcessor
6
+ def process(record)
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def query(requested_page)
11
+ raise NotImplementedError
12
+ end
13
+
14
+ def fetch_batch
15
+ query(next_page).results
16
+ end
17
+
18
+ def total_records
19
+ @total_records ||= query(1).total
20
+ end
21
+
22
+ private
23
+ def next_page
24
+ @page ||= 0
25
+ @page += 1
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,23 @@
1
+ require_relative "batch_processor"
2
+
3
+ module Processor
4
+ module Data
5
+ class SolrProcessor < BatchProcessor
6
+ def process(record)
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def query
11
+ raise NotImplementedError
12
+ end
13
+
14
+ def fetch_batch
15
+ query.results
16
+ end
17
+
18
+ def total_records
19
+ @total_records ||= query.total
20
+ end
21
+ end
22
+ end
23
+ end
@@ -26,7 +26,7 @@ module Processor
26
26
  end
27
27
 
28
28
  def after_record_processing(record, result)
29
- logger.info "Successfully processed #{id_for record}: #{result}"
29
+ logger.info "Processed #{id_for record}: #{result}"
30
30
  end
31
31
 
32
32
  def processing_finished(processor)
@@ -39,7 +39,7 @@ module Processor
39
39
  end
40
40
 
41
41
  def processing_error(processor, exception)
42
- logger.fatal "Processing #{processor.name} failed: #{exception}"
42
+ logger.fatal "Processing #{processor.name} FAILED: #{exception.backtrace}"
43
43
  end
44
44
 
45
45
  private
@@ -79,7 +79,7 @@ module Processor
79
79
  return record[method.to_s] if record.key? method.to_s
80
80
  end if record.respond_to?(:key?) && record.respond_to?(:[])
81
81
 
82
- record.to_s
82
+ record.to_s.strip
83
83
  end
84
84
  end
85
85
  end
@@ -0,0 +1,12 @@
1
+ require_relative "threads"
2
+
3
+ module Processor
4
+ module ProcessRunner
5
+ class Successive < Threads
6
+ private
7
+ def new_thread(processor, record, &block)
8
+ block.call processor, record
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,51 @@
1
+ module Processor
2
+ module ProcessRunner
3
+ class Threads
4
+ def initialize(number_of_threads = 1)
5
+ @number_of_threads = number_of_threads
6
+ @threads = []
7
+ end
8
+
9
+ def call(processor, events, recursion_preventer)
10
+ join_threads
11
+
12
+ begin
13
+ processor.records.each do |record|
14
+ recursion_preventer.call
15
+ if threads_created >= number_of_threads then join_threads end
16
+
17
+ new_thread(processor, record) do |thread_data_processor, thread_record|
18
+ begin
19
+ events.register :before_record_processing, thread_record
20
+
21
+ result = thread_data_processor.process(thread_record)
22
+
23
+ events.register :after_record_processing, thread_record, result
24
+ rescue StandardError => exception
25
+ events.register :record_processing_error, thread_record, exception
26
+ end
27
+ end
28
+
29
+ end
30
+ ensure # join already created threads
31
+ join_threads
32
+ end
33
+ end
34
+
35
+ private
36
+ attr_reader :threads_created, :number_of_threads
37
+
38
+ def new_thread(processor, record, &block)
39
+ @threads << ::Thread.new(processor, record, &block)
40
+ @threads_created += 1
41
+ end
42
+
43
+ def join_threads
44
+ @threads.each(&:join)
45
+ @threads_created = 0
46
+ @threads = []
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -7,11 +7,10 @@ module Processor
7
7
  @events = events_registrator
8
8
  end
9
9
 
10
- def run(records_processor)
10
+ def run(process_runner)
11
11
  events.register :processing_started, processor
12
- until processor.done?(records = processor.fetch_records)
13
- records_processor.call records, processor, events, method(:recursion_preventer)
14
- end
12
+
13
+ process_runner.call processor, events, method(:recursion_preventer)
15
14
 
16
15
  events.register :processing_finished, processor
17
16
  rescue Exception => exception
@@ -33,7 +32,7 @@ module Processor
33
32
  end
34
33
 
35
34
  def max_records_to_process
36
- (processor.total_records * 1.1).round + 10
35
+ @max_records_to_process ||= (processor.total_records * 1.1).round + 10
37
36
  end
38
37
  end
39
38
  end
@@ -1,6 +1,6 @@
1
1
  require 'processor/runner'
2
- require 'processor/records_processor/successive'
3
- require 'processor/records_processor/threads'
2
+ require 'processor/process_runner/successive'
3
+ require 'processor/process_runner/threads'
4
4
 
5
5
  module Processor
6
6
  class Thread
@@ -8,16 +8,16 @@ module Processor
8
8
  @runner = Runner.new data_processor, EventsRegistrator.new(observers)
9
9
  end
10
10
 
11
- def run_as(&records_processor)
12
- runner.run records_processor
11
+ def run_as(&process_runner)
12
+ runner.run process_runner
13
13
  end
14
14
 
15
15
  def run_successive
16
- runner.run RecordsProcessor::Successive.new
16
+ runner.run ProcessRunner::Successive.new
17
17
  end
18
18
 
19
- def run_in_threads
20
- runner.run RecordsProcessor::Threads.new
19
+ def run_in_threads(number_of_threads = 2)
20
+ runner.run ProcessRunner::Threads.new number_of_threads
21
21
  end
22
22
 
23
23
  private
@@ -1,3 +1,3 @@
1
1
  module Processor
2
- VERSION = "0.0.1"
2
+ VERSION = "1.0.0.alpha"
3
3
  end
data/lib/processor.rb CHANGED
@@ -1,4 +1,17 @@
1
1
  require "processor/version"
2
2
 
3
+ require "processor/data/array_processor"
4
+ require "processor/data/batch_processor"
5
+ require "processor/data/null_processor"
6
+
7
+ require "processor/observer/logger"
8
+ require "processor/observer/null_observer"
9
+
10
+ require "processor/process_runner/successive"
11
+ require "processor/process_runner/threads"
12
+
13
+ require "processor/runner"
14
+ require "processor/thread"
15
+
3
16
  module Processor
4
17
  end
data/processor.gemspec CHANGED
@@ -8,8 +8,9 @@ Gem::Specification.new do |gem|
8
8
  gem.version = Processor::VERSION
9
9
  gem.authors = ["Alexander Paramonov"]
10
10
  gem.email = ["alexander.n.paramonov@gmail.com"]
11
- gem.summary = %q{Process records one by one}
12
- gem.description = %q{Universal processor for data migration}
11
+ gem.summary = %q{Universal processor for data migration and reports generation.}
12
+ gem.description = %q{Processor could execute any DataProcessor you specify and log entire process.
13
+ You may add own observers for monitoring background tasks on even send email to bussiness with generated report.}
13
14
  gem.homepage = "http://github.com/AlexParamonov/processor"
14
15
  gem.license = "MIT"
15
16
 
@@ -0,0 +1,11 @@
1
+ require 'spec_helper_lite'
2
+ require 'processor/data/array_processor'
3
+
4
+ describe Processor::Data::ArrayProcessor do
5
+ it "should have total records count equals to count of records" do
6
+ records = *1..5
7
+ subject.stub(records: records)
8
+ subject.total_records.should eq 5
9
+ end
10
+ end
11
+
@@ -0,0 +1,38 @@
1
+ require "spec_helper_lite"
2
+
3
+ require "processor/data/batch_processor"
4
+
5
+ describe Processor::Data::BatchProcessor do
6
+ it "should create and process records by batch" do
7
+ processor = Processor::Data::BatchProcessor.new 2
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
16
+
17
+ query = Enumerator.new do |y|
18
+ a = 1
19
+ loop do
20
+ break if a > 10
21
+ watcher.created
22
+ y << a
23
+ a += 1
24
+ end
25
+ end
26
+
27
+ processor.stub(query: query)
28
+ processor.records.each do |record|
29
+ watcher.processed
30
+ end
31
+ end
32
+
33
+ it "should have total records count equals to count of query" do
34
+ query = 1..5
35
+ subject.stub(query: query)
36
+ subject.total_records.should eq 5
37
+ end
38
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper_lite'
2
+ require 'processor/data/null_processor'
3
+
4
+ describe Processor::Data::NullProcessor do
5
+ it "should have a name equals to underscored class name" do
6
+ subject.name.should eq "processor_data_null_processor"
7
+ end
8
+ end
9
+
@@ -0,0 +1,50 @@
1
+ shared_examples_for "a records processor" do
2
+ let(:process_runner) { described_class.new }
3
+ let(:records) { 1..2 }
4
+ let(:processor) { stub.tap { |p| p.stub(records: records) } }
5
+
6
+ let(:no_recursion_preventer) { Proc.new{} }
7
+ let(:no_events) { stub.as_null_object }
8
+
9
+ it "should fetch records from processor" do
10
+ processor.should_receive(:records).and_return([])
11
+ process_runner.call processor, no_events, no_recursion_preventer
12
+ end
13
+
14
+ it "should send each found record to processor" do
15
+ records.each { |record| processor.should_receive(:process).with(record) }
16
+ process_runner.call processor, no_events, no_recursion_preventer
17
+ end
18
+
19
+ describe "exception handling" do
20
+ describe "processing a record raised StandardError" do
21
+ it "should continue processing" do
22
+ processor.should_receive(:process).twice.and_raise(StandardError)
23
+ expect { process_runner.call processor, no_events, no_recursion_preventer }.to_not raise_error
24
+ end
25
+
26
+ it "should register a record_processing_error event" do
27
+ event = mock.tap { |event| event.should_receive(:trigger).any_number_of_times }
28
+
29
+ events_registrator = stub
30
+ events_registrator.should_receive(:register) do |event_name, failed_record, exception|
31
+ next if event_name != :record_processing_error
32
+ event_name.should eq :record_processing_error
33
+ exception.should be_a StandardError
34
+ event.trigger
35
+ end.any_number_of_times
36
+
37
+ processor.stub(:process).and_raise(StandardError)
38
+
39
+ process_runner.call processor, events_registrator, no_recursion_preventer rescue nil
40
+ end
41
+ end
42
+
43
+ describe "processing a record raised Exception" do
44
+ it "should break processing" do
45
+ processor.should_receive(:process).once.and_raise(Exception)
46
+ expect { process_runner.call processor, no_events, no_recursion_preventer }.to raise_error(Exception)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper_lite'
2
+ require_relative 'specs'
3
+ require 'processor/process_runner/successive'
4
+
5
+ describe Processor::ProcessRunner::Successive do
6
+ it_behaves_like "a records processor"
7
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper_lite'
2
+ require_relative 'specs'
3
+ require 'processor/process_runner/threads'
4
+
5
+ describe Processor::ProcessRunner::Threads do
6
+ let(:no_recursion_preventer) { Proc.new{} }
7
+ let(:no_events) { stub.as_null_object }
8
+ it_behaves_like "a records processor"
9
+
10
+ it "should run in defined number of threads" do
11
+ process_runner = Processor::ProcessRunner::Threads.new 5
12
+
13
+ process_runner.should_receive(:join_threads).once.ordered.and_call_original
14
+ process_runner.should_receive(:new_thread).exactly(5).times.ordered.and_call_original
15
+ process_runner.should_receive(:join_threads).once.ordered.and_call_original
16
+ process_runner.should_receive(:new_thread).exactly(4).times.ordered.and_call_original
17
+ process_runner.should_receive(:join_threads).once.ordered.and_call_original
18
+
19
+ processor = mock
20
+ processor.stub(records: 1..9)
21
+ processor.should_receive(:process).exactly(9).times
22
+
23
+ process_runner.call(processor, no_events, no_recursion_preventer)
24
+ end
25
+ end
@@ -5,25 +5,10 @@ describe Processor::Runner do
5
5
  let(:runner) { Processor::Runner.new(processor, events_registrator) }
6
6
  let(:processor) { stub }
7
7
  let(:events_registrator) { stub.as_null_object }
8
- let(:no_records_processor) { Proc.new{} }
9
-
10
- it "should fetch records from processor till it'll be done" do
11
- processor.stub(:done?).and_return(false, false, true)
12
- processor.should_receive(:fetch_records).exactly(3).times.and_return([])
13
- runner.run no_records_processor
14
- end
8
+ let(:no_process_runner) { Proc.new{} }
15
9
 
16
10
  describe "exception handling" do
17
- let(:record) { stub }
18
- before(:each) do
19
- processor.stub(:fetch_records).and_return([record], [record], [])
20
- end
21
-
22
11
  describe "processing records raised" do
23
- before(:each) do
24
- processor.stub(done?: false)
25
- end
26
-
27
12
  it "should break processing and rerise" do
28
13
  expect do
29
14
  runner.run Proc.new { raise RuntimeError }
@@ -36,21 +21,6 @@ describe Processor::Runner do
36
21
  end
37
22
  end
38
23
 
39
- describe "fetching records raised" do
40
- it "should break processing and rerise Exception" do
41
- processor.stub(:fetch_records).and_raise(RuntimeError)
42
- processor.should_not_receive(:process)
43
- expect { runner.run no_records_processor }.to raise_error(RuntimeError)
44
- end
45
-
46
- it "should register a processing_error" do
47
- register_processing_error_event mock.tap { |event| event.should_receive :trigger }
48
-
49
- processor.stub(:fetch_records).and_raise(RuntimeError)
50
- runner.run no_records_processor rescue nil
51
- end
52
- end
53
-
54
24
  private
55
25
  def register_processing_error_event(event)
56
26
  # Check that processing_error event was register
@@ -66,22 +36,21 @@ describe Processor::Runner do
66
36
 
67
37
  describe "recursion" do
68
38
  before(:each) do
69
- processor.stub(done?: false)
70
39
  processor.stub(total_records: 100)
71
- processor.stub(fetch_records: 1..3)
40
+ processor.stub(records: 1..Float::INFINITY)
72
41
  end
73
42
 
74
43
  it "should not fall into recursion" do
75
44
  processor.should_receive(:process).at_most(1000).times
76
45
 
77
46
  expect do
78
- records_processor = Proc.new do |records, records_processor, events, recursion_preventer|
79
- records.each do |record|
47
+ process_runner = Proc.new do |data_processor, events, recursion_preventer|
48
+ data_processor.records.each do |record|
80
49
  recursion_preventer.call
81
- records_processor.process record
50
+ data_processor.process record
82
51
  end
83
52
  end
84
- runner.run records_processor
53
+ runner.run process_runner
85
54
  end.to raise_error(Exception, /Processing fall into recursion/)
86
55
  end
87
56
 
@@ -89,13 +58,13 @@ describe Processor::Runner do
89
58
  processor.should_receive(:process).exactly(120).times
90
59
 
91
60
  expect do
92
- records_processor = Proc.new do |records, records_processor, events, recursion_preventer|
93
- records.each do |record|
61
+ process_runner = Proc.new do |data_processor, events, recursion_preventer|
62
+ data_processor.records.each do |record|
94
63
  recursion_preventer.call
95
- processor.process record
64
+ data_processor.process record
96
65
  end
97
66
  end
98
- runner.run records_processor
67
+ runner.run process_runner
99
68
  end.to raise_error(Exception, /Processing fall into recursion/)
100
69
  end
101
70
  end
@@ -13,8 +13,8 @@ describe Processor::Thread do
13
13
 
14
14
  it "should run a migration using provided block" do
15
15
  thread = Processor::Thread.new @migration
16
- thread.run_as do |records, processor, *|
17
- records.each do |record|
16
+ thread.run_as do |processor, *|
17
+ processor.records.each do |record|
18
18
  processor.process record
19
19
  end
20
20
  end
@@ -30,7 +30,7 @@ describe Processor::Thread do
30
30
  thread.run_in_threads
31
31
  end
32
32
 
33
- pending "should run a migration in specifien number of threads" do
33
+ it "should run a migration in specifien number of threads" do
34
34
  thread = Processor::Thread.new @migration
35
35
  thread.run_in_threads 3
36
36
  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: 0.0.1
5
- prerelease:
4
+ version: 1.0.0.alpha
5
+ prerelease: 6
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-11 00:00:00.000000000 Z
12
+ date: 2013-05-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -59,7 +59,9 @@ dependencies:
59
59
  - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
- description: Universal processor for data migration
62
+ description: ! "Processor could execute any DataProcessor you specify and log entire
63
+ process.\n You may add own observers for monitoring background tasks on even send
64
+ email to bussiness with generated report."
63
65
  email:
64
66
  - alexander.n.paramonov@gmail.com
65
67
  executables: []
@@ -78,26 +80,31 @@ files:
78
80
  - example/migration.rb
79
81
  - example/migrator.rb
80
82
  - example/observer/progress_bar.rb
81
- - example/solr_migration.rb
82
- - example/solr_pages_migration.rb
83
83
  - lib/processor.rb
84
- - lib/processor/data_processor.rb
84
+ - lib/processor/data/array_processor.rb
85
+ - lib/processor/data/batch_processor.rb
86
+ - lib/processor/data/csv_processor.rb
87
+ - lib/processor/data/null_processor.rb
88
+ - lib/processor/data/solr_pages_processor.rb
89
+ - lib/processor/data/solr_processor.rb
85
90
  - lib/processor/events_registrator.rb
86
91
  - lib/processor/observer/logger.rb
87
92
  - lib/processor/observer/null_observer.rb
88
- - lib/processor/records_processor/successive.rb
89
- - lib/processor/records_processor/threads.rb
93
+ - lib/processor/process_runner/successive.rb
94
+ - lib/processor/process_runner/threads.rb
90
95
  - lib/processor/runner.rb
91
96
  - lib/processor/thread.rb
92
97
  - lib/processor/version.rb
93
98
  - processor.gemspec
94
99
  - spec/example_spec.rb
95
- - spec/processor/data_processor_spec.rb
100
+ - spec/processor/data/array_processor_spec.rb
101
+ - spec/processor/data/batch_processor_spec.rb
102
+ - spec/processor/data/null_processor_spec.rb
96
103
  - spec/processor/events_registrator_spec.rb
97
104
  - spec/processor/observer/logger_spec.rb
98
- - spec/processor/records_processor/specs.rb
99
- - spec/processor/records_processor/successive_spec.rb
100
- - spec/processor/records_processor/threads_spec.rb
105
+ - spec/processor/process_runner/specs.rb
106
+ - spec/processor/process_runner/successive_spec.rb
107
+ - spec/processor/process_runner/threads_spec.rb
101
108
  - spec/processor/runner_spec.rb
102
109
  - spec/processor/thread_spec.rb
103
110
  - spec/spec_helper_lite.rb
@@ -116,30 +123,29 @@ required_ruby_version: !ruby/object:Gem::Requirement
116
123
  version: '0'
117
124
  segments:
118
125
  - 0
119
- hash: 4470760754938067473
126
+ hash: -1752503773457190967
120
127
  required_rubygems_version: !ruby/object:Gem::Requirement
121
128
  none: false
122
129
  requirements:
123
- - - ! '>='
130
+ - - ! '>'
124
131
  - !ruby/object:Gem::Version
125
- version: '0'
126
- segments:
127
- - 0
128
- hash: 4470760754938067473
132
+ version: 1.3.1
129
133
  requirements: []
130
134
  rubyforge_project:
131
135
  rubygems_version: 1.8.25
132
136
  signing_key:
133
137
  specification_version: 3
134
- summary: Process records one by one
138
+ summary: Universal processor for data migration and reports generation.
135
139
  test_files:
136
140
  - spec/example_spec.rb
137
- - spec/processor/data_processor_spec.rb
141
+ - spec/processor/data/array_processor_spec.rb
142
+ - spec/processor/data/batch_processor_spec.rb
143
+ - spec/processor/data/null_processor_spec.rb
138
144
  - spec/processor/events_registrator_spec.rb
139
145
  - spec/processor/observer/logger_spec.rb
140
- - spec/processor/records_processor/specs.rb
141
- - spec/processor/records_processor/successive_spec.rb
142
- - spec/processor/records_processor/threads_spec.rb
146
+ - spec/processor/process_runner/specs.rb
147
+ - spec/processor/process_runner/successive_spec.rb
148
+ - spec/processor/process_runner/threads_spec.rb
143
149
  - spec/processor/runner_spec.rb
144
150
  - spec/processor/thread_spec.rb
145
151
  - spec/spec_helper_lite.rb
@@ -1,31 +0,0 @@
1
- require 'processor/data_processor'
2
-
3
- module Processor
4
- module Example
5
- class SolrMigration < DataProcessor
6
- def process(user)
7
- user.set_contact_method "Address book"
8
- user.save!
9
- end
10
-
11
- def fetch_records
12
- query.results
13
- end
14
-
15
- def total_records
16
- @total_records ||= query.total
17
- end
18
-
19
- private
20
- # query will return 0 records when all users'll be processed successfully
21
- def query
22
- User.search {
23
- fulltext "My company"
24
- with :title, "Manager"
25
- with :contact_method, "Direct contact"
26
- paginate page: 1, per_page: 10
27
- }
28
- end
29
- end
30
- end
31
- end
@@ -1,48 +0,0 @@
1
- require 'processor/data_processor'
2
-
3
- module Processor
4
- module Example
5
- class SolrPagesMigration < DataProcessor
6
- def done?(records)
7
- # Optional custom check for migration to be done.
8
-
9
- # your custom check
10
-
11
- # use a default check if you like
12
- super
13
- end
14
-
15
- def process(user)
16
- user.set_contact_method "Address book"
17
- user.save!
18
- end
19
-
20
- def fetch_records
21
- query(next_page).results
22
- end
23
-
24
- def total_records
25
- @total_records ||= query(1).total
26
- end
27
-
28
- # optional name to use in observers.
29
- def name
30
- "my_company_users_contact_method_migration"
31
- end
32
-
33
- private
34
- def query(requested_page)
35
- User.search {
36
- fulltext "My company"
37
- paginate page: requested_page, per_page: 10
38
- }
39
- end
40
-
41
- def next_page
42
- @page ||= 0
43
- @page += 1
44
- end
45
- end
46
- end
47
- end
48
-
@@ -1,28 +0,0 @@
1
- module Processor
2
- class DataProcessor
3
- def done?(records)
4
- records.count < 1
5
- end
6
-
7
- def process(record)
8
- raise NotImplementedError
9
- end
10
-
11
- def fetch_records
12
- raise NotImplementedError
13
- end
14
-
15
- def total_records
16
- raise NotImplementedError
17
- end
18
-
19
- def name
20
- # underscore a class name
21
- self.class.name.to_s.
22
- gsub(/::/, '_').
23
- gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
24
- gsub(/([a-z\d])([A-Z])/,'\1_\2').
25
- downcase
26
- end
27
- end
28
- end
@@ -1,20 +0,0 @@
1
- module Processor
2
- module RecordsProcessor
3
- class Successive
4
- def call(records, processor, events, recursion_preventer)
5
- records.each do |record|
6
- recursion_preventer.call
7
- begin
8
- events.register :before_record_processing, record
9
-
10
- result = processor.process(record)
11
-
12
- events.register :after_record_processing, record, result
13
- rescue RuntimeError => exception
14
- events.register :record_processing_error, record, exception
15
- end
16
- end
17
- end
18
- end
19
- end
20
- end
@@ -1,27 +0,0 @@
1
- module Processor
2
- module RecordsProcessor
3
- class Threads
4
- def call(records, processor, events, recursion_preventer)
5
- threads = []
6
- begin
7
- records.each do |record|
8
- recursion_preventer.call
9
- threads << ::Thread.new(processor, record) do |thread_data_processor, thread_record|
10
- begin
11
- events.register :before_record_processing, thread_record
12
-
13
- result = thread_data_processor.process(thread_record)
14
-
15
- events.register :after_record_processing, thread_record, result
16
- rescue RuntimeError => exception
17
- events.register :record_processing_error, thread_record, exception
18
- end
19
- end
20
- end
21
- ensure # join already created threads even if recursion was detected
22
- threads.each(&:join)
23
- end
24
- end
25
- end
26
- end
27
- end
@@ -1,20 +0,0 @@
1
- require 'spec_helper_lite'
2
- require 'processor/data_processor'
3
-
4
- describe Processor::DataProcessor do
5
- it "should have a name equals to underscored class name" do
6
- subject.name.should eq "processor_data_processor"
7
- end
8
-
9
- it "should be done when there are 0 records to process" do
10
- records = []
11
- subject.done?(records).should be true
12
- end
13
-
14
- %w[done? fetch_records total_records process].each do |method_name|
15
- it "should respond to #{method_name}" do
16
- subject.should respond_to method_name
17
- end
18
- end
19
- end
20
-
@@ -1,38 +0,0 @@
1
- shared_examples_for "a records processor" do
2
- let(:records_processor) { described_class.new }
3
- let(:records) { 1..2 }
4
- let(:processor) { stub }
5
-
6
- let(:no_recursion_preventer) { Proc.new{} }
7
- let(:no_events) { stub.as_null_object }
8
-
9
- it "should send each found record to processor" do
10
- records.each { |record| processor.should_receive(:process).with(record) }
11
- records_processor.call records, processor, no_events, no_recursion_preventer
12
- end
13
-
14
- describe "exception handling" do
15
- describe "processing a record raised RuntimeError" do
16
- it "should continue processing" do
17
- processor.should_receive(:process).twice.and_raise(RuntimeError)
18
- expect { records_processor.call records, processor, no_events, no_recursion_preventer }.to_not raise_error
19
- end
20
-
21
- it "should register a record_processing_error event" do
22
- event = mock.tap { |event| event.should_receive(:trigger).any_number_of_times }
23
-
24
- events_registrator = stub
25
- events_registrator.should_receive(:register) do |event_name, failed_record, exception|
26
- next if event_name != :record_processing_error
27
- event_name.should eq :record_processing_error
28
- exception.should be_a RuntimeError
29
- event.trigger
30
- end.any_number_of_times
31
-
32
- processor.stub(:process).and_raise(RuntimeError)
33
-
34
- records_processor.call records, processor, events_registrator, no_recursion_preventer rescue nil
35
- end
36
- end
37
- end
38
- end
@@ -1,7 +0,0 @@
1
- require 'spec_helper_lite'
2
- require_relative 'specs'
3
- require 'processor/records_processor/successive'
4
-
5
- describe Processor::RecordsProcessor::Successive do
6
- it_behaves_like "a records processor"
7
- end
@@ -1,8 +0,0 @@
1
- require 'spec_helper_lite'
2
- require_relative 'specs'
3
- require 'processor/records_processor/threads'
4
-
5
- describe Processor::RecordsProcessor::Threads do
6
- it_behaves_like "a records processor"
7
- it "should run in defined number of threads"
8
- end