Module: Dalliance

Extended by:
ActiveSupport::Concern
Defined in:
lib/dalliance.rb,
lib/dalliance/engine.rb,
lib/dalliance/schema.rb,
lib/dalliance/version.rb,
lib/dalliance/workers.rb,
lib/dalliance/progress_meter.rb,
lib/dalliance/workers/resque.rb,
lib/dalliance/workers/delayed_job.rb,
lib/generators/dalliance/update/update_generator.rb,
lib/generators/dalliance/progress_meter/progress_meter_generator.rb

Defined Under Namespace

Modules: ClassMethods, Glue, Schema, VERSION, Workers Classes: Engine, ProgressMeter, ProgressMeterGenerator, UpdateGenerator

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.background_processing=(value) ⇒ Object



29
30
31
# File 'lib/dalliance.rb', line 29

def background_processing=(value)
  options[:background_processing] = value
end

.configure {|_self| ... } ⇒ Object

Yields:

  • (_self)

Yield Parameters:

  • _self (Dalliance)

    the object that the method was called on



61
62
63
# File 'lib/dalliance.rb', line 61

def configure
  yield(self) if block_given?
end

.dalliance_progress_meter=(value) ⇒ Object



33
34
35
# File 'lib/dalliance.rb', line 33

def dalliance_progress_meter=(value)
  options[:dalliance_progress_meter] = value
end

.dalliance_progress_meter_total_count_method=(value) ⇒ Object



37
38
39
# File 'lib/dalliance.rb', line 37

def dalliance_progress_meter_total_count_method=(value)
  options[:dalliance_progress_meter_total_count_method] = value
end

.detect_loggerObject



77
78
79
80
81
82
83
84
85
# File 'lib/dalliance.rb', line 77

def detect_logger
  if defined?(ActiveRecord)
    ActiveRecord::Base.logger
  elsif defined?(Rails)
    Rails.logger
  else
    ::Logger.new(STDOUT)
  end
end

.detect_worker_classObject



65
66
67
68
69
70
71
72
73
74
75
# File 'lib/dalliance.rb', line 65

def detect_worker_class
  if defined? ::Delayed::Job
    ActiveSupport::Deprecation.warn(
      'Support for Delayed::Job will be removed in future versions. ' \
      'Use Resque instead.'
    )
    return Dalliance::Workers::DelayedJob
  end

  return Dalliance::Workers::Resque if defined? ::Resque
end

.duration_column=(value) ⇒ Object



53
54
55
# File 'lib/dalliance.rb', line 53

def duration_column=(value)
  options[:duration_column] = value
end

.error_notifier=(value) ⇒ Object



57
58
59
# File 'lib/dalliance.rb', line 57

def error_notifier=(value)
  options[:error_notifier] = value
end

.logger=(value) ⇒ Object



49
50
51
# File 'lib/dalliance.rb', line 49

def logger=(value)
  options[:logger] = value
end

.optionsObject



16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/dalliance.rb', line 16

def options
  @options ||= {
    :background_processing => (defined?(Rails) ? (Rails.env.production? || Rails.env.staging?) : true),
    :dalliance_progress_meter => true,
    :dalliance_progress_meter_total_count_method => :dalliance_progress_meter_total_count,
    :worker_class => detect_worker_class,
    :queue => 'dalliance',
    :logger => detect_logger,
    :duration_column => 'dalliance_duration',
    :error_notifier => ->(e){}
  }
end

.queue=(value) ⇒ Object



45
46
47
# File 'lib/dalliance.rb', line 45

def queue=(value)
  options[:queue] = value
end

.worker_class=(value) ⇒ Object



41
42
43
# File 'lib/dalliance.rb', line 41

def worker_class=(value)
  options[:worker_class] = value
end

Instance Method Details

#calculate_dalliance_progress_meter_total_countObject

If the progress_meter_total_count_method is not implemented or raises and error just use 1…



398
399
400
401
402
403
404
405
406
407
408
# File 'lib/dalliance.rb', line 398

def calculate_dalliance_progress_meter_total_count
  begin
    if respond_to?(self.class.dalliance_options[:dalliance_progress_meter_total_count_method])
      self.send(self.class.dalliance_options[:dalliance_progress_meter_total_count_method])
    else
      1
    end
  rescue
    1
  end
end

#cancel_and_dequeue_dalliance!Object

Cancels the job and removes it from the queue if has not already been taken by a worker. If the job is processing, it is up to the job implementation to stop and do any necessary cleanup. If the job does not honor the cancellation request, it will finish processing as normal and finish with a dalliance_status of ‘completed’.

Jobs can currently only be removed from Resque queues. DelayedJob jobs will not be dequeued, but will immediately exit once taken by a worker.



236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/dalliance.rb', line 236

def cancel_and_dequeue_dalliance!
  should_dequeue = pending?

  request_cancel_dalliance!

  if should_dequeue
    self.dalliance_options[:worker_class].dequeue(self)
    dalliance_log("[dalliance] #{self.class.name}(#{id}) - #{dalliance_status} - Removed from #{processing_queue} queue")
    cancelled_dalliance!
  end

  true
end

#dalliance_background_process(background_processing = nil) ⇒ Object

Force background_processing w/ true



289
290
291
292
293
294
295
# File 'lib/dalliance.rb', line 289

def dalliance_background_process(background_processing = nil)
  if background_processing || (background_processing.nil? && self.class.dalliance_options[:background_processing])
    self.class.dalliance_options[:worker_class].enqueue(self, processing_queue, :dalliance_process)
  else
    dalliance_process(false)
  end
end

#dalliance_background_reprocess(background_processing = nil) ⇒ Object



304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/dalliance.rb', line 304

def dalliance_background_reprocess(background_processing = nil)
  # Reset state to 'pending' before queueing up
  # Otherwise the model will stay on completed/processing_error until the job
  # is taken by a worker, which could be a long time after this method is
  # called.
  reprocess_dalliance!
  if background_processing || (background_processing.nil? && self.class.dalliance_options[:background_processing])
    self.class.dalliance_options[:worker_class].enqueue(self, processing_queue, :do_dalliance_reprocess)
  else
    do_dalliance_reprocess(false)
  end
end

#dalliance_log(message) ⇒ Object



177
178
179
180
181
# File 'lib/dalliance.rb', line 177

def dalliance_log(message)
  if self.class.dalliance_options[:logger]
    self.class.dalliance_options[:logger].info(message)
  end
end

#dalliance_process(background_processing = false) ⇒ Object



297
298
299
300
301
302
# File 'lib/dalliance.rb', line 297

def dalliance_process(background_processing = false)
  do_dalliance_process(
    perform_method: self.class.dalliance_options[:dalliance_method],
    background_processing: background_processing
  )
end

#dalliance_progressObject



385
386
387
388
389
390
391
392
393
394
395
# File 'lib/dalliance.rb', line 385

def dalliance_progress
  if completed?
    100
  else
    if self.class.dalliance_options[:dalliance_progress_meter] && dalliance_progress_meter
      dalliance_progress_meter.progress
    else
      0
    end
  end
end

#dalliance_reprocess(background_processing = false) ⇒ Object



317
318
319
320
# File 'lib/dalliance.rb', line 317

def dalliance_reprocess(background_processing = false)
  reprocess_dalliance!
  do_dalliance_reprocess(background_processing)
end

#do_dalliance_process(perform_method:, background_processing: false) ⇒ Object



322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# File 'lib/dalliance.rb', line 322

def do_dalliance_process(perform_method:, background_processing: false)
  # The job might have been cancelled after it was queued, but before
  # processing started.  Check for that up front before doing any processing.
  cancelled_dalliance! if cancel_requested?
  return if cancelled? # method generated from AASM

  start_time = Time.now

  begin
    start_dalliance!

    if self.class.dalliance_options[:dalliance_progress_meter]
      build_dalliance_progress_meter(:total_count => calculate_dalliance_progress_meter_total_count).save!
    end

    self.send(perform_method)

    finish_dalliance! unless validation_error? || cancelled?
  rescue StandardError => e
    #Save the error for future analysis...
    self.dalliance_error_hash = {:error => e.class.name, :message => e.message, :backtrace => e.backtrace}

    begin
      error_dalliance!
    rescue
      begin
        self.dalliance_status = 'processing_error'

        dalliance_log("[dalliance] #{self.class.name}(#{id}) - #{dalliance_status} #{dalliance_error_hash}")

        self.dalliance_error_hash = { error: 'Persistance Failure: See Logs' }

        self.class.where(id: self.id).update_all(dalliance_status: dalliance_status, dalliance_error_hash: dalliance_error_hash )
      # rubocop:disable Lint/SuppressedException
      rescue
      # rubocop:enable Lint/SuppressedException
      end
    end

    error_notifier.call(e)

    # Don't raise the error if we're background processing...
    raise e unless background_processing && self.class.dalliance_options[:worker_class].rescue_error?
  ensure
    if self.class.dalliance_options[:dalliance_progress_meter] && dalliance_progress_meter
      #Works with optimistic locking...
      Dalliance::ProgressMeter.delete(dalliance_progress_meter.id)
      self.dalliance_progress_meter = nil
    end

    duration = Time.now - start_time

    dalliance_log("[dalliance] #{self.class.name}(#{id}) - #{dalliance_status} #{duration.to_i}")

    duration_column = self.class.dalliance_options[:duration_column]
    if duration_column.present?
      current_duration = self.send(duration_column) || 0
      self.class.where(id: self.id)
        .update_all(duration_column => current_duration + duration.to_f)
    end
  end
end

#error_notifierObject



216
217
218
# File 'lib/dalliance.rb', line 216

def error_notifier
  self.dalliance_options[:error_notifier]
end

#error_or_completed?Boolean

Returns:

  • (Boolean)


220
221
222
# File 'lib/dalliance.rb', line 220

def error_or_completed?
  validation_error? || processing_error? || completed? || cancelled?
end

#human_dalliance_status_nameObject



224
225
226
# File 'lib/dalliance.rb', line 224

def human_dalliance_status_name
  I18n.t("activerecord.state_machines.dalliance_status.states.#{dalliance_status}")
end

#pending_or_processing?Boolean

Returns:

  • (Boolean)


263
264
265
# File 'lib/dalliance.rb', line 263

def pending_or_processing?
  pending? || processing?
end

#processing_queueObject



267
268
269
270
271
272
273
# File 'lib/dalliance.rb', line 267

def processing_queue
  if self.class.dalliance_options[:queue].respond_to?(:call)
    self.class.instance_exec self, &dalliance_options[:queue]
  else
    self.class.dalliance_options[:queue]
  end
end

#queued?(queue_name: processing_queue) ⇒ Boolean

Is a job queued to the given processing queue for this record?

Parameters:

  • queue_name (String) (defaults to: processing_queue)

    the name of the queue to check for jobs. Defaults to the configured processing queue

Returns:

  • (Boolean)


282
283
284
285
286
# File 'lib/dalliance.rb', line 282

def queued?(queue_name: processing_queue)

  worker_class = self.class.dalliance_options[:worker_class]
  worker_class.queued?(self, queue_name)
end

#store_dalliance_validation_error!Object



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/dalliance.rb', line 183

def store_dalliance_validation_error!
  self.dalliance_error_hash = {}

  if defined?(Rails) && Rails.gem_version >= Gem::Version.new('6.1')
    self.errors.each do |error|
      self.dalliance_error_hash[error.attribute] ||= []
      self.dalliance_error_hash[error.attribute] << error.message
    end
  else
    self.errors.each do |attribute, error|
      self.dalliance_error_hash[attribute] ||= []
      self.dalliance_error_hash[attribute] << error
    end
  end

  begin
    validation_error_dalliance!
  rescue
    begin
      self.dalliance_status = 'validation_error'

      dalliance_log("[dalliance] #{self.class.name}(#{id}) - #{dalliance_status} #{self.dalliance_error_hash}")

      self.dalliance_error_hash = { error: 'Persistance Failure: See Logs' }

      self.class.where(id: self.id).update_all(dalliance_status: dalliance_status, dalliance_error_hash: dalliance_error_hash )
    # rubocop:disable Lint/SuppressedException
    rescue
    # rubocop:enable Lint/SuppressedException
    end
  end
end

#validate_dalliance_statusObject



250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/dalliance.rb', line 250

def validate_dalliance_status
  unless error_or_completed?
    errors.add(:dalliance_status, "Processing must be finished or cancelled, but status is '#{dalliance_status}'")
    if defined?(Rails)
      throw(:abort)
    else
      return false
    end
  end

  true
end