trackable is an add-on to Rails that provides for user-readable, fully configurable messaging on change tracking in ActiveRecord models.

trackable is inspired by two plugins: acts_as_eventable from Netphase and paper_trail from Air Blade Software. I wanted something that had the human-readability of acts_as_eventable, but with custom messaging that could be dictated by Ruby code and not database-bound. I also wanted the potential to track user involvement, like paper_trail does. So the trackable gem was born!

Use Case

You want something that looks like this:

You can choose what message to show when the user selects booleans. For string fields, you can pick between a sensible default, a custom message generator, or specific enumerated values. For references to other models, all you need is a to_s defined on them, and you’ll get a similar choice to string values. You can choose what to include and what not to include, and can get up and running in no time at all.

Installation

Add the following to your config/environment.rb file.

  config.gem 'trackable'

You’ll then need to install the gem using rake gems:install or your sudo equivalent.

After the gem is installed, create your migration with this command:

script/generate trackable_migration

And you’re ready! When that command completes and you run your migrations with rake db:migrate, you’ll have a new table named events that trackable will keep its event information in. Let’s take a look at what we get.

Integration

If you’re the type to not read the instructions, and you’re already getting antsy, just drop trackable into one of your models, and check out what you get in your events association. Simple as that!

For those that like a bit more detail, let’s take a look at the following bit of code and see what we can get out of it.


# create_table :foos, :force => true do |t|
#   t.boolean :no_homers
#   t.string :status
#   t.string :custom_status
#   t.string :do_not_track
#   t.integer :bar_id
#   t.integer :custom_bar_id    
# 
#   t.timestamps
# end

class Foo < ActiveRecord::Base
  trackable :events =>{
    :no_homers => {true => "Homers have been barred.", false => "Homers have been allowed."},
    :custom_status => {:message => Proc.new {|n| "The value of a custom string field changed to #{n}" }},
    :custom_bar_id => {:message => Proc.new{|n| "Active Bar set to #{n}"}}
  }, :exclude => :do_not_track
  belongs_to :bar, :class_name => "Bar"
  belongs_to :custom_bar, :class_name => "Bar"
end

Booleans

For booleans, you can indicate what messages that you’d like by setting up a hash with the true and false messages like this:

{:your_boolean_field_name => {true => "True message", false => "False message"}}

You can use the default message, but most of the time that won’t be what you want.

Strings (or integers)

You might not have to do anything. You’ll have a message like “Humanized field name changed to new value” without intervention. If you do want something custom, you can do this:

{:your_string_field => {:message => Proc.new {|n| "Field value set to #{n}" }}}

The value of n will be the new string value that’s been saved in your tracked object.

If you happen to have a string field with only a few values that are known ahead of time, you could use the same sort of signature as the hash in the Boolean example above to set a custom message for each value. This might be useful in state machines, for example. You could have something like this.

{:aasm_state_ => {"published" => "Post has been published.",
                  "submitted" => "Post was submitted for approval"}}

Associations

As long as your association field ends with _id, trackable will call to_s on the new value, and then it’ll be treated like the string example above.

Excluding certain fields.

As in the far above example, you can pass an :exclude option with a single field name or an array of field names to ignore. These won’t result events being added.

Questions

I’m available at jim at jimvanfleet dot com, or you can send me a message right here on Github. You can also open issues here, I’ll try to keep an eye on them.