Class: MailyHerald::Sequence

Inherits:
Dispatch
  • Object
show all
Includes:
Autonaming
Defined in:
app/models/maily_herald/sequence.rb

Instance Attribute Summary

Attributes inherited from Dispatch

#absolute_delay, #conditions, #from, #list_id, #mailer_name, #name, #override_subscription, #period, #sequence_id, #state, #subject, #template, #title, #type

Instance Method Summary collapse

Methods included from Autonaming

included

Methods inherited from Dispatch

#archive, #archive!, #archived?, #disable, #disable!, #disabled?, #enable, #enable!, #enabled?, #has_start_at_proc?, #in_scope?, #list=, #locked?, #processable?, #start_at, #start_at=, #start_at_changed?, #subscription_valid?

Instance Method Details

#calculate_processing_time_for(entity, mailing = nil) ⇒ Object

Calculates processing time for given entity.


181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'app/models/maily_herald/sequence.rb', line 181

def calculate_processing_time_for entity, mailing = nil
  mailing ||= next_mailing(entity)
  ls = processed_logs(entity)

  if ls.first
    ls.last.processing_at + (mailing.absolute_delay - ls.last.mailing.absolute_delay)
  else
    subscription = self.list.subscription_for(entity)

    if has_start_at_proc?
      evaluated_start = start_at.call(entity, subscription)
    else
      evaluator = Utils::MarkupEvaluator.new(self.list.context.drop_for(entity, subscription))

      evaluated_start = evaluator.evaluate_start_at(self.start_at)
    end

    evaluated_start ? evaluated_start + mailing.absolute_delay : nil
  end
end

#last_processed_mailing(entity) ⇒ Object

Gets last MailyHerald::SequenceMailing object delivered to user.


113
114
115
# File 'app/models/maily_herald/sequence.rb', line 113

def last_processed_mailing entity
  processed_mailings(entity).last
end

#last_processing_time(entity) ⇒ Object

Gets the timestamp of last processed email for given entity.


95
96
97
98
# File 'app/models/maily_herald/sequence.rb', line 95

def last_processing_time entity
  ls = processed_logs(entity)
  ls.last.processing_at if ls.last
end

#mailing(name, options = {}) ⇒ Object

Fetches or defines an MailyHerald::SequenceMailing.

If no block provided, MailyHerald::SequenceMailing with given name is returned.

If block provided, MailyHerald::SequenceMailing with given name is created or edited and block is evaluated within that mailing.

Options Hash (options):

  • :locked (true, false) — default: false

    Determines whether Mailing is locked.

See Also:


41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'app/models/maily_herald/sequence.rb', line 41

def mailing name, options = {}
  if SequenceMailing.table_exists?
    mailing = SequenceMailing.find_by_name(name)
    lock = options.delete(:locked)

    if block_given? && !MailyHerald.dispatch_locked?(name) && (!mailing || lock)
      mailing ||= self.mailings.build(name: name)
      mailing.sequence = self
      yield(mailing)
      mailing.skip_updating_schedules = true if self.new_record?
      mailing.save!

      MailyHerald.lock_dispatch(name) if lock
    end

    mailing
  end
end

#mailing_processing_log_for(entity, mailing) ⇒ Object


122
123
124
# File 'app/models/maily_herald/sequence.rb', line 122

def mailing_processing_log_for entity, mailing
  Log.ordered.processed.for_entity(entity).for_mailing(mailing).last
end

#next_mailing(entity) ⇒ Object

Gets next MailyHerald::SequenceMailing object to be delivered to user.


118
119
120
# File 'app/models/maily_herald/sequence.rb', line 118

def next_mailing entity
  pending_mailings(entity).first
end

#next_processing_time(entity) ⇒ Object

Get next email processing time for given entity.


203
204
205
# File 'app/models/maily_herald/sequence.rb', line 203

def next_processing_time entity
  schedule_for(entity).try(:processing_at)
end

#pending_mailings(entity) ⇒ Object

Gets collection of MailyHerald::SequenceMailing objects that are to be sent to entity.


101
102
103
104
# File 'app/models/maily_herald/sequence.rb', line 101

def pending_mailings entity
  ls = processed_logs(entity)
  ls.empty? ? self.mailings.enabled : self.mailings.enabled.where("id not in (?)", ls.map(&:mailing_id))
end

#processed_logs(entity) ⇒ Object

Returns collection of processed Logs for given entity.


82
83
84
# File 'app/models/maily_herald/sequence.rb', line 82

def processed_logs entity
  Log.ordered.processed.for_entity(entity).for_mailings(self.mailings.select(:id))
end

#processed_logs_for(entity, mailing) ⇒ Object

Returns collection of processed Logs for given entity and mailing.


90
91
92
# File 'app/models/maily_herald/sequence.rb', line 90

def processed_logs_for entity, mailing
  Log.ordered.processed.for_entity(entity).for_mailing(self.mailings.find(mailing))
end

#processed_mailings(entity) ⇒ Object

Gets collection of MailyHerald::SequenceMailing objects that were sent to entity.


107
108
109
110
# File 'app/models/maily_herald/sequence.rb', line 107

def processed_mailings entity
  ls = processed_logs(entity)
  ls.empty? ? self.mailings.where(id: nil) : self.mailings.where("id in (?)", ls.map(&:mailing_id))
end

#runObject

Sends sequence mailings to all subscribed entities.

Performs actual sending of emails; should be called in background.

Returns array of Log with actual `Mail::Message` objects stored in Log.mail attributes.


66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'app/models/maily_herald/sequence.rb', line 66

def run
  # TODO better scope here to exclude schedules for users outside context scope
  schedules.where("processing_at <= (?)", Time.now).each do |schedule|
    if schedule.entity
      mail = schedule.mailing.send(:deliver, schedule)
      schedule.reload
      schedule.mail = mail
      schedule
    else
      MailyHerald.logger.log_processing(schedule.mailing, {class: schedule.entity_type, id: schedule.entity_id}, prefix: "Removing schedule for non-existing entity") 
      schedule.destroy
    end
  end
end

#schedule_for(entity) ⇒ Object

Returns Log object which is the delivery schedule for given entity.


171
172
173
# File 'app/models/maily_herald/sequence.rb', line 171

def schedule_for entity
  schedules.for_entity(entity).first
end

#schedulesObject

Returns collection of all delivery schedules (Log collection).


176
177
178
# File 'app/models/maily_herald/sequence.rb', line 176

def schedules
  Log.ordered.scheduled.for_mailings(self.mailings.select(:id))
end

#set_schedule_for(entity) ⇒ Object

Sets the delivery schedule for given entity

New schedule will be created or existing one updated.

Schedule is Log object of type “schedule”.


131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'app/models/maily_herald/sequence.rb', line 131

def set_schedule_for entity
  # TODO handle override subscription?

  subscribed = self.list.subscribed?(entity)
  mailing = next_mailing(entity)
  start_time = calculate_processing_time_for(entity, mailing) if mailing

  if !subscribed || !self.start_at || !enabled? || !mailing || !start_time 
    log = schedule_for(entity)
    log.try(:destroy)
    return
  end

  log = schedule_for(entity)
  log ||= Log.new
  log.with_lock do
    log.set_attributes_for(mailing, entity, {
      status: :scheduled,
      processing_at: start_time,
    })
    log.save!
  end
  log
end

#set_schedulesObject

Sets delivery schedules of all entities in mailing scope.

New schedules will be created or existing ones updated.


159
160
161
162
163
164
# File 'app/models/maily_herald/sequence.rb', line 159

def set_schedules
  self.list.context.scope_with_subscription(self.list, :outer).each do |entity|
    MailyHerald.logger.debug "Updating schedule of #{self} sequence for entity ##{entity.id} #{entity}"
    set_schedule_for entity
  end
end

#to_sObject


207
208
209
# File 'app/models/maily_herald/sequence.rb', line 207

def to_s
  "<Sequence: #{self.title || self.name}>"
end

#update_schedules_callbackObject


166
167
168
# File 'app/models/maily_herald/sequence.rb', line 166

def update_schedules_callback
  Rails.env.test? ? set_schedules : MailyHerald::ScheduleUpdater.perform_in(10.seconds, self.id)
end