processor 0.0.1 → 1.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
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