qwirk 0.0.1

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 (154) hide show
  1. data/History.md +7 -0
  2. data/LICENSE.txt +201 -0
  3. data/README.md +180 -0
  4. data/Rakefile +34 -0
  5. data/examples/README +1 -0
  6. data/examples/activemq.xml +84 -0
  7. data/examples/advanced_requestor/README.md +15 -0
  8. data/examples/advanced_requestor/base_request_worker.rb +18 -0
  9. data/examples/advanced_requestor/char_count_worker.rb +16 -0
  10. data/examples/advanced_requestor/config.ru +24 -0
  11. data/examples/advanced_requestor/exception_raiser_worker.rb +17 -0
  12. data/examples/advanced_requestor/length_worker.rb +14 -0
  13. data/examples/advanced_requestor/print_worker.rb +14 -0
  14. data/examples/advanced_requestor/publisher.rb +49 -0
  15. data/examples/advanced_requestor/qwirk.yml +16 -0
  16. data/examples/advanced_requestor/reverse_worker.rb +14 -0
  17. data/examples/advanced_requestor/triple_worker.rb +14 -0
  18. data/examples/batch/my_batch_worker.rb +30 -0
  19. data/examples/batch/my_line_worker.rb +8 -0
  20. data/examples/qwirk.yml +20 -0
  21. data/examples/requestor/README.md +13 -0
  22. data/examples/requestor/config.ru +13 -0
  23. data/examples/requestor/qwirk_persist.yml +5 -0
  24. data/examples/requestor/requestor.rb +68 -0
  25. data/examples/requestor/reverse_echo_worker.rb +15 -0
  26. data/examples/setup.rb +13 -0
  27. data/examples/shared/README.md +24 -0
  28. data/examples/shared/config.ru +13 -0
  29. data/examples/shared/publisher.rb +49 -0
  30. data/examples/shared/qwirk_persist.yml +5 -0
  31. data/examples/shared/shared_worker.rb +16 -0
  32. data/examples/simple/README +53 -0
  33. data/examples/simple/bar_worker.rb +10 -0
  34. data/examples/simple/baz_worker.rb +10 -0
  35. data/examples/simple/config.ru +14 -0
  36. data/examples/simple/publisher.rb +49 -0
  37. data/examples/simple/qwirk_persist.yml +4 -0
  38. data/examples/simple/tmp/kahadb/db-1.log +0 -0
  39. data/examples/simple/tmp/kahadb/db.data +0 -0
  40. data/examples/simple/tmp/kahadb/db.redo +0 -0
  41. data/examples/task/README +47 -0
  42. data/examples/task/config.ru +14 -0
  43. data/examples/task/foo_worker.rb +10 -0
  44. data/examples/task/messages.out +1000 -0
  45. data/examples/task/publisher.rb +25 -0
  46. data/examples/task/qwirk_persist.yml +5 -0
  47. data/examples/task/task.rb +36 -0
  48. data/lib/qwirk.rb +63 -0
  49. data/lib/qwirk/adapter.rb +45 -0
  50. data/lib/qwirk/base_worker.rb +96 -0
  51. data/lib/qwirk/batch.rb +4 -0
  52. data/lib/qwirk/batch/acquire_file_strategy.rb +47 -0
  53. data/lib/qwirk/batch/active_record.rb +3 -0
  54. data/lib/qwirk/batch/active_record/batch_job.rb +111 -0
  55. data/lib/qwirk/batch/active_record/failed_record.rb +5 -0
  56. data/lib/qwirk/batch/active_record/outstanding_record.rb +6 -0
  57. data/lib/qwirk/batch/file_status_strategy.rb +86 -0
  58. data/lib/qwirk/batch/file_worker.rb +228 -0
  59. data/lib/qwirk/batch/job_status.rb +29 -0
  60. data/lib/qwirk/batch/parse_file_strategy.rb +48 -0
  61. data/lib/qwirk/engine.rb +9 -0
  62. data/lib/qwirk/loggable.rb +23 -0
  63. data/lib/qwirk/manager.rb +140 -0
  64. data/lib/qwirk/marshal_strategy.rb +74 -0
  65. data/lib/qwirk/marshal_strategy/bson.rb +37 -0
  66. data/lib/qwirk/marshal_strategy/json.rb +37 -0
  67. data/lib/qwirk/marshal_strategy/none.rb +26 -0
  68. data/lib/qwirk/marshal_strategy/ruby.rb +26 -0
  69. data/lib/qwirk/marshal_strategy/string.rb +25 -0
  70. data/lib/qwirk/marshal_strategy/yaml.rb +25 -0
  71. data/lib/qwirk/publish_handle.rb +170 -0
  72. data/lib/qwirk/publisher.rb +67 -0
  73. data/lib/qwirk/queue_adapter.rb +3 -0
  74. data/lib/qwirk/queue_adapter/active_mq.rb +13 -0
  75. data/lib/qwirk/queue_adapter/active_mq/publisher.rb +12 -0
  76. data/lib/qwirk/queue_adapter/active_mq/worker_config.rb +16 -0
  77. data/lib/qwirk/queue_adapter/in_mem.rb +7 -0
  78. data/lib/qwirk/queue_adapter/in_mem/factory.rb +45 -0
  79. data/lib/qwirk/queue_adapter/in_mem/publisher.rb +98 -0
  80. data/lib/qwirk/queue_adapter/in_mem/queue.rb +88 -0
  81. data/lib/qwirk/queue_adapter/in_mem/reply_queue.rb +56 -0
  82. data/lib/qwirk/queue_adapter/in_mem/topic.rb +48 -0
  83. data/lib/qwirk/queue_adapter/in_mem/worker.rb +63 -0
  84. data/lib/qwirk/queue_adapter/in_mem/worker_config.rb +59 -0
  85. data/lib/qwirk/queue_adapter/jms.rb +50 -0
  86. data/lib/qwirk/queue_adapter/jms/connection.rb +42 -0
  87. data/lib/qwirk/queue_adapter/jms/consumer.rb +37 -0
  88. data/lib/qwirk/queue_adapter/jms/publisher.rb +126 -0
  89. data/lib/qwirk/queue_adapter/jms/worker.rb +89 -0
  90. data/lib/qwirk/queue_adapter/jms/worker_config.rb +38 -0
  91. data/lib/qwirk/remote_exception.rb +42 -0
  92. data/lib/qwirk/request_worker.rb +62 -0
  93. data/lib/qwirk/task.rb +177 -0
  94. data/lib/qwirk/task.rb.sav +194 -0
  95. data/lib/qwirk/version.rb +3 -0
  96. data/lib/qwirk/worker.rb +222 -0
  97. data/lib/qwirk/worker_config.rb +187 -0
  98. data/lib/rails/generators/qwirk/qwirk_generator.rb +82 -0
  99. data/lib/rails/generators/qwirk/templates/initializer.rb +6 -0
  100. data/lib/rails/generators/qwirk/templates/migration.rb +9 -0
  101. data/lib/rails/generators/qwirk/templates/schema.rb +28 -0
  102. data/lib/rails/railties/tasks.rake +8 -0
  103. data/lib/tasks/qwirk_tasks.rake +4 -0
  104. data/test/base_test.rb +248 -0
  105. data/test/database.yml +14 -0
  106. data/test/dummy/Rakefile +7 -0
  107. data/test/dummy/app/controllers/application_controller.rb +3 -0
  108. data/test/dummy/app/helpers/application_helper.rb +2 -0
  109. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  110. data/test/dummy/config.ru +4 -0
  111. data/test/dummy/config/application.rb +45 -0
  112. data/test/dummy/config/boot.rb +10 -0
  113. data/test/dummy/config/database.yml +22 -0
  114. data/test/dummy/config/environment.rb +5 -0
  115. data/test/dummy/config/environments/development.rb +26 -0
  116. data/test/dummy/config/environments/production.rb +49 -0
  117. data/test/dummy/config/environments/test.rb +35 -0
  118. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  119. data/test/dummy/config/initializers/inflections.rb +10 -0
  120. data/test/dummy/config/initializers/mime_types.rb +5 -0
  121. data/test/dummy/config/initializers/secret_token.rb +7 -0
  122. data/test/dummy/config/initializers/session_store.rb +8 -0
  123. data/test/dummy/config/locales/en.yml +5 -0
  124. data/test/dummy/config/routes.rb +58 -0
  125. data/test/dummy/log/development.log +0 -0
  126. data/test/dummy/log/production.log +0 -0
  127. data/test/dummy/log/server.log +0 -0
  128. data/test/dummy/log/test.log +0 -0
  129. data/test/dummy/public/404.html +26 -0
  130. data/test/dummy/public/422.html +26 -0
  131. data/test/dummy/public/500.html +26 -0
  132. data/test/dummy/public/favicon.ico +0 -0
  133. data/test/dummy/public/javascripts/application.js +2 -0
  134. data/test/dummy/public/javascripts/controls.js +965 -0
  135. data/test/dummy/public/javascripts/dragdrop.js +974 -0
  136. data/test/dummy/public/javascripts/effects.js +1123 -0
  137. data/test/dummy/public/javascripts/prototype.js +6001 -0
  138. data/test/dummy/public/javascripts/rails.js +191 -0
  139. data/test/dummy/script/rails +6 -0
  140. data/test/integration/navigation_test.rb +7 -0
  141. data/test/jms.yml +9 -0
  142. data/test/jms_fail_test.rb +149 -0
  143. data/test/jms_requestor_block_test.rb +278 -0
  144. data/test/jms_requestor_test.rb +238 -0
  145. data/test/jms_test.rb +287 -0
  146. data/test/marshal_strategy_test.rb +62 -0
  147. data/test/support/integration_case.rb +5 -0
  148. data/test/test_helper.rb +7 -0
  149. data/test/test_helper.rbold +22 -0
  150. data/test/test_helper_active_record.rb +61 -0
  151. data/test/unit/qwirk/batch/acquire_file_strategy_test.rb +101 -0
  152. data/test/unit/qwirk/batch/active_record/batch_job_test.rb +35 -0
  153. data/test/unit/qwirk/batch/parse_file_strategy_test.rb +49 -0
  154. metadata +366 -0
@@ -0,0 +1,25 @@
1
+ require 'rumx'
2
+ require 'qwirk'
3
+
4
+ class Publisher
5
+ include Rumx::Bean
6
+
7
+ bean_attr_reader :tasks, :list, 'Tasks', :list_type => :bean
8
+
9
+ bean_operation :perform_task, :void, 'Perform task which will send <count> messages to the worker and write the output to <output_file>', [
10
+ [ :task_id, :string, 'Id for this task ', 'task1' ],
11
+ [ :count, :integer, 'Count of messages', 1000 ],
12
+ [ :message, :string, 'String portion of the message to send', 'M' ],
13
+ [ :sleep_time, :float, 'Time to sleep between messages', 0.0 ],
14
+ [ :output_file, :string, 'Output file to write returned messages to', 'messages.out' ]
15
+ ]
16
+
17
+ def initialize(adapter_key)
18
+ @adapter_key = adapter_key
19
+ @tasks = []
20
+ end
21
+
22
+ def perform_task(task_id, count, message, sleep_time, output_file)
23
+ @tasks << Task.new(@adapter_key, task_id, count, message, sleep_time, output_file)
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ ---
2
+ Foo:
3
+ :min_count: 2
4
+ :max_count: 100
5
+ :sleep_time: 0.1
@@ -0,0 +1,36 @@
1
+ require 'rumx'
2
+ require 'qwirk'
3
+
4
+ class Task
5
+ include Qwirk::Task
6
+
7
+ def initialize(adapter_key, task_id, total_count, message, sleep_time, output_file)
8
+ publisher = Qwirk[adapter_key].create_publisher(:queue_name => 'Foo', :marshal => :bson, :persistent => false, :response => {:time_to_live => 10000, :marshal => :string})
9
+ super(publisher, task_id, total_count)
10
+ @out = File.open(output_file, 'w')
11
+ (1..total_count).each do |i|
12
+ obj = {'count' => i, 'message' => message}
13
+ #puts "Publishing object: #{obj.inspect}"
14
+ publish(obj)
15
+ sleep sleep_time
16
+ end
17
+ finished_publishing
18
+ end
19
+
20
+ def on_response(request, response)
21
+ #puts "For request #{request}, got response #{response}"
22
+ @out.puts response
23
+ end
24
+
25
+ def on_exception(request, exception)
26
+ puts "For request #{request} got exception #{exception.message}"
27
+ end
28
+
29
+ def on_update()
30
+ end
31
+
32
+ def on_done
33
+ @out.close
34
+ puts "We're done"
35
+ end
36
+ end
data/lib/qwirk.rb ADDED
@@ -0,0 +1,63 @@
1
+ require 'rubygems'
2
+ require 'qwirk/remote_exception'
3
+ require 'qwirk/marshal_strategy'
4
+ require 'qwirk/base_worker'
5
+ require 'qwirk/worker_config'
6
+ require 'qwirk/worker'
7
+ require 'qwirk/request_worker'
8
+ require 'qwirk/task'
9
+ require 'qwirk/publisher'
10
+ require 'qwirk/publish_handle'
11
+ require 'qwirk/adapter'
12
+ require 'qwirk/queue_adapter'
13
+ #require 'qwirk/batch'
14
+ require 'qwirk/manager'
15
+ require 'qwirk/loggable'
16
+
17
+ module Qwirk
18
+ extend Qwirk::Loggable
19
+
20
+ DEFAULT_NAME = 'Qwirk'
21
+
22
+ @@config = nil
23
+ @@hash = {}
24
+
25
+ class MyBean
26
+ include Rumx::Bean
27
+
28
+ bean_attr_reader :adapters, :hash, 'Adapters', :hash_type => :bean
29
+
30
+ def initialize(hash)
31
+ @adapters = hash
32
+ end
33
+ end
34
+
35
+ def self.config=(config)
36
+ @@config = config
37
+ Rumx::Bean.root.bean_add_child(DEFAULT_NAME, MyBean.new(@@hash))
38
+ end
39
+
40
+ def self.[](key)
41
+ if @@config.nil?
42
+ if defined?(Rails)
43
+ # Allow user to use JMS w/o modifying qwirk.yml which could be checked in and hose other users
44
+ env = ENV['QWIRK_ENV'] || Rails.env
45
+ self.config = YAML.load(ERB.new(File.read(Rails.root.join("config", "qwirk.yml")), nil, '-').result(binding))[env]
46
+ Manager.default_options = {
47
+ :persist_file => Rails.root.join('log', 'qwirk_persist.yml'),
48
+ :worker_file => Rails.root.join('config', 'qwirk_workers.yml'),
49
+ :stop_on_signal => true,
50
+ :env => env,
51
+ }
52
+ end
53
+ end
54
+ raise 'Qwirk not configured' unless @@config && @@config[key]
55
+ @@hash[key] ||= Qwirk::Adapter.new(@@config[key])
56
+ end
57
+
58
+ def self.fail_queue_name(queue_name)
59
+ return "#{queue_name.to_s}Fail"
60
+ end
61
+ end
62
+
63
+ require 'qwirk/engine' if defined?(Rails)
@@ -0,0 +1,45 @@
1
+ module Qwirk
2
+
3
+ # Defines the queuing strategy. Currently, only JMS and InMem.
4
+ class Adapter
5
+ include Rumx::Bean
6
+
7
+ attr_reader :config, :log_times, :adapter_info
8
+
9
+ def initialize(config)
10
+ @config = config.dup
11
+ @log_times = config.delete(:log_times)
12
+
13
+ adapter = config.delete(:adapter)
14
+ raise "No adapter definition" unless adapter
15
+ namespace = Qwirk::QueueAdapter.const_get(adapter)
16
+ @adapter_info = nil
17
+ if namespace.respond_to?(:init)
18
+ @adapter_info = namespace.init(config)
19
+ end
20
+ @publisher_klass = namespace.const_get('Publisher')
21
+ @worker_config_klass = namespace.const_get('WorkerConfig')
22
+ end
23
+
24
+ def create_publisher(options={})
25
+ @publisher_parent ||= Rumx::Beans::Folder.new
26
+ publisher = Publisher.new(self, @config.merge(options))
27
+ @publisher_parent.bean_add_child(publisher.to_s, publisher)
28
+ return publisher
29
+ end
30
+
31
+ def create_manager(options={})
32
+ manager = Manager.new(self, @config.merge(options))
33
+ bean_add_child(:Workers, manager)
34
+ return manager
35
+ end
36
+
37
+ def create_adapter_publisher(queue_name, topic_name, options, response_options)
38
+ @publisher_klass.new(self, queue_name, topic_name, options, response_options)
39
+ end
40
+
41
+ def create_adapter_worker_config(parent, queue_name, topic_name, options, response_options)
42
+ @worker_config_klass.new(self, parent, queue_name, topic_name, options, response_options)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,96 @@
1
+ require 'rumx'
2
+
3
+ module Qwirk
4
+ # TODO: Is this necessary anymore or just put in worker.rb? Decide when flat file queue_adapter is implemented.
5
+ module BaseWorker
6
+
7
+ attr_accessor :index, :thread, :config
8
+
9
+ module ClassMethods
10
+ def default_name
11
+ name = self.name.sub(/Worker$/, '')
12
+ name.sub(/::/, '_')
13
+ end
14
+
15
+ # Dynamic class create form WorkerConfig and extended through config_accessor, etc calls that will be defined in the worker
16
+ def config_class
17
+ @config_class ||= Class.new(WorkerConfig)
18
+ end
19
+
20
+ # Default values for all the config attributes
21
+ def default_config
22
+ @default_config ||= WorkerConfig.initial_default_config
23
+ end
24
+
25
+ #config_accessor :sleep_time, :float, 'Number of seconds to sleep between messages', 5
26
+ def config_accessor(name, type, description, default_value=nil)
27
+ make_bean_attr(:bean_attr_accessor, name, type, description, default_value)
28
+ end
29
+
30
+ def config_reader(name, type, description, default_value=nil)
31
+ make_bean_attr(:bean_attr_reader, name, type, description, default_value)
32
+ end
33
+
34
+ def config_writer(name, type, description, default_value=nil)
35
+ make_bean_attr(:bean_attr_writer, name, type, description, default_value)
36
+ end
37
+
38
+ def define_configs(configs)
39
+ @configs = configs
40
+ end
41
+
42
+ def each_config(&block)
43
+ # Configs are either defined with a define_configs call or default to a single instance with default_config
44
+ if @configs
45
+ @configs.each do |name, config|
46
+ yield name, default_config.merge(config)
47
+ end
48
+ else
49
+ yield default_name, default_config
50
+ end
51
+ end
52
+
53
+ #######
54
+ private
55
+ #######
56
+
57
+ def make_bean_attr(attr_method, name, type, description, default_value)
58
+ config_class.send(attr_method, name, type, description, :config_item => true)
59
+ default_config[name.to_sym] = default_value
60
+ end
61
+ end
62
+
63
+ def self.included(base)
64
+ Rumx::Bean.included(base)
65
+ base.extend(ClassMethods)
66
+ if base.kind_of?(Class)
67
+ @worker_classes ||= []
68
+ @worker_classes << base unless @worker_classes.include?(base)
69
+ end
70
+ end
71
+
72
+ def self.worker_classes
73
+ @worker_classes ||= []
74
+ end
75
+
76
+ def start
77
+ raise "Need to override start method in #{self.class.name}"
78
+ end
79
+
80
+ def stop
81
+ raise "Need to override stop method in #{self.class.name}"
82
+ end
83
+
84
+ def join
85
+ thread.join
86
+ end
87
+
88
+ def status
89
+ raise "Need to override status method in #{self.class.name}"
90
+ end
91
+
92
+ def to_s
93
+ "#{config.name}:#{index}"
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,4 @@
1
+ require 'qwirk/batch/acquire_file_strategy'
2
+ require 'qwirk/batch/parse_file_strategy'
3
+ require 'qwirk/batch/file_worker'
4
+ require 'qwirk/batch/job_status'
@@ -0,0 +1,47 @@
1
+ module Qwirk
2
+ module Batch
3
+ class AcquireFileStrategy
4
+ def initialize(options)
5
+ @glob = options[:glob]
6
+ raise "file_options glob value not set" unless @glob
7
+ @poll_time = (options[:poll_time] || 10.0).to_f
8
+ # Ftp's could be in progress, make sure the file is at least 60 seconds old by default before processing
9
+ @age = (options[:age] || 60).to_i
10
+ @stopped = false
11
+ end
12
+
13
+ # Returns the next file or nil if stopped
14
+ def next_file
15
+ until @stopped
16
+ Dir.glob(@glob).each do |file|
17
+ unless file.match /\.(processing|completed)$/
18
+ return file if (Time.now - File.mtime(file) > @age)
19
+ end
20
+ end
21
+ @sleep_thread = Thread.current
22
+ sleep @poll_time
23
+ end
24
+ return nil
25
+ end
26
+
27
+ def mark_file_as_processing(file)
28
+ new_file = file + '.processing'
29
+ File.rename(file, new_file)
30
+ return new_file
31
+ end
32
+
33
+ def complete_file(file)
34
+ file.match(/(.*)\.processing$/) || raise("#{file} is not currently being processed")
35
+ new_file = $1 + '.completed'
36
+ File.rename(file, new_file)
37
+ return new_file
38
+ end
39
+
40
+ def stop
41
+ @stopped = true
42
+ @sleep_thread.wakeup if @sleep_thread
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,3 @@
1
+ require 'qwirk/batch/active_record/batch_job'
2
+ require 'qwirk/batch/active_record/failed_record'
3
+ require 'qwirk/batch/active_record/outstanding_record'
@@ -0,0 +1,111 @@
1
+ module Qwirk::Batch::ActiveRecord
2
+ class BatchJob < ActiveRecord::Base
3
+ include Qwirk::Batch::JobStatus
4
+
5
+ set_table_name 'mt_batch_jobs'
6
+
7
+ has_many :failed_records, :dependent => :destroy
8
+ has_many :outstanding_records, :dependent => :destroy
9
+
10
+ validates :file, :worker_name, :finished_count, :status, :presence => true
11
+ #t.string :file, :null => false
12
+ #t.string :worker_name, :null => false
13
+ #t.integer :total_count
14
+ #t.integer :finished_count, :null => false, :default => 0
15
+ #t.column :status, 'char(8)', :null => false, :default => Qwirk::Batch::JobStatus::INITED
16
+ #t.datetime :created_at, :null => false
17
+ #t.datetime :updated_at, :null => false
18
+
19
+ # Acquire this file if it hasn't already been acquired.
20
+ def self.acquire(file, worker_name)
21
+ return nil if find_by_file_and_worker_name(file, worker_name)
22
+ create!(:file => file, :worker_name => worker_name)
23
+ rescue ActiveRecord::ActiveRecordError => e
24
+ Rails.logger.warn("Assuming race condition (duplicate index) for BatchJob file=#{file} worker=#{worker_name}: #{e.message}")
25
+ return nil
26
+ end
27
+
28
+ # Acquire and resume a paused job if available
29
+ def self.resume_paused_job(worker_name)
30
+ transaction do
31
+ job = where(:worker_name => worker_name, status => PAUSED).lock(true).first
32
+ return nil unless job
33
+ job.outstanding_records.each do |record|
34
+ job.start_record(record.file_position)
35
+ record.destroy
36
+ end
37
+ job.update_attribute(:status => RUNNING)
38
+ end
39
+ end
40
+
41
+ def initialize(opts={})
42
+ super
43
+ @outstanding_array = []
44
+ end
45
+
46
+ def run(total_count)
47
+ update_attributes(:status => RUNNING, :total_count => total_count)
48
+ end
49
+
50
+ def pause
51
+ save_outstanding_array
52
+ update_attribute(:status => STOPPED)
53
+ end
54
+
55
+ def abort
56
+ save_outstanding_array
57
+ update_attribute(:status => ABORTED)
58
+ end
59
+
60
+ def cancel
61
+ save_outstanding_array
62
+ update_attribute(:status => CANCELED)
63
+ end
64
+
65
+ def finish
66
+ update_attribute(:status => FINISHED)
67
+ end
68
+
69
+ def start_record(file_position)
70
+ @outstanding_array << file_position
71
+ end
72
+
73
+ def finish_record(file_position)
74
+ @outstanding_array.delete(file_position)
75
+ update_attribute(:finished_count => finished_count + 1)
76
+ end
77
+
78
+ def fail_record(file_position, message)
79
+ @outstanding_array.delete(file_position)
80
+ failed_records.create!(:file_position => file_position, :message => message)
81
+ end
82
+
83
+ def retry_failed_record
84
+ failed_record = failed_records.first
85
+ return nil unless failed_record
86
+ failed_record.destroy
87
+ return failed_record.file_position
88
+ end
89
+
90
+ def outstanding_array
91
+ @outstanding_array
92
+ end
93
+
94
+ def failed_hash
95
+ hash = {}
96
+ failed_records.each do |failed_record|
97
+ hash[failed_record.file_position] = failed_record.message
98
+ end
99
+ return hash
100
+ end
101
+
102
+ private
103
+
104
+ def save_outstanding_array
105
+ outstanding_records.each {|record| record.destroy}
106
+ @outstanding_array.each do |file_position|
107
+ outstanding_records.create!(:file_position => file_position)
108
+ end
109
+ end
110
+ end
111
+ end