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.



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

def current_resource
  @current_resource
end

#current_userObject

Returns the value of attribute current_user.



7
8
9
# File 'app/models/effective/notification.rb', line 7

def current_user
  @current_user
end

#view_contextObject

Returns the value of attribute view_context.



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

def view_context
  @view_context
end

Instance Method Details

#assign_renderer(view_context) ⇒ Object



205
206
207
208
209
# File 'app/models/effective/notification.rb', line 205

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 3 places:

  1. The report.report_columns

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



383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'app/models/effective/notification.rb', line 383

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 3
  report_assigns.merge(reportable_view_assigns)
end

#audience_emailsObject



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

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

#audience_emails?Boolean

Returns:

  • (Boolean)


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

def audience_emails?
  audience == 'emails'
end

#audience_report?Boolean

Returns:

  • (Boolean)


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

def audience_report?
  audience == 'report'
end

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



401
402
403
404
405
406
407
408
# File 'app/models/effective/notification.rb', line 401

def build_notification_log(resource: nil, skipped: false)
  user = resource_user(resource)

  email = resource_email(resource) || user.try(:email)
  email ||= audience_emails_to_s if scheduled_email?

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

#disable!Object



231
232
233
# File 'app/models/effective/notification.rb', line 231

def disable!
  update!(enabled: false)
end

#enable!Object



227
228
229
# File 'app/models/effective/notification.rb', line 227

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)


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

def immediate?
  schedule_type == 'immediate'
end

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

Returns:

  • (Boolean)


305
306
307
308
309
310
311
312
313
314
315
# File 'app/models/effective/notification.rb', line 305

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)


324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'app/models/effective/notification.rb', line 324

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

  email = resource_email(resource) || resource_user(resource).try(:email)
  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



219
220
221
# File 'app/models/effective/notification.rb', line 219

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

#notifiable_scheduled?(date: nil) ⇒ Boolean

Returns:

  • (Boolean)


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

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)


317
318
319
320
# File 'app/models/effective/notification.rb', line 317

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

#notifiable_tomorrow_rows_countObject



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

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



266
267
268
# File 'app/models/effective/notification.rb', line 266

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



271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'app/models/effective/notification.rb', line 271

def notify_by_resources!(force: false)
  notified = 0

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

    # For logging
    assign_attributes(current_resource: resource)

    # Send the resource email
    build_notification_log(resource: resource).save!
    Effective::NotificationsMailer.notify_resource(self, resource).deliver_now

    notified += 1

    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



293
294
295
296
297
298
299
300
301
302
303
# File 'app/models/effective/notification.rb', line 293

def notify_by_schedule!(force: false)
  notified = 0

  if notifiable_scheduled? || force
    build_notification_log(resource: nil).save!
    Effective::NotificationsMailer.notify(self).deliver_now
    notified += 1
  end

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

#render_email(resource = nil) ⇒ Object



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

def render_email(resource = nil)
  raise('expected an acts_as_reportable resource') if resource.present? && !resource.class.try(:acts_as_reportable?)

  to = if audience == 'emails'
    audience_emails.presence
  elsif audience == 'report'
    resource_email(resource) || resource_user(resource).try(:email)
  end

  raise('expected a to email address') unless to.present?

  assigns = assigns_for(resource)

  {
    to: to,
    from: from,
    cc: cc.presence,
    bcc: bcc.presence,
    content_type: CONTENT_TYPES.first,
    subject: template_subject.render(assigns),
    body: template_body.render(assigns)
  }.compact
end

#rendererObject



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

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

#report_variablesObject



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

def report_variables
  assigns_for().keys
end

#rows_countObject



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

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

#scheduleObject



151
152
153
154
155
156
157
158
159
# File 'app/models/effective/notification.rb', line 151

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)


167
168
169
# File 'app/models/effective/notification.rb', line 167

def scheduled?
  schedule_type == 'scheduled'
end

#scheduled_datesObject



189
190
191
# File 'app/models/effective/notification.rb', line 189

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)


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

def scheduled_email?
  scheduled? && audience_emails?
end

#send_now!Object

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



237
238
239
240
241
# File 'app/models/effective/notification.rb', line 237

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



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'app/models/effective/notification.rb', line 245

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

#template_bodyObject



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

def template_body
  Liquid::Template.parse(body)
end

#template_subjectObject



193
194
195
# File 'app/models/effective/notification.rb', line 193

def template_subject
  Liquid::Template.parse(subject)
end

#to_sObject



147
148
149
# File 'app/models/effective/notification.rb', line 147

def to_s
  subject.presence || model_name.human
end