Changes Are Logged

This is a simple way to record when a ActiveRecord model is modified.

Usage

Hook changes_are_logged into your ActiveRecord model by calling the automatically_log_changes method:

class Game < ActiveRecord::Base
  include ChangesAreLogged
  automatically_log_changes
end

Then any time that object is modified, a new entry in the change_logs table will be added:

> game.change_logs
 => []
> game.update_attribute(:name, 'Wombats Rule')
 => true
> game.change_logs
 => [#<ChangeLog id: 442, target_id: 65, target_type: "Game", changes_logged: {"name"=>["Old Name", "Wombats Rule"]}, comments: nil, user_id: 68, created_at: "2011-11-16 00:01:04">]
>

You can alter the logged values by passing a block to automatically_log_changes, which can be handy if the column contains a complex Ruby object (think Carrierwave uploaders, etc). The block expects you to return a two-element array with the modified old and new values:

class Game < ActiveRecord::Base
  # example carrierwave uploader attached to the 'thumbnail' column
  mount_uploader :thumbnail, MyApp::MyThumbnailUploader

  include ChangesAreLogged
  automatically_log_changes do |attribute, old_value, new_value|
    if attribute == 'thumbnail'
      [old_value.filename, new_value.filename]  # or something similar
    end
  end
end

Now the value of the thumbnail column won't be included if it changes. The text "Attribute changed, but value has been filtered." will be logged instead.

Assumptions

The method current_user is defined by your app, and returns the user instance that is currently logged in.

Setup

Add migration for creation of the change_logs table. Here's the important bit:

create_table :change_logs do |t|
  t.column :target_id, :integer
  t.column :target_type, :string
  t.column :changes_logged, :text
  t.column :comments, :text
  t.column :user_id, :integer
  t.column :user_is_staff, :boolean
  t.timestamps
end