app/models/sweep.rb
# A sweep represents a batch of Snapshots that were triggered simultaneously,
# such as after a deploy.
class Sweep < ActiveRecord::Base
belongs_to :project
has_many :snapshots
attr_accessor :delay_seconds
validates :title, presence: true
validates :email, format: { with: /\A.+@.+\Z/ },
allow_nil: true,
allow_blank: true
after_create :take_snapshots,
:refresh_project_last_sweep
before_create :set_start_time_from_delay_seconds
after_destroy :refresh_project_last_sweep
default_scope { order('created_at DESC') }
def delay_seconds_remaining
return nil unless start_time
return nil if start_time < Time.now
(start_time - Time.now).round
end
def pending_snapshots
snapshots.select(&:pending?)
end
def accepted_snapshots
snapshots.select(&:accepted?)
end
def rejected_snapshots
snapshots.select(&:rejected?)
end
def under_review_snapshots
snapshots.select(&:under_review?)
end
# Updates the snapshot counters and sends out an email if the sweep reaches a
# "done" state. This method is using a pessimistic locking approach to avoid
# race conditions that can cause duplicate emails to be sent and/or the
# progress counters to be stale.
#
# @see
# http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html
# for more info on pessimistic locking.
#
# @return [Sweep] returns itself (useful for method chaining)
def refresh!
with_lock do
update_counters!
send_email_if_needed!
save!
end
self # for chaining
end
private
def update_counters!
self.count_pending = pending_snapshots.count
self.count_accepted = accepted_snapshots.count
self.count_rejected = rejected_snapshots.count
self.count_under_review = under_review_snapshots.count
end
def send_email_if_needed!
return if email.blank?
return if snapshots.count == 0
return if count_pending > 0
return if emailed_at
SweepMailer.ready_for_review(self).deliver_now
self.emailed_at = Time.now
end
def take_snapshots
if start_time
SweepWorker.perform_at(start_time, id)
else
SweepWorker.new.perform_with_sweep(self)
end
end
def refresh_project_last_sweep
project.refresh_last_sweep!
end
def set_start_time_from_delay_seconds
return unless delay_seconds.to_i > 0
self.start_time = Time.now + delay_seconds.to_i.seconds
end
end