Class: NotifyUser::BaseNotification

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
AASM, ActionView::Helpers::TextHelper
Defined in:
app/models/notify_user/base_notification.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.aggregate_message(notifications) ⇒ Object



94
95
96
97
98
99
100
101
# File 'app/models/notify_user/base_notification.rb', line 94

def self.aggregate_message(notifications)
  string = ActionView::Base.new(
         ActionController::Base.view_paths).render(
         :template => self.class.views[:mobile_sdk][:aggregate_path].call(self), :formats => [:html],
         :locals => { :notifications => notifications})

  return ::CGI.unescapeHTML("#{string}")
end

.channel(name, options = {}) ⇒ Object

Configure a channel



215
216
217
218
219
# File 'app/models/notify_user/base_notification.rb', line 215

def self.channel(name, options={})
  channels_clone = self.channels.clone
  channels_clone[name] = options
  self.channels = channels_clone
end

.deliver_channels(notification_id) ⇒ Object

Deliver a single notification across each channel.



328
329
330
331
332
# File 'app/models/notify_user/base_notification.rb', line 328

def self.deliver_channels(notification_id)
  self.channels.each do |channel_name, options|
    self.deliver_notification_channel(notification_id, channel_name)
  end
end

.deliver_channels_aggregated(notifications) ⇒ Object

Deliver multiple notifications across each channel as an aggregate message.



335
336
337
338
339
340
341
342
# File 'app/models/notify_user/base_notification.rb', line 335

def self.deliver_channels_aggregated(notifications)
  self.channels.each do |channel_name, options|
      if options[:aggregate_per] != false && !unsubscribed_from_channel?(notifications.first.target, channel_name)
        channel = (channel_name.to_s + "_channel").camelize.constantize
        channel.deliver_aggregated(notifications, options)
      end
  end
end

.deliver_notification_channel(notification_id, channel_name) ⇒ Object

Deliver a single notification to a specific channel.



347
348
349
350
351
352
353
354
355
356
# File 'app/models/notify_user/base_notification.rb', line 347

def self.deliver_notification_channel(notification_id, channel_name)
  notification = self.find(notification_id) # Raise an exception if not found.

  channel_options = channels[channel_name.to_sym]
  channel = (channel_name.to_s + "_channel").camelize.constantize

  unless notification.user_has_unsubscribed?(channel_name)
    channel.deliver(notification, channel_options)
  end
end

.deliver_notifications_channel(notifications, channel_name) ⇒ Object

Deliver a aggregated notifications to a specific channel.



359
360
361
362
363
364
365
366
367
# File 'app/models/notify_user/base_notification.rb', line 359

def self.deliver_notifications_channel(notifications, channel_name)
  channel_options = channels[channel_name.to_sym]
  channel = (channel_name.to_s + "_channel").camelize.constantize

  #check if user unsubsribed from channel type
  unless notifications.first.user_has_unsubscribed?(channel_name)
    channel.deliver_aggregated(notifications, channel_options)
  end
end

.for_target(target) ⇒ Object

Sending



223
224
225
226
# File 'app/models/notify_user/base_notification.rb', line 223

def self.for_target(target)
  where(target_id: target.id)
  .where(target_type: target.class.base_class)
end

.notify_aggregated_channel(notification_id, channel_name) ⇒ Object

Prepares a single channel for aggregation



370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/notify_user/base_notification.rb', line 370

def self.notify_aggregated_channel(notification_id, channel_name)
  notification = self.find(notification_id) # Raise an exception if not found.

  # Find any pending notifications with the same type and target, which can all be sent in one message.
  if self.aggregate_grouping
    notifications = self.pending_aggregation_by_group_with(notification)
  else
    notifications = self.pending_aggregation_with(notification)
  end

  notifications.map(&:mark_as_sent)
  notifications.map(&:save)

  # If the notification has been marked as read before it's sent we don't want to send it.
  return if notification.read? || notifications.empty?

  if notifications.length == 1
    # Despite waiting for more to aggregate, we only got one in the end.
    self.deliver_notification_channel(notifications.first.id, channel_name)
  else
    # We got several notifications while waiting, send them aggregated.
    self.deliver_notifications_channel(notifications, channel_name)
  end
end

.pending_aggregation_by_group_with(notification) ⇒ Object

Used to find all pending notifications with aggregation enabled for target



268
269
270
271
272
# File 'app/models/notify_user/base_notification.rb', line 268

def self.pending_aggregation_by_group_with(notification)
  for_target(notification.target)
  .where(state: [:pending, :pending_as_aggregation_parent])
  .where(group_id: notification.group_id)
end

.pending_aggregation_with(notification) ⇒ Object

Used to find all pending notifications for target



275
276
277
278
279
# File 'app/models/notify_user/base_notification.rb', line 275

def self.pending_aggregation_with(notification)
  where(type: notification.type)
  .for_target(notification.target)
  .where(state: [:pending, :pending_as_aggregation_parent])
end

.pending_aggregations_grouped_marked_as_parent(notification) ⇒ Object

Used for aggregation when grouping based on group_id for target



260
261
262
263
264
265
# File 'app/models/notify_user/base_notification.rb', line 260

def self.pending_aggregations_grouped_marked_as_parent(notification)
  where(type: notification.type)
  .for_target(notification.target)
  .where(state: :pending_as_aggregation_parent)
  .where(group_id: notification.group_id)
end

.pending_aggregations_marked_as_parent(notification) ⇒ Object

Used for aggregation when grouping isn’t enabled



253
254
255
256
257
# File 'app/models/notify_user/base_notification.rb', line 253

def self.pending_aggregations_marked_as_parent(notification)
  where(type: notification.type)
  .for_target(notification.target)
  .where(state: :pending_as_aggregation_parent)
end

Instance Method Details

#aggregate_groupingObject

True will implement a grouping/aggregation algorithm so that even though 10 notifications are delivered eg. Push Notifications Only 1 notification will be displayed to the user within the notification.json payload



201
# File 'app/models/notify_user/base_notification.rb', line 201

class_attribute :aggregate_grouping

#aggregate_perObject

Aggregation



196
# File 'app/models/notify_user/base_notification.rb', line 196

class_attribute :aggregate_per

#aggregation_intervalObject



166
167
168
# File 'app/models/notify_user/base_notification.rb', line 166

def aggregation_interval
  pending_and_sent_aggregation_parents.count
end

#aggregation_parentsObject



235
236
237
238
# File 'app/models/notify_user/base_notification.rb', line 235

def aggregation_parents
  current_parents
  .where('id != ?', id)
end

#aggregation_pending?Boolean

Returns:

  • (Boolean)


281
282
283
284
285
286
287
288
289
290
291
# File 'app/models/notify_user/base_notification.rb', line 281

def aggregation_pending?
  # A notification of the same type, that would have an aggregation job associated with it,
  # already exists.

  # When group aggregation is enabled we provide a different scope
  if self.aggregate_grouping
    return (self.class.pending_aggregations_grouped_marked_as_parent(self).where('id != ?', id).count > 0)
  else
    return (self.class.pending_aggregations_marked_as_parent(self).where('id != ?', id).count > 0)
  end
end

#channelsObject

Channels



190
# File 'app/models/notify_user/base_notification.rb', line 190

class_attribute :channels

#count_for_targetObject

returns the global unread notification count for a user



87
88
89
90
91
92
# File 'app/models/notify_user/base_notification.rb', line 87

def count_for_target
  NotifyUser::BaseNotification.for_target(target)
    .where('parent_id IS NULL')
    .where('state IN (?)', ["sent_as_aggregation_parent", "sent", "pending"])
    .count
end

#current_parentsObject

Returns all parent notifications with a given group_id



229
230
231
232
233
# File 'app/models/notify_user/base_notification.rb', line 229

def current_parents
  self.class
  .for_target(self.target)
  .where(group_id: group_id)
end

#delay_time(options) ⇒ Object



170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'app/models/notify_user/base_notification.rb', line 170

def delay_time(options)
  a_interval = options[:aggregate_per][aggregation_interval]

  # uses the last interval by default once we deplete the intervals
  a_interval = options[:aggregate_per].last if a_interval.nil?

  # last sent notification
  last_sent_parent = sent_aggregation_parents.first
  # Uses the time of the last notification sent otherwise will send it now.
  delay_time = last_sent_parent ? last_sent_parent.sent_time : created_at

  # If this is the first notification the aggregate interval will return 0. Thus sending the notification now!
  return delay_time + a_interval.minutes
end

#deliverObject

Aggregates appropriately



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'app/models/notify_user/base_notification.rb', line 294

def deliver
  if pending? and not user_has_unsubscribed?
    # if aggregation is false bypass aggregation completely
    self.channels.each do |channel_name, options|
      if(options[:aggregate_per] == false)
        self.mark_as_sent!
        self.class.delay.deliver_notification_channel(self.id, channel_name)
      else
        # only notifies channels if no pending aggregate notifications
        unless aggregation_pending?
          self.mark_as_pending_as_aggregation_parent!

          # adds fallback support for integer or array of integers
          if options[:aggregate_per].kind_of?(Array)
            self.class.delay_until(delay_time(options)).notify_aggregated_channel(self.id, channel_name)
          else
            a_interval = options[:aggregate_per] ? options[:aggregate_per].minutes : self.aggregate_per
            self.class.delay_for(a_interval).notify_aggregated_channel(self.id, channel_name)
          end
        end
      end
    end
  end
end

#deliver!Object

Sends immediately and without aggregation



320
321
322
323
324
325
# File 'app/models/notify_user/base_notification.rb', line 320

def deliver!
  if pending_no_aggregation? and not user_has_unsubscribed?
    self.mark_as_sent!
    self.class.deliver_channels(self.id)
  end
end

#descriptionObject

Notification description



186
# File 'app/models/notify_user/base_notification.rb', line 186

class_attribute :description

#generate_unsubscribe_hashObject



161
162
163
164
# File 'app/models/notify_user/base_notification.rb', line 161

def generate_unsubscribe_hash
  #check if a hash already exists for that user otherwise create a new one
  return NotifyUser::UserHash.where(target_id: self.target.id).where(target_type: self.target.class.base_class).where(type: self.type).where(active: true).first || NotifyUser::UserHash.create(target: self.target, type: self.type, active: true)
end

#grouped_by_id(group_id) ⇒ Object



132
133
134
135
# File 'app/models/notify_user/base_notification.rb', line 132

def grouped_by_id(group_id)
  self.group_id = group_id
  self
end

#messageObject



103
104
105
106
107
108
109
110
# File 'app/models/notify_user/base_notification.rb', line 103

def message
  string = ActionView::Base.new(
         ActionController::Base.view_paths).render(
         :template => self.class.views[:mobile_sdk][:template_path].call(self), :formats => [:html],
         :locals => { :params => self.params, :notification => self})

  return ::CGI.unescapeHTML("#{string}")
end

#mobile_message(length = 115) ⇒ Object



112
113
114
115
116
117
118
119
# File 'app/models/notify_user/base_notification.rb', line 112

def mobile_message(length=115)
  string = truncate(ActionView::Base.new(
         ActionController::Base.view_paths).render(
         :template => self.class.views[:mobile_sdk][:template_path].call(self), :formats => [:html],
         :locals => { :params => self.params, :notification => self}), :length => length)

  return ::CGI.unescapeHTML("#{string}")
end

#notify(deliver = true) ⇒ Object

Send any Emails/SMS/APNS



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'app/models/notify_user/base_notification.rb', line 143

def notify(deliver=true)
  #All notifications except the notification at interval 0 should have there parent_id set
  if self.aggregate_grouping
    parents = current_parents.where(parent_id: nil).where('created_at >= ?', 24.hours.ago).order('created_at DESC')

    if parents.any?
      self.parent_id = parents.first.id
    end
  end

  # Sends with aggregation if enabled
  save

  ## if deliver == false don't perform deliver log but still perform aggregation logic
  ## notification then gets marked as sent
  mark_as_sent! unless deliver
end

#notify!Object



137
138
139
140
# File 'app/models/notify_user/base_notification.rb', line 137

def notify!
  # Bang version of 'notify' ignores aggregation
  dont_aggregate!
end

#paramsObject



78
79
80
81
82
83
84
# File 'app/models/notify_user/base_notification.rb', line 78

def params
  if super.nil?
    {}
  else
    super.with_indifferent_access
  end
end

#pending_and_sent_aggregation_parentsObject



246
247
248
249
250
# File 'app/models/notify_user/base_notification.rb', line 246

def pending_and_sent_aggregation_parents
  aggregation_parents
  .where(state: [:sent_as_aggregation_parent, :pending_as_aggregation_parent])
  .order('created_at DESC')
end

#sent_aggregation_parentsObject



240
241
242
243
244
# File 'app/models/notify_user/base_notification.rb', line 240

def sent_aggregation_parents
  aggregation_parents
  .where(state: :sent_as_aggregation_parent)
  .order('created_at DESC')
end

#to(user) ⇒ Object

Public Interface



122
123
124
125
# File 'app/models/notify_user/base_notification.rb', line 122

def to(user)
  self.target = user
  self
end

#user_has_unsubscribed?(channel_name = nil) ⇒ Boolean

Returns:

  • (Boolean)


395
396
397
398
# File 'app/models/notify_user/base_notification.rb', line 395

def user_has_unsubscribed?(channel_name=nil)
  #return true if user has unsubscribed
  return Unsubscribe.has_unsubscribed_from?(self.target, self.type, self.group_id, channel_name)
end

#with(*args) ⇒ Object



127
128
129
130
# File 'app/models/notify_user/base_notification.rb', line 127

def with(*args)
  self.params = args.reduce({}, :update)
  self
end