No commit activity in last 3 years
No release in over 3 years
Versioning for your ActiveRecord models
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

>= 0
>= 0

Runtime

 Project Readme

Hindsight

Hindsight adds version tracking to your ActiveRecord models. It is a bit different from other versioning gems in that there is no extra versioning table. All versions remain in same table as the "current" record. This is done in order to allow tracking changes to associations between different versions. A latest_versions scope is provided in order to return only the latest version of any record in a table.

Migration

Hindsight::Schema.version_table(:names, :of, :tables, :to, :add, :versioning, :to)

Usage

class Document < ActiveRecord::Base
  has_hindsight
end

Creating new versions

A new version is created whenever a record is saved. The current record is updated and becomes the new version.

document = Document.new
document.version #=> 0

document.save
document.version #=> 1

document.update_attributes(:body => "Once upon a time...", :authors = author_list)
document.version #=> 2

A new version can be created from a record, without becoming that new version.

document.version #=> 2
new_version = document.new_version(:body => "Once upon a time...")
document.version #=> 2
new_version.version #=> 3

The new_version method can accept a block argument similar to the block version of create.

document.new_version |new_version|
  new_version.body = "Once upon a time..."
  new_version.authors = author_list
end

Creating a saving or creating a new version from an old version will raise Hindsight::ReadOnlyVersion exception.

document.new_version #=> a new version is created making document an old version
document.new_version #=> raises Hindsight::ReadOnlyVersion

NOTE: While association versioning can be maintained using update_attributes, using collection<< or collection= will modify the associations on the current version without creating a new version since the versioned record is not modified by the change. The block form of new_version can be used to modify associations by any means as the block is yielded the new version, so no changes affect the current version.

Versioning Associations

Changes to associations are tracked whenever possible. As new versions of records are created, their associations are copied to the new version.

Consider the following examples where Project.has_many :documents.

# A record is added to an association
project_v1.documents #=> []
project_v2 = project.new_version(:documents => [document_a])
project_v1.documents #=> []
project_v2.documents #=> [document_a]
# A record is moved from one record's association to another record's
alpha_project.documents #=> [document_a]
beta_project.update_attributes(:documents => [document_a])
beta_project.documents #=> [document_a]
alpha_project.versions[-2].documents #=> [document_a] # The previous version still has an associated document...
alpha_project.versions.last.documents #=> [] # ...but the latest version no longer has a document as it has been moved.

NOTE: To make associations "version-aware", the original association is replaced with a new one that adds a scope on the end. The additional scope limits the records returned by the association to just those that are the latest version. The original association is available as "association_name_versions", e.g. "documents_versions", and will include all versions of all documents attached to this record.

belongs_to

Versioned through the mere fact that the foreign key is part of the current record, so changes are automatically tracked from one version to the next.

has_many

Since the foreign key lies with the associated record, these associations are versioned only if the associated record is versioned. Saving a new version of the current record will update these associations as necessary, also triggering the creation of new versions of the associated records as their foreign keys are updated to point at this new version.

Un-versioned has_many associations will save as expected, however, older versions of the current record will lose any trace of those associated records as they are updated to point at the current record's latest version.

has_many :through

Changes to these associations are tracked, regardless of whether the associated model is versioned. This is made possible by the :through record, which is automatically created when assigning a record to the association. One end of the :through record points at the new version, and the other end point at the (un-)versioned associated record.

TODO

  • Add support for has_one and has_one :through
  • Add support for version-aware associations that already have a condition
  • Add support for soft-delete