Hound

Build Status

Hound is designed to track activity on models. It'll track your usual create update and destroy actions as well as custom activity.

At Allur or more specifically the Spark CRM we've built, we use Hound for implementing activity tabs on our popular models. These activity tabs display the last x amount of actions taken out upon these models. We have a lot of custom associations so we can't simply track the update to models. With Hound we can build custom actions on the fly.

If you need something similar, Hound could be a good fit.

Installation

Add Hound to your Gemfile and run bundle install:

gem 'hound'

Hound expects a hound_actions table to exist in your schema, go ahead and run the generator provided:

rails generate hound:install

This will create a new migration file. Run rake db:migrate to add this table.

Usage

Hound will automatically track create update and destroy methods that occur on your 'hounded' models. You can tell Hound which actions you'd like to track in the hound method:

class Article < ActiveRecord::Base
  hound actions: [:create, :update] # only track create/update
end

Hound also adds a hound_action helper method to your controllers. This allows you to set a custom action on any of your hounded models.

class ArticlesController < ApplicationController
  def add_to_frontpage
    # Does not update @article so no action will be created
    @frontpage << @article
    hound_action @article, 'added_to_frontpage'
  end
end

@article.actions.last.action #=> 'added_to_frontpage'

Hound will track the current user making these changes assuming your application controller responds to a current_user method. If you have a custom method, you can override hound_user to return this instead.

class ApplicationController < ActionController::Base
  def hound_user
    current_admin_user
  end
end

For this to work successfully you must tell Hound about your user class using the hound_user method. The user association on the Hound::Action class is polymorphic, so you can use hound_user in more than one class.

class User
  hound_user
end

class Admin
  hound_user
end

Now hound_user can return either an instance of User or an instance of Admin.

You can also disable Hound on a model instance basis:

article = Article.new title: 'Hello, World!'
article.hound? #=> true
article.hound = false # disable hound
article.save
article.actions #=> []

Tracking Changes

Hound also tracks the changes made when updating your hounded records. You can access the change updates through the changeset attribute:

article = Article.create! title: 'Hello, World!'
article.update_attributes(title: 'Salut, World!')
article.actions.last.changeset
  #=> {"title" => ["Hello, World!", "Salut, World!"]}

Displaying model activity

Because Hound hooks into your existing user model as well as any models you tell it to track, you can display activity from either side. In fact, your user object doesn't even need to belong to the object you're tracking.

current_user.name #=> "Lee"
article = Article.create! title: 'Hello, World!'

article.actions.each do |action|
  puts "#{action.user.name} #{action + 'ed'} the " \
    "#{action.actionable_type} #{action.actionable.title}"
end

current_user.actions.each do |action|
  puts "#{action.user.name} #{action + 'ed'} the " \
    "#{action.actionable_type} #{action.actionable.title}"
end

Both of the above snippets will print the same thing:

Lee created the Article Hello, World!

Console

Hound implements a storage facility on the current thread for storing the id of the current user. This is how we make it available to the model data without sending it via the controller itself. This means when creating records via the console, there will be no current_user available.

>> Article.create! title: 'Foo'
>> _.actions.last.user
=> nil

You can solve this by setting Hound.store[:current_user_id]:

>> Hound.store[:current_user_id] = User.create!(name: 'Lee').id
>> Article.create! title: 'Foo'
>> _.actions.last.user.name
=> "Lee"

Cleaning Up

With all this action creating we're doing, your database is bound to start getting full quickly. You have two options for cleaning up after yourself, either create a rake task:

task :prune_hound_actions do
  Hound.actions.where('created_at < ?', 1.week.ago).delete_all
end

And run it as a cron job, or you can simply limit records on a per model basis

class Article < ActiveRecord::Base
  hound limit: 10
end

Now Hound will never store more than 10 actions for an Article. You can configure this globally through Hound.config.limit, too. Do note though that adding this functionality means whenever an action is tracked, Hound will not only create a new action, it will check and destroy any actions outside of this limit. This requires an extra call to the database, so if that could be an issue, using a rake task might be a better idea.

But we already have Paper Trail?

Yes, and Paper Trail is awesome. Hound is not designed to replace it. They do different things. Hound is designed to track activity (in the form of actions) on a model. Paper Trail does the same thing but it stores snapshots of your model at certain times (when they change). Hound is not just for tracking changes to your model, but you can attach custom activity to it, too.

  • Do I want custom actions and activity attached to my models? Use Hound.
  • Do I need to restore my model to an earlier time? Use Paper Trail.

At Allur we use them both for different things, and they work great.

TODO

  • Implement action grouping
  • Generate a config initializer on install?
  • Disable hound in test environment?