Class: Effective::Notification

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
app/models/effective/notification.rb

Constant Summary collapse

AUDIENCES =
[
  ['Send to user or email from the report', 'report'],
  ['Send to specific addresses', 'emails']
]
SCHEDULE_TYPES =
[
  ['On the first day they appear in the report and every x days thereafter', 'immediate'],
  ['When present in the report on the following dates', 'scheduled']
]
SCHEDULED_METHODS =

TODO: [‘Send once’, ‘Send daily’, ‘Send weekly’, ‘Send monthly’, ‘Send quarterly’, ‘Send yearly’, ‘Send now’]

[
  ['The following dates...', 'dates'],
]
CONTENT_TYPES =
['text/plain', 'text/html']

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#current_resourceObject

Returns the value of attribute current_resource.



10
11
12
# File 'app/models/effective/notification.rb', line 10

def current_resource
  @current_resource
end

#current_userObject

Returns the value of attribute current_user.



9
10
11
# File 'app/models/effective/notification.rb', line 9

def current_user
  @current_user
end

#view_contextObject

Returns the value of attribute view_context.



11
12
13
# File 'app/models/effective/notification.rb', line 11

def view_context
  @view_context
end

Instance Method Details

#already_notified_today?(resource) ⇒ Boolean

Returns:

  • (Boolean)


345
346
347
348
349
350
351
352
353
354
# File 'app/models/effective/notification.rb', line 345

def already_notified_today?(resource)
  email = resource_emails_to_s(resource)
  raise("expected an email for #{report} #{report&.id} and #{resource} #{resource&.id}") unless email.present?

  logs = notification_logs.select { |log| log.email == email }
  return false if logs.count == 0

  # If we already notified today
  logs.any? { |log| log.created_at&.beginning_of_day == Time.zone.now.beginning_of_day }
end

#assign_renderer(view_context) ⇒ Object



190
191
192
193
194
# File 'app/models/effective/notification.rb', line 190

def assign_renderer(view_context)
  raise('expected renderer to respond to') unless view_context.respond_to?(:root_url)
  assign_attributes(view_context: view_context)
  self
end

#assigns_for(resource = nil) ⇒ Object

We pull the Assigns from 2 places:

  1. The report.report_columns

  2. The class’s def reportable_view_assigns(view) method



397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'app/models/effective/notification.rb', line 397

def assigns_for(resource = nil)
  return {} unless report.present? 

  resource ||= report.reportable.new
  raise('expected an acts_as_reportable resource') unless resource.class.try(:acts_as_reportable?)
  
  report_assigns = Array(report.report_columns).inject({}) do |h, column|
    value = resource.send(column.name)
    h[column.name] = column.format(value); h
  end

  reportable_view_assigns = resource.reportable_view_assigns(renderer).deep_stringify_keys
  raise('expected notification assigns to return a Hash') unless reportable_view_assigns.kind_of?(Hash)

  # Merge all assigns
  report_assigns.merge(reportable_view_assigns)
end

#audience_emailsObject



174
175
176
# File 'app/models/effective/notification.rb', line 174

def audience_emails
  Array(self[:audience_emails]) - [nil, '']
end

#audience_emails?Boolean

Returns:

  • (Boolean)


160
161
162
# File 'app/models/effective/notification.rb', line 160

def audience_emails?
  audience == 'emails'
end

#audience_report?Boolean

Returns:

  • (Boolean)


164
165
166
# File 'app/models/effective/notification.rb', line 164

def audience_report?
  audience == 'report'
end

#build_notification_log(resource: nil, skipped: false) ⇒ Object



415
416
417
418
419
420
421
422
# File 'app/models/effective/notification.rb', line 415

def build_notification_log(resource: nil, skipped: false)
  emailable = resource_emailable(resource)

  email = resource_emails_to_s(resource)
  email ||= audience_emails_to_s if scheduled_email?

  notification_logs.build(email: email, report: report, resource: resource, user: emailable, skipped: skipped)
end

#disable!Object



216
217
218
# File 'app/models/effective/notification.rb', line 216

def disable!
  update!(enabled: false)
end

#email_templateObject



182
183
184
# File 'app/models/effective/notification.rb', line 182

def email_template
  :notification # We always use this email template
end

#email_template_variablesObject



186
187
188
# File 'app/models/effective/notification.rb', line 186

def email_template_variables
  assigns_for().keys
end

#enable!Object



212
213
214
# File 'app/models/effective/notification.rb', line 212

def enable!
  update!(enabled: true)
end

#immediate?Boolean

This operates on each row of the resource. We track the number of notifications total to see if we should notify again or not

Returns:

  • (Boolean)


152
153
154
# File 'app/models/effective/notification.rb', line 152

def immediate?
  schedule_type == 'immediate'
end

#notifiable?(resource, date: nil) ⇒ Boolean

Returns:

  • (Boolean)


328
329
330
331
332
333
334
335
336
337
338
# File 'app/models/effective/notification.rb', line 328

def notifiable?(resource, date: nil)
  raise('expected an acts_as_reportable resource') unless resource.class.try(:acts_as_reportable?)

  if schedule_type == 'immediate'
    notifiable_immediate?(resource: resource, date: date)
  elsif schedule_type == 'scheduled'
    notifiable_scheduled?(date: date)
  else
    raise("unsupported schedule_type")
  end
end

#notifiable_immediate?(resource:, date: nil) ⇒ Boolean

Consider the notification logs which track how many and how long ago this notification was sent It’s notifiable? when first time or if it’s been immediate_days since last notification

Returns:

  • (Boolean)


358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# File 'app/models/effective/notification.rb', line 358

def notifiable_immediate?(resource:, date: nil)
  raise('expected an immediate? notification') unless immediate?

  email = resource_emails_to_s(resource)
  raise("expected an email for #{report} #{report&.id} and #{resource} #{resource&.id}") unless email.present?

  logs = notification_logs.select { |log| log.email == email }

  if logs.count == 0
    true # This is the first time. We should send.
  elsif logs.count < immediate_times
    # We still have to send it but consider dates.
    last_sent_days_ago = logs.map { |log| log.days_ago(date: date) }.min || 0
    (last_sent_days_ago >= immediate_days)
  else
    false # We've already sent enough times
  end
end

#notifiable_rows_countObject



204
205
206
# File 'app/models/effective/notification.rb', line 204

def notifiable_rows_count
  report.collection().select { |resource| notifiable?(resource) }.count if report
end

#notifiable_scheduled?(date: nil) ⇒ Boolean

Returns:

  • (Boolean)


377
378
379
380
381
382
383
384
385
386
387
388
# File 'app/models/effective/notification.rb', line 377

def notifiable_scheduled?(date: nil)
  raise('expected a scheduled? notification') unless scheduled?

  date ||= Time.zone.now.beginning_of_day

  case scheduled_method
  when 'dates'
    scheduled_dates.find { |day| day == date.strftime('%F') }.present?
  else
    raise('unsupported scheduled_method')
  end
end

#notifiable_tomorrow?(resource) ⇒ Boolean

Returns:

  • (Boolean)


340
341
342
343
# File 'app/models/effective/notification.rb', line 340

def notifiable_tomorrow?(resource)
  date = Time.zone.now.beginning_of_day.advance(days: 1)
  notifiable?(resource, date: date)
end

#notifiable_tomorrow_rows_countObject



208
209
210
# File 'app/models/effective/notification.rb', line 208

def notifiable_tomorrow_rows_count
  report.collection().select { |resource| notifiable_tomorrow?(resource) }.count if report
end

#notify!(force: false) ⇒ Object

The main function to send this thing



251
252
253
# File 'app/models/effective/notification.rb', line 251

def notify!(force: false)
  scheduled_email? ? notify_by_schedule!(force: force) : notify_by_resources!(force: force)
end

#notify_by_resources!(force: false) ⇒ Object

Operates on every resource in the data source. Sends one email for each row



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'app/models/effective/notification.rb', line 270

def notify_by_resources!(force: false)
  notified = 0

  report.collection().find_each do |resource|
    next unless notifiable?(resource) || force

    # Send Now functionality. Don't duplicate if it's same day.
    next if already_notified_today?(resource) && !force

    print('.')

    begin
      # For logging
      assign_attributes(current_resource: resource)

      # Send the resource email
      Effective::NotificationsMailer.notification(self, resource, email_notification_params).deliver_now

      # Log that it was sent
      build_notification_log(resource: resource).save!

      # Count how many we actually sent
      notified += 1
    rescue => e
      EffectiveLogger.error(e.message, associated: self) if defined?(EffectiveLogger)
      ExceptionNotifier.notify_exception(e, data: { notification_id: id, resource_id: resource.id, resource_type: resource.class.name }) if defined?(ExceptionNotifier)
      raise(e) if Rails.env.test? || Rails.env.development?
    end

    GC.start if (notified % 250) == 0
  end

  notified > 0 ? update!(last_notified_at: Time.zone.now, last_notified_count: notified) : touch
end

#notify_by_schedule!(force: false) ⇒ Object



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'app/models/effective/notification.rb', line 305

def notify_by_schedule!(force: false)
  notified = 0

  if notifiable_scheduled? || force
    begin
      Effective::NotificationsMailer.notification(self, nil, email_notification_params).deliver_now

      # Log that it was sent
      build_notification_log(resource: nil).save!

      # Count how many we actually sent
      notified += 1
    rescue => e
      EffectiveLogger.error(e.message, associated: self) if defined?(EffectiveLogger)
      ExceptionNotifier.notify_exception(e, data: { notification_id: id }) if defined?(ExceptionNotifier)
      raise(e) if Rails.env.test? || Rails.env.development?
    end

  end

  notified > 0 ? update!(last_notified_at: Time.zone.now, last_notified_count: notified) : touch
end

#previewObject

Returns a message. Do not call deliver.



256
257
258
259
260
261
262
263
264
265
266
267
# File 'app/models/effective/notification.rb', line 256

def preview
  return unless report.present?

  if audience_emails?
    # notify_by_schedule
    Effective::NotificationsMailer.notification(self, nil, email_notification_params) 
  else
    # notify_by_resources
    resource = report.collection.order('RANDOM()').first
    Effective::NotificationsMailer.notification(self, resource, email_notification_params) if resource
  end
end

#rendererObject



196
197
198
# File 'app/models/effective/notification.rb', line 196

def renderer
  view_context || nil # This isn't ideal
end

#rows_countObject



200
201
202
# File 'app/models/effective/notification.rb', line 200

def rows_count
  @rows_count ||= report.collection().count if report
end

#scheduleObject



140
141
142
143
144
145
146
147
148
# File 'app/models/effective/notification.rb', line 140

def schedule
  if immediate?
    "Send immediately then every #{immediate_days} days for #{immediate_times} times total"
  elsif scheduled? && scheduled_method == 'dates'
    "Send on #{scheduled_dates.length} scheduled days: #{scheduled_dates.sort.to_sentence}"
  else
    'todo'
  end
end

#scheduled?Boolean

Returns:

  • (Boolean)


156
157
158
# File 'app/models/effective/notification.rb', line 156

def scheduled?
  schedule_type == 'scheduled'
end

#scheduled_datesObject



178
179
180
# File 'app/models/effective/notification.rb', line 178

def scheduled_dates
  Array(self[:scheduled_dates]) - [nil, '']
end

#scheduled_email?Boolean

Only scheduled emails can have attached reports. Only scheduled emails can do Send Now

Returns:

  • (Boolean)


170
171
172
# File 'app/models/effective/notification.rb', line 170

def scheduled_email?
  scheduled? && audience_emails?
end

#send_now!Object

Enqueues this notification to send right away. Only applies to scheduled_email? notifications



222
223
224
225
226
# File 'app/models/effective/notification.rb', line 222

def send_now!
  raise('expected to be persisted') unless persisted?
  NotificationJob.perform_later(id, force: true)
  true
end

#skip_once!Object

Only applies to immedate? notifications Skips over one notification on the immediate notifications



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'app/models/effective/notification.rb', line 230

def skip_once!
  notified = 0

  report.collection().find_each do |resource|
    print('.')

    # For logging
    assign_attributes(current_resource: resource)

    # Send the resource email
    build_notification_log(resource: resource, skipped: true).save!

    notified += 1

    GC.start if (notified % 250) == 0
  end

  touch
end

#to_email(resource) ⇒ Object



390
391
392
# File 'app/models/effective/notification.rb', line 390

def to_email(resource)
  audience == 'emails' ? audience_emails.presence : resource_emails_to_s(resource)
end

#to_sObject



136
137
138
# File 'app/models/effective/notification.rb', line 136

def to_s
  subject.presence || model_name.human
end