require'set'moduleAudited# Audit saves the changes to ActiveRecord models. It has the following attributes:## * <tt>auditable</tt>: the ActiveRecord model that was changed# * <tt>user</tt>: the user that performed the change; a string or an ActiveRecord model# * <tt>action</tt>: one of create, update, or delete# * <tt>audited_changes</tt>: a hash of all the changes# * <tt>comment</tt>: a comment set with the audit# * <tt>version</tt>: the version of the model# * <tt>request_uuid</tt>: a uuid based that allows audits from the same controller request# * <tt>created_at</tt>: Time that the change was performed#classYAMLIfTextColumnTypeclass<<selfdefload(obj)ifAudited.audit_class.columns_hash["audited_changes"].sql_type=="text"ActiveRecord::Coders::YAMLColumn.new(Object).load(obj)elseobjendenddefdump(obj)ifAudited.audit_class.columns_hash["audited_changes"].sql_type=="text"ActiveRecord::Coders::YAMLColumn.new(Object).dump(obj)elseobjendendendendclassAudit<::ActiveRecord::Basebelongs_to:auditable,polymorphic: truebelongs_to:user,polymorphic: truebelongs_to:associated,polymorphic: truebefore_create:set_version_number,:set_audit_user,:set_request_uuid,:set_remote_addresscattr_accessor:audited_class_namesself.audited_class_names=Set.newserialize:audited_changes,YAMLIfTextColumnTypescope:ascending,->{reorder(version: :asc)}scope:descending,->{reorder(version: :desc)}scope:creates,->{where(action: 'create')}scope:updates,->{where(action: 'update')}scope:destroys,->{where(action: 'destroy')}scope:up_until,->(date_or_time){where("created_at <= ?",date_or_time)}scope:from_version,->(version){where('version >= ?',version)}scope:to_version,->(version){where('version <= ?',version)}scope:auditable_finder,->(auditable_id,auditable_type){where(auditable_id: auditable_id,auditable_type: auditable_type)}# Return all audits older than the current one.defancestorsself.class.ascending.auditable_finder(auditable_id,auditable_type).to_version(version)end# Return an instance of what the object looked like at this revision. If# the object has been destroyed, this will be a new record.defrevisionclazz=auditable_type.constantize(clazz.find_by_id(auditable_id)||clazz.new).tapdo|m|self.class.assign_revision_attributes(m,self.class.reconstruct_attributes(ancestors).merge(version: version))endend# Returns a hash of the changed attributes with the new valuesdefnew_attributes(audited_changes||{}).inject({}.with_indifferent_access)do|attrs,(attr,values)|attrs[attr]=values.is_a?(Array)?values.last:valuesattrsendend# Returns a hash of the changed attributes with the old valuesdefold_attributes(audited_changes||{}).inject({}.with_indifferent_access)do|attrs,(attr,values)|attrs[attr]=Array(values).firstattrsendend# Allows user to be set to either a string or an ActiveRecord object# @privatedefuser_as_string=(user)# reset both either wayself.user_as_model=self.username=niluser.is_a?(::ActiveRecord::Base)?self.user_as_model=user:self.username=userendalias_method:user_as_model=,:user=alias_method:user=,:user_as_string=# @privatedefuser_as_stringuser_as_model||usernameendalias_method:user_as_model,:useralias_method:user,:user_as_string# Returns the list of classes that are being auditeddefself.audited_classesaudited_class_names.map(&:constantize)end# All audits made during the block called will be recorded as made# by +user+. This method is hopefully threadsafe, making it ideal# for background operations that require audit information.defself.as_user(user,&block)::Audited.store[:audited_user]=useryieldensure::Audited.store[:audited_user]=nilend# @privatedefself.reconstruct_attributes(audits)attributes={}result=audits.collectdo|audit|attributes.merge!(audit.new_attributes)[:version]=audit.versionyieldattributesifblock_given?endblock_given??result:attributesend# @privatedefself.assign_revision_attributes(record,attributes)attributes.eachdo|attr,val|record=record.dupifrecord.frozen?ifrecord.respond_to?("#{attr}=")record.attributes.key?(attr.to_s)?record[attr]=val:record.send("#{attr}=",val)endendrecordend# use created_at as timestamp cache keydefself.collection_cache_key(collection=all,timestamp_column=:created_at)super(collection,:created_at)endprivatedefset_version_numbermax=self.class.auditable_finder(auditable_id,auditable_type).descending.first.try(:version)||0self.version=max+1enddefset_audit_userself.user||=::Audited.store[:audited_user]# from .as_userself.user||=::Audited.store[:current_user]# from Sweepernil# prevent stopping callback chainsenddefset_request_uuidself.request_uuid||=::Audited.store[:current_request_uuid]self.request_uuid||=SecureRandom.uuidenddefset_remote_addressself.remote_address||=::Audited.store[:current_remote_address]endendend