Module: Auditable::Auditing

Extended by:
ActiveSupport::Concern
Defined in:
lib/auditable/auditing.rb

Defined Under Namespace

Modules: ClassMethods

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#audit_actionObject

INSTANCE METHODS



170
171
172
# File 'lib/auditable/auditing.rb', line 170

def audit_action
  @audit_action
end

#audit_tagObject

INSTANCE METHODS



170
171
172
# File 'lib/auditable/auditing.rb', line 170

def audit_tag
  @audit_tag
end

Instance Method Details

#audit_changed_byObject



172
173
174
175
176
177
178
179
180
# File 'lib/auditable/auditing.rb', line 172

def audit_changed_by
  changed_by_call = self.class.audited_cache('changed_by')

  if changed_by_call.respond_to? :call
    changed_by_call.call(self)
  else
    self.send(changed_by_call)
  end
end

#audit_tag_with(tag) ⇒ Object

Mark the latest record with a tag in order to easily find and perform diff against later If there are no audits for this record, create a new audit with this tag



196
197
198
199
200
201
202
203
204
205
206
# File 'lib/auditable/auditing.rb', line 196

def audit_tag_with(tag)
  if audit = last_audit
    audit.update_attribute(:tag, tag)

    # Force the trigger of a reload if audited_version is used. Happens automatically otherwise
    audits.reload if self.class.audited_version
  else
    self.audit_tag = tag
    snap!
  end
end

#audited_changes(options = {}) ⇒ Object

Get the latest changes by comparing the latest two audits



282
283
284
# File 'lib/auditable/auditing.rb', line 282

def audited_changes(options = {})
  last_audit.try(:latest_diff, options) || {}
end

#last_auditObject

Get the latest audit record



183
184
185
186
187
188
189
190
191
192
# File 'lib/auditable/auditing.rb', line 183

def last_audit
  # if version is enabled, use the version
  if self.class.audited_version
    audits.order('version DESC').first

  # other pull last inserted
  else
    audits.last
  end
end

#last_change_of(attribute) ⇒ Object

Return last attribute’s change

This method may be slow and inefficient on model with lots of audit objects. Go through each audit in the reverse order and find the first occurrence when audit.modifications changed



291
292
293
294
295
296
297
298
299
300
301
# File 'lib/auditable/auditing.rb', line 291

def last_change_of(attribute)
  raise "#{attribute} is not audited for model #{self.class}. Audited attributes: #{self.class.audited_attributes}" unless self.class.audited_attributes.include? attribute.to_sym
  attribute = attribute.to_s # support symbol as well
  last = audits.size - 1
  last.downto(1) do |i|
    if audits[i].modifications[attribute] != audits[i-1].modifications[attribute]
      return audits[i].diff(audits[i-1])[attribute]
    end
  end
  nil
end

#save_audit(data) ⇒ Object



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/auditable/auditing.rb', line 251

def save_audit(data)
  last_saved_audit = last_audit

  # build new audit
  audit = audits.build(data)

  # only save if it's different from before
  if !audit.same_audited_content?(last_saved_audit)
    # If version is enabled, wrap in a transaction to get the next version number
    # before saving
    if self.class.audited_version
      ActiveRecord::Base.transaction do
        if self.class.audited_version.is_a? Symbol
          audit.version = self.send( self.class.audited_version )
        else
          audit.version = (audits.maximum('version')||0) + 1
        end
        audit.save
      end

    # Save as usual
    else
      audit.save
    end
  else
    audits.delete(audit)
  end

end

#snapObject

Take a snapshot of the current state of the audited record’s audited attributes



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/auditable/auditing.rb', line 209

def snap
  serialize_attribute = lambda do |attribute| 
    # If a proc, do nothing, cannot be serialized
    # XXX: raise warning on passing in a proc?
    if attribute.is_a? Proc
      # noop
     
    # Is an ActiveRecord, serialize as hash instead of serializing the object
    elsif attribute.class.ancestors.include?(ActiveRecord::Base)            
      attribute.serializable_hash

    # If an array, such as from an association, serialize the elements in the array
    elsif attribute.is_a?(Array) || attribute.is_a?(ActiveRecord::Associations::CollectionProxy)
      attribute.map { |element| serialize_attribute.call(element) }

    # otherwise, return val
    else
      attribute
    end
  end

  {}.tap do |s|
    self.class.audited_attributes.each do |attr|
      val = self.send attr
      s[attr.to_s] = serialize_attribute.call(val)
    end
  end
end

#snap!(options = {}) ⇒ Object

Take a snapshot of and save the current state of the audited record’s audited attributes

Accept values for :tag, :action and :user in the argument hash. However, these are overridden by the values set by the auditable record’s virtual attributes (#audit_tag, #audit_action, #changed_by) if defined



241
242
243
244
245
246
247
248
249
# File 'lib/auditable/auditing.rb', line 241

def snap!(options = {})
  data = options.merge(:modifications => self.snap)

  data[:tag]        = self.audit_tag    if self.audit_tag
  data[:action]     = self.audit_action if self.audit_action
  data[:changed_by] = self.audit_changed_by   if self.audit_changed_by

  self.save_audit( data )
end