Keeping change history for important models are crucial for some application. And acts_as_auditable provides a clean implementation for keeping history.
Install
1. Download acts_as_auditable and put in your vendor folder
2. run
ruby script\generate audit
from your application directory. Which will generate a migration script to create audits table and a model named Audit.
Expectation
1. User model that inherits from ActiveRecord::Base with an instance method of auditor_name(it is required as it stores the auditors name as well as the auditor’s reference for keeping history).
2. A property named auditor(which will be injected in the model where you are using audit) should be prefilled with the user object who is changing the model.
3. Now you can use audit in your model as,
audit :when => :before_update, :if => lambda {|work_item| work_item.changed? }, :with_message => lambda { |post| 'Something changed :)' }
Here
:when => any active record callbacks (like :before_create) :if => provide a condition which will be checked to create the audit :with_message => the message to be stored
Well that’s about it to use acts_as_auditable.
Implementing in real project
The challenge that I faced is to create a human readable message for model and make it maintainable.I actually used audit like,
audit :when => :before_update, :if => lambda {|work_item| work_item.changed? }, :with_message => lambda { |work_item| work_item.audit_message }
Now here is the code to generate the message.
def audit_message history_text = '' self.changes.each do |field, change| history_text += "#{field.humanize} changed from '#{change[0]}' to '#{change[1]}'\n" unless field.ends_with?('_id') end history_text += audit_message_for('project_id', Project, :name, 'project') history_text += audit_message_for('sprint_id', Sprint, :name, 'sprint') history_text += audit_message_for('responsible_person_id', User, :full_name, 'responsible_person', 'Responsible Person') history_text += audit_message_for('release_id', Release, :name, 'release') history_text += audit_message_for('point_id', Point, :name, 'point') return history_text end
And here is the nasty part that at least cleans the audit message implementation. It helps to generate audit message for reference objects.
private # Used for making the audit text readable # # ==== Parameters # # * +field+ - The database field to inspect changes # * +klass+ - The class for the referenced object # * +klass_property+ - The property of the object which will be readable to store audit data # * +readable_name+ - readable name for the class. By default it is klass.to_s # # ==== Returns # The audit text for that field. # If there is no change in the field then empty string is returned def audit_message_for(field, klass, klass_property, belongs_to, readable_name = klass.to_s) history_text = '' seperator = '\n' #as this is done with reflaction, catching exeptions for protection begin if self.send("#{field}_changed?") field_was = "#{field}_was" changed_from = klass.find(self.send(field_was)).send(klass_property) unless self.send(field_was) == nil changed_to = self.send(belongs_to).send(klass_property) unless self.send(field) == nil if self.send(field_was) == nil history_text = "#{readable_name} assigned to '#{changed_to}'#{seperator}" elsif self.send(field) == nil history_text = "Removed #{readable_name} from '#{changed_from}'#{seperator}" else history_text = "#{readable_name} changed from '#{changed_from}' to '#{changed_to}'#{seperator}" end end rescue Exception => ex logger.error "Error #{ex.to_s}" end return history_text end
Another thing to consider is assigning the auditor before saving the history. It is cleaner if you define a method named auditor. That is how you can avoid assigning to that attribute.
def auditor return self.new_record? ? self.creator : self.updator end
But for implementing like this you need to modify some code in the library. Open the vendor\plugins\acts_as_auditable\lib\shooter\acts\auditable.rb and remove the line,
attr_accessor :auditor
Otherwise your method will be overwritten.
No comments:
Post a Comment