module Dunlop module OverdueHandling @registry = {} @final_states = %w[overdue completed rejected] def self.registry @registry end def self.final_states @final_states end def self.final_states=(states) @final_states = states end def self.define(&block) @registry = {} # empty the registry definition_proxy = DefinitionProxy.new definition_proxy.instance_eval(&block) end def self.handle_overdue! registry.each do |workflow_step_name, config| base_scope = WorkflowInstance.all #where.not(state: final_states) base_scope = base_scope.where(sti_type: config.scenarios.map{|s| "WorkflowInstance::#{s.to_s.classify}"}) if config.scenarios.any? base_scope = base_scope.joins(workflow_step_name).where.not("#{workflow_step_name.to_s.pluralize}.state" => final_states) #.includes(workflow_step_name) if config.from.is_nested? base_scope = base_scope.joins(config.from.key) if config.from.key != workflow_step_name end base_scope = base_scope.where("#{config.date_target_class.table_name}.#{config.date_target_column} <= ?", Date.current - config.time) base_scope.find_each do |workflow_instance| next unless workflow_step = workflow_instance.public_send(workflow_step_name) workflow_step.is_overdue! end end end class DefinitionProxy def when_workflow_step(workflow_step, options = {}, &block) raise "Workflow step #{workflow_step} does not exist" unless WorkflowInstance.possible_workflow_step_names.include?(workflow_step) workflow_step_proxy = WorkflowStepProxy.new(workflow_step, options) workflow_step_proxy.instance_eval(&block) if block workflow_step_proxy.register_callbacks! ::Dunlop::OverdueHandling.registry[workflow_step] = workflow_step_proxy end def the_final_states_are(states) ::Dunlop::OverdueHandling.final_states = states end end class WorkflowStepProxy attr_reader :workflow_step, :options, :overdue_target_class def initialize(workflow_step, options) @workflow_step, @options = workflow_step, options @overdue_target_class = workflow_step.to_s.classify.constantize end def date_target_class @date_target_class ||= from.is_nested? ? from.key.to_s.classify.constantize : WorkflowInstance end def date_target_column @date_target_column ||= from.field end def scenarios options[:scenarios] || [] end def from WorkflowStepBaseDate.new(workflow_step, options[:from]) end def time options[:is] end #TODO: find a simple way to do this!!! def register_callbacks! date_target_classes = [date_target_class] #TODO: only implement on specific scenarios if given as argument date_column = date_target_column workflow_step_name = workflow_step # make local scope for after_save definition overdue_time = time date_target_classes.each do |target_class| target_class.after_save do |record| if saved_changes[date_column] target_record = record.workflow_instance.public_send(workflow_step_name) if date = record.public_send(date_column) if date <= Date.current - overdue_time target_record.is_overdue! if target_record.pending? or target_record.processing? else target_record.process! if target_record.overdue? end else target_record.process! if target_record.overdue? end end end end end end class WorkflowStepBaseDate attr_reader :workflow_step, :config def initialize(workflow_step, config) @workflow_step, @config = workflow_step, config end def is_nested? config.is_a?(Hash) end def key is_nested? ? config.keys.first : config end def field is_nested? ? config.values.first : config end end end end