Skip to content

Instantly share code, notes, and snippets.

@sumskyi
Created September 28, 2010 09:18
Show Gist options
  • Save sumskyi/600667 to your computer and use it in GitHub Desktop.
Save sumskyi/600667 to your computer and use it in GitHub Desktop.
model change tracker
# Usage:
#
# class SomeModel < ActiveRecord::Base
# include ModelChange::Tracker
# will_track_changes_of :field_1, :field_2, ... field_n
# end
#
# Now, if we update a record, it will generate a new ModelChange record for every column (field_1, field_2...) that has changed.
#
# Also it will add a has_many relationship between SomeModel and ModelChange, so we can now use
# some_model.model_changes to get a list of changes in this particular object
#
# Additionalyly, it adds two new methods for each tracked column: has_column_changes? and column_changes, wich are self-explanatory I guess.
# They work like ActiveRecord::Dirty but for saved records on db, not 'on the fly changes' that are not yet commited to db.
#
class ModelChange < ActiveRecord::Base
belongs_to :object, :polymorphic => true
named_scope :on_attribute, lambda {|attribute| { :conditions => { :attribute => attribute }}}
module Tracker
def self.included(base)
base.send(:extend, ClassMethods)
base.send(:after_update, :check_for_model_change)
end
def check_for_model_change
self.class.read_inheritable_attribute(:fields_to_check_for_changes).each do |field|
if changes.keys.include?(field)
self.model_changes.create!(:attribute => field, :old_value => changes[field].first, :new_value => changes[field].last)
changes.delete(field) rescue nil
end
end
end
module ClassMethods
def will_track_changes_of(*fields)
fields_to_track = [fields].flatten.compact.map(&:to_s)
write_inheritable_attribute :fields_to_check_for_changes, fields_to_track
has_many :model_changes, :as => :object, :dependent => :destroy, :order => 'id ASC'
changes_module = Module.new
fields_to_track.each do |field_name|
changes_module.module_eval <<-EOF
def has_#{field_name}_changes?
model_changes.on_attribute('#{field_name}').count > 0
end
def #{field_name}_changes
model_changes.on_attribute('#{field_name}').all(:order => 'created_at ASC')
end
def #{field_name}_restore
if last_row = model_changes.on_attribute('#{field_name}').last
self.#{field_name} = last_row.old_value
end
#rescue
end
def #{field_name}_clear
model_changes.reload.on_attribute('#{field_name}').each {|r| r.destroy }
end
EOF
end
include changes_module
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment