module RequiresApproval

Public Class Methods

included(klass) click to toggle source
# File lib/requires_approval.rb, line 8
def self.included(klass)
  klass.send(:extend, ClassMethods)
end

Public Instance Methods

approve_all_attributes() click to toggle source
# File lib/requires_approval.rb, line 12
def approve_all_attributes
  self.approve_attributes(self.fields_requiring_approval)
end
approve_attributes(*attributes) click to toggle source

approve a list of attributes

# File lib/requires_approval.rb, line 17
def approve_attributes(*attributes)

  return true unless self.has_pending_changes?

  # validate an normalize our attributes
  attributes = self.check_attributes_for_approval(attributes)

  # make sure that all attributes are provided if we have never
  # been approved
  fields_not_being_approved = (self.fields_requiring_approval - attributes)

  if fields_not_being_approved.present? && self.never_approved?
    raise PartialApprovalForNewObject.new(
      "You must approve #{self.fields_requiring_approval.join(", ")} " + 
      "for a new #{self.class.name}"
    )
  end

  attributes.flatten.each do |attr|
    write_attribute(attr, self.latest_unapproved_version.send(attr))
  end

  # if we have approved all requested changes, make our latest
  # unapproved version approved -
  # this is ALWAYS true for a new record even though its pending_changes
  # hash is forced to have values
  if self.is_first_version? || self.no_pending_changes?
    self.latest_unapproved_version.update_attribute(:is_approved, true)
  else
    # makes our latest_unapproved_version approved and
    # creates another unapproved version with any remaining
    # attributes
    self.create_approval_version_record
  end

  self.is_frozen = false

  self.save
  self.reload
  true
end
deny_attributes(*attributes) click to toggle source
# File lib/requires_approval.rb, line 59
def deny_attributes(*attributes)

  unless self.has_approved_version?
    raise DenyingNeverApprovedError.new
  end

  attributes = self.check_attributes_for_approval(attributes)

  attributes.flatten.each do |attr|
    self.latest_unapproved_version.send("#{attr}=", self.send(attr))
    true
  end

  # if we have denied all changes, remove the record
  unless self.has_pending_changes?
    self.latest_unapproved_version.destroy
  else
    self.latest_unapproved_version.save
  end
  
  self.reload
  true
end
has_approved_version?() click to toggle source

have any of our versions ever been approved?

# File lib/requires_approval.rb, line 84
def has_approved_version?
  self.versions.count(:conditions => {:is_approved => true}) > 0
end
has_pending_changes?() click to toggle source

have we already approved all outstanding changes?

# File lib/requires_approval.rb, line 89
def has_pending_changes?
  self.pending_changes.present?
end
is_first_version?() click to toggle source

are we the first version?

# File lib/requires_approval.rb, line 94
def is_first_version?
  !self.has_approved_version?
end
no_pending_changes?() click to toggle source

returns true if there are no changes to approve

# File lib/requires_approval.rb, line 99
def no_pending_changes?
  !self.has_pending_changes?
end
pending_changes() click to toggle source

the changes users have requested since the last approval

# File lib/requires_approval.rb, line 104
def pending_changes
  return {} if self.latest_unapproved_version.blank?
  
  ret = {}
  # check each field requiring approval
  self.fields_requiring_approval.each do |field|
    
    # if it is the same in the unapproved as in the parent table
    # we skip it
    if self.is_first_version? || 
      self.send(field) != self.latest_unapproved_version.send(field)
      
      # otherwise we get the change set
      ret[field] = {
        # our first version is always nil, regardless of the
        # defaults in that table
        "was" => self.is_first_version? ? nil : self.send(field), 
        "became" => self.latest_unapproved_version.send(field)
      }
    end
  end
  ret
end

Protected Instance Methods

attributes_requiring_approval() click to toggle source

the attributes that require approval

# File lib/requires_approval.rb, line 131
def attributes_requiring_approval
  self.attributes.select{|k,v| self.fields_requiring_approval.include?(k)}
end
check_attributes_for_approval(attributes) click to toggle source

check if our attributes are valid for approval

# File lib/requires_approval.rb, line 136
def check_attributes_for_approval(attributes)
   # normalize attributes
  attributes = Array.wrap(attributes).flatten.collect(&:to_s)

  # check for invalid attributes
  invalid_fields = (attributes - self.fields_requiring_approval)
  # if we have fields not requiring approval, raise an error
  if invalid_fields.present?
    raise InvalidFieldsError.new(
      "fields_requiring_approval don't include #{invalid_fields.join(",")}"
    )
  end
  attributes
end
create_approval_version_record() click to toggle source

creates the record of an individual approval

# File lib/requires_approval.rb, line 152
def create_approval_version_record
  outstanding_changes = self.pending_attributes
  # update our old latest_unapproved_version to reflect our changes
  self.latest_unapproved_version.update_attributes(
    self.attributes_requiring_approval.merge(:is_approved => true)
  )
  # reload so this unapproved version is out of our cache and will not
  # get its foreign key unassigned
  self.latest_unapproved_version(true)

  self.latest_unapproved_version = self.versions_class.new(
    self.attributes_requiring_approval.merge(outstanding_changes)
  )
end
latest_unapproved_version_with_nil_check() click to toggle source

gets the latest unapproved version or creates a new one

# File lib/requires_approval.rb, line 168
def latest_unapproved_version_with_nil_check
  self.latest_unapproved_version ||= begin
    self.versions_class.new(self.attributes_requiring_approval)
  end
end
never_approved?() click to toggle source

has this record never been approved?

# File lib/requires_approval.rb, line 175
def never_approved?
  !self.has_approved_version?
end
pending_attributes() click to toggle source

ActiveRecord-style attribute hash for the requested changes

# File lib/requires_approval.rb, line 181
def pending_attributes
  ret = {}
  self.pending_changes.each_pair do |k, change|
    ret[k] = change["became"]
  end
  ret
end
versions_class() click to toggle source

the class which our versions are

# File lib/requires_approval.rb, line 190
def versions_class
  self.class.versions_class
end