centro / tracktor

This URL has Read+Write access

name age message
file .gitignore Tue Jun 09 05:44:57 -0700 2009 Cleaned the test directory to separate tests fr... [Douglas Meyer]
file MIT-LICENSE Tue Aug 19 13:45:52 -0700 2008 Initial import of Tracktor plugin. [Dan Hodos]
file README.rdoc Fri Feb 13 08:34:40 -0800 2009 Changed created_by to creator_id and updated_by... [Douglas Meyer]
file Rakefile Tue Aug 19 13:45:52 -0700 2008 Initial import of Tracktor plugin. [Dan Hodos]
file init.rb Wed Aug 12 18:31:09 -0700 2009 Check if we are actually in test mode before lo... [gabrielg]
file install.rb Tue Aug 19 13:45:52 -0700 2008 Initial import of Tracktor plugin. [Dan Hodos]
directory lib/ Tue Nov 17 15:02:05 -0800 2009 Don't set the updater unless the record hash ch... [Corey Burrows]
directory tasks/ Tue Aug 19 13:45:52 -0700 2008 Initial import of Tracktor plugin. [Dan Hodos]
directory test/ Tue Jun 09 08:04:04 -0700 2009 Did a bit of meta-programming to make the tests... [Douglas Meyer]
README.rdoc

Tracktor

Tracktor is a Rails plugin that eases audit logging of actors (creator and updater) for any class that descends from ActiveRecord::Base. The goal is to seamlessly log the actors without needing to explicitly mention them when writing your application code. This is accomplished by storing the current actor in a thread-local variable for the duration of a particular HTTP request (note that this means that Tracktor will not work with multi-threaded application servers).

Any time you create or update a record within the context of a request, an ActiveRecord::Observer takes over and sets the creator and updater automatically. In other words:

  # before - app/controllers/books_controller.rb
  def create
    @book = Book.create(params[:book].merge(:creator => current_user, :updater => current_user))
  end

  # after - app/controllers/books_controller.rb
  def create
    @book = Book.create(params[:book])
  end

The simplest setup for Tracktor is as follows:

  # app/models/book.rb (repeat with every model class for which you'd like automatic actor tracking)
  class Book < ActiveRecord::Base
    track_actors
  end

  # db/migrate/001_create_books.rb
  class CreateBooks < ActiveRecord::Migration
    def self.up
      create_table :books do |t|
        t.actors
      end
    end
  end

  # app/controllers/application.rb
  class ApplicationController < ActionController::Base
    enable_actor_tracking
  end

Models

Tracktor adds an ActiveRecord::Base.track_actors method that sets up automatic actor tracking for the class. By default, Tracktor assumes that the actor’s class name is "User"; if this is not the case, you can specify the class name like so:

  class Book < ActiveRecord::Base
    track_actors :class_name => "Member"
  end

You can also specify a default actor, which will be used if there is no current actor set:

  class Book < ActiveRecord::Base
    track_actors :default => lambda { User.find_by_id(0) }
  end

Specifying track_actors will allow you to reference the creator and updater associations from the object being tracked:

  book = Book.create!(:name => "Survivor")
  puts book.creator

Migrations

Tracktor adds an ActiveRecord::Migration#actors method that creates two integer columns: creator_id and updater_id. These columns reference the row ids of the actors that created or updated the book.

By default, these columns have not-null constraints (:null => false), but this may be overridden if desired. No foreign keys are created; if you care about that sort of thing, make sure you include it in your migration.

Controllers

Tracktor adds an ActionController::Base.enable_actor_tracking method, which should be added to application.rb after any before_filter or other code that is responsible for logging users in. This method wraps the request such that the current actor is available to an Observer that watches for record creation or updating.

By default, the enable_actor_tracking method will try to grab the actor by calling the current_user method; this can be overridden like so:

  enable_actor_tracking :from => :logged_in_user

Testing

Tracktor hearts Test-Driven Development. In fact, Tracktor hearts TDD so much that it adds a Test::Unit::TestCase.test_actor_tracking_for method that can be used like so:

  class BookTest < Test::Unit::TestCase
    test_actor_tracking_for :book
  end

This will create and run the following tests:

  • test_creator_association
  • test_updater_association
  • test_creator_should_be_set_to_current_before_validation_on_create
  • test_updater_should_be_set_to_current_before_validation
  • test_creator_should_default_when_current_is_not_set
  • test_updater_should_default_when_current_is_not_set

Any other tests you’d like to write (to test custom class names, etc.) are on you.

If you’re using Shoulda by ThoughtBot, you can instead use the Shoulda macro, like so:

  class BookTest < Test::Unit::TestCase
    context "an existing book"
      setup { @book = Book.create! }
      should_track_actors_for :book
    end
  end

This will create the appropriate shoulds.

Etc.

There are times when it is necessary to create/update objects that normally track actors when you are outside of the context of an HTTP request (or inside one without a logged in user). For this, Tracktor includes the as_actor method, which can be used like so:

  as_actor(User.find_by_id(0)) do
    Book.create!(:name => "Choke")
  end

This can be especially useful when writing tests, command-line scripts, rake tasks, etc. Tracktor adds validations to creator_id and updater_id, so an error that states "Updated by can’t be blank" will often point to the fact that you are operating outside the context of an HTTP request, and need to use as_actor.

Copyright © 2008 Centro, released under the MIT license. Authored by: