# frozen_string_literal: true module Cron # # Value object for a cron tab entry # class Tab include StandardModel include SearchAble # # Constants # WILDCARD = '*' unless defined? WILDCARD COMMA_DELIM = ',' unless defined? COMMA_DELIM SLASH_DEMO = '/' unless defined? SLASH_DEMO TIME_UNITS = %i[min hour wday mday month].freeze unless defined? TIME_UNITS # # Fields # field :name, type: String field :enabled, type: Boolean, default: true field :min, type: String, default: 0 field :hour, type: String, default: 0 field :wday, type: String, default: WILDCARD field :mday, type: String, default: WILDCARD field :month, type: String, default: WILDCARD field :last_run_at, type: Time # # Validations # validates :name, presence: true, uniqueness: true validate :valid_values # # The method might look a bit obtuse, but basically we want to compare the time values for # * Minute # * Hour # * Day of Week # * Day of Month # * Month # def time_to_run?(time) enabled? && TIME_UNITS.collect { |unit| valid_time?(time, unit, send(unit)) }.all? end # # For completion of class, but must be implemented by child class # def run set({ last_run_at: Time.now.utc }) end private # # Check that all values are within the range # def valid_values valid_range :min, 0..59 valid_range :hour, 0..23 valid_range :month, 0..11 valid_range :wday, 0..6 valid_range :mday, 0..30 end def valid_range(field, range) value = send(field) valid = case value when WILDCARD true when Integer range.include?(value) else if value.include?(SLASH_DEMO) (numerator, divisor) = value.split(SLASH_DEMO) range.include?(divisor.to_i) && numerator.eql?(WILDCARD) elsif value.include?(COMMA_DELIM) options = value.split(COMMA_DELIM) options.collect { |o| range.include?(o.to_i) }.all? else range.include?(value.to_i) end end errors.add(field, "Invalid value, allowed range: #{range}") unless valid end # # Test if the target value matches the time unit or the wild card, or the comma separated list # 0 - matches the zero value # * - Wildcard, any value matches # */15 - matches any value where dividing by 15 is even, or the modulus is zero # 5,10,15 - matches on 5, 10 and 15 values # def valid_time?(time, unit, target) case target when WILDCARD true when Integer time.send(unit).eql?(target) else if target.include?(SLASH_DEMO) divisor = target.split(SLASH_DEMO).last.to_i (time.send(unit) % divisor).zero? elsif target.include?(COMMA_DELIM) options = target.split(COMMA_DELIM) options.collect { |o| time.send(unit.eql?(o.to_i)) }.any? else time.send(unit).eql?(target.to_i) end end end end end