Module: Alarmable

Extended by:
ActiveSupport::Concern
Defined in:
lib/alarmable.rb,
lib/alarmable/version.rb

Overview

The gem version details.

Constant Summary collapse

VERSION =

The version of the alarmable gem

'2.5.0'

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.alarm_base_date_propertyObject

Getter/Setter



69
70
71
# File 'lib/alarmable.rb', line 69

def alarm_base_date_property
  @alarm_base_date_property
end

.alarm_jobObject

Getter/Setter



69
70
71
# File 'lib/alarmable.rb', line 69

def alarm_job
  @alarm_job
end

Class Method Details

.gem_versionGem::Version

Returns the version of the gem as a Gem::Version.

Returns:

  • (Gem::Version)

    the gem version as object



19
20
21
# File 'lib/alarmable/version.rb', line 19

def gem_version
  Gem::Version.new VERSION
end

.versionString

Returns the version of gem as a string.

Returns:

  • (String)

    the gem version as string



12
13
14
# File 'lib/alarmable/version.rb', line 12

def version
  VERSION
end

Instance Method Details

#alarm_base_date_propertySymbol

Getter for the alarm base date property.

Returns:

  • (Symbol)

    The user defined base date property



94
# File 'lib/alarmable.rb', line 94

delegate :alarm_base_date_property, to: :class

#alarm_defaultsObject

Set some defaults on the relevant alarm properties.



97
98
99
100
# File 'lib/alarmable.rb', line 97

def alarm_defaults
  self.alarms ||= []
  self.alarm_jobs ||= {}
end

#alarm_id(channel, before_minutes) ⇒ String

Generate a unique and recalculatable identifier for a given alarm object. We build a hash of the primary keys (before_minutes and channel) to achieve this. Afterwards, this alarm id is used to reference dedicated scheduled jobs and track their updates. (Or cancel them accordingly)

Parameters:

  • channel (String)

    The alarm channel

  • before_minutes (Integer)

    The minutes before the alarm starts

Returns:

  • (String)

    The unique alarm id



130
131
132
# File 'lib/alarmable.rb', line 130

def alarm_id(channel, before_minutes)
  (Digest::MD5.new << "#{channel}#{before_minutes}").to_s
end

#alarm_jobClass

Getter for the alarm job class.

Returns:

  • (Class)

    The alarm job class



89
# File 'lib/alarmable.rb', line 89

delegate :alarm_job, to: :class

#alarms_destroy_callbackObject

Cancel all alarm notification jobs on parent destroy.



199
200
201
# File 'lib/alarmable.rb', line 199

def alarms_destroy_callback
  alarm_jobs.each_value { |job_id| alarm_job.cancel(job_id) }
end

#alarms_update_callbackObject

Reschedule only on updates when the alarm settings are changed.



194
195
196
# File 'lib/alarmable.rb', line 194

def alarms_update_callback
  reschedule_alarm_jobs if alarms_changed?
end

#reschedule_alarm_job(alarm) ⇒ Object

Schedule a new Active Job for the alarm notification. This method takes care of the notification time (+date) and will not touch anything when the desired time already passed. It cancels the correct job for the given combination, when it is present. In the end it schedules a new (renewed) job for the given alarm settings.

Parameters:

  • alarm (Hash)

    The alarm object

Returns:

  • (Object)

    The new alarm_jobs instance (partial) Example: { “alarm id”: “job id” }



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/alarmable.rb', line 143

def reschedule_alarm_job(alarm)
  # Symbolize the hash keys (just to be sure).
  alarm = alarm.symbolize_keys

  # Calculate the alarm id for job canceling and cancel a found job.
  id = alarm_id(alarm[:channel], alarm[:before_minutes])
  previous_job_id = alarm_jobs.try(:[], id)
  alarm_job.cancel(previous_job_id) unless previous_job_id.nil?

  base_date = self[alarm_base_date_property]

  # When the base date is not set, we schedule not a new notification job.
  return {} if base_date.nil?

  # Calculate the time when the job should run.
  notify_at = base_date - alarm[:before_minutes].minutes

  # Do nothing when the notification date already passed.
  return {} if Time.current >= notify_at

  # Put a new job to the queue with the new (current) job execution date.
  job = alarm_job.set(wait_until: notify_at).perform_later(self.id, alarm)

  # Construct a new alarm_jobs partial instance for this job
  { id => job.job_id }
end

#reschedule_alarm_jobsObject

Initiate a reschedule for each alarm in the alarm settings and cancel all left-overs.



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/alarmable.rb', line 172

def reschedule_alarm_jobs
  # Perform the reschedule of all the current alarms.
  new_alarm_jobs = alarms.each_with_object({}) do |alarm, memo|
    memo.merge!(reschedule_alarm_job(alarm))
  end

  # Detect the differences from the original alarm_jobs hash to the new
  # built (by partials) alarm_jobs hash. The jobs from negative
  # differences must be canceled.
  diff = Hashdiff.diff(alarm_jobs, new_alarm_jobs)

  diff.select { |prop| prop.first == '-' }.each do |prop|
    alarm_job.cancel(prop.last)
  end

  # Update the alarm_jobs reference pool with our fresh hash.  Bypass the
  # regular validation and callbacks here, this is required to not stuck
  # in endless create-update loops.
  update_columns(alarm_jobs: new_alarm_jobs)
end

#validate_alarm_settingsObject

Validate the presence of the alarm_job property and the accessibility of the specified class. Also validate the alarm_base_date_property setting.

rubocop:disable Style/GuardClause – because its fine like this



107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/alarmable.rb', line 107

def validate_alarm_settings
  raise 'Alarmable +alarm_job+ is not configured' if alarm_job.nil?
  unless alarm_job.is_a? Class
    raise 'Alarmable +alarm_job+ is not instantiable'
  end
  if alarm_base_date_property.nil?
    raise 'Alarmable +alarm_base_date_property+ is not configured'
  end
  unless has_attribute? alarm_base_date_property
    raise 'Alarmable +alarm_base_date_property+ is not usable'
  end
end