Skip to content

This originated as a fork of Jason Whitehorn's code, plus some fixes.

License

Notifications You must be signed in to change notification settings

vitulicny/acts_as_versioned

 
 

Repository files navigation

DbActsAsVersioned

acts_as_versioned is a gem for Rails 3.1, 3.2 & 4 to enable easy versioning of models. As a versioned model is updated revisions are kept in a seperate table, providing a record of what changed.

Update:

The forked acts_as_versioned code systematically deleted records in the version table, when the associated record was deleted in the parent table. This seemed both illogical to me, and created "gaps" in the data history. Given this, starting in version 3.5.0, I made the feature optional, with the new default behavior to "save deleted records in the version table".

For anyone wanting to retain the original functionality (deleting history of deleted parent records), add the new "dependent_version_association: option:

acts_as_versioned :dependent_version_association => :delete_all

Getting Started

In your Gemfile simply include:

gem 'db_acts_as_versioned', '~> 3.5.0'

The next time you run bundle install you'll be all set to start using acts_as_versioned.

Usage

Versioning a Model

By default acts_as_versioned is unobtrusive. You will need to explicitly state which models to version. To do so, add the line acts_as_versioned to your model, like so:

class MyModel < ActiveRecord::Base
  acts_as_versioned
  #...
end

Next we need to create a migration to setup our versioning tables:

bundle exec rails generate migration AddVersioningToMyModel

Once that is completed, edit the generated migration. acts_as_versioned patches your model to add a create_versioned_table and drop_versioned_table method. A migration for MyModel (assuming MyModel already existed) might look like:

class AddVersioningToMyModel < ActiveRecord::Migration
  def self.up
    MyModel.create_versioned_table
  end

  def self.down
    MyModel.drop_versioned_table
  end
end

Execute your migration:

bundle exec rake db:migrate

And you're finished! Without any addition work, MyModel is being versioned.

Excluding attributes from versioning

Sometime you want to exclude an attribute of a model from being versioned. That can be accomplished with the :except parameter to acts_as_versioned:

class MyMode < ActiveRecord::Base
  acts_as_versioned :except => :some_attr_i_dont_want_versioned

end

Revisions

Recording a history of changes to a model is only useful if you can do something with that data. With acts_as_versioned there are several ways you can interact with a model's revisions.

Version Number

To determine what the current version number for a model is:

model.version

The version attribute is available for both the actual model, and also any revisions of a model. Thusly, the following is valid:

model.versions.last.version
Revisions List

As alluded to above, you can get an array of revisions of a model via the versions attribute:

model.versions

The returned objects are of a type MyModel::Version where MyModel is the model you are working with. These objects have identical fields to MyModel. So, if MyModel had a name attribute, you could also say:

model.versions.last.name
Reverting to a Revision

To revert a model to an older revision, simply call revert_to with the version number you desire to rever to:

model.revert_to(version_number)
Saving Without Revisions

Occasionally you might need to save a model without necessary creating revisions. To do so, use the save_without_revision method:

model.save_without_revision

Migrations

Adding a field to your model does not automatically add it to the versioning table. So, when you add new fields, be sure to add them to both:

class AddNewFieldToMyModel < ActiveRecord::Migration
  def change
    add_column :my_models, :new_field_, :string
    add_column :my_model_versions, :new_field_, :string
  end
end

Version Class

As has been stated, the versioned data is stored seperately from the main class. This also implies that model.versions returns an area of object of a class other than MyModel (where model is an instance of MyModel, keeping with our working example). The instances returned are actually of type MyModel::Version. With this, comes the fact that any methods, associations, etc. defined on MyModel are not present on MyModel::Version.

While this sounds obvious, it can some times be unexpected. Especially when acts_as_versioned make it so easy to grab historical records from a live record. A common scenario where this can come up is associations.

Say MyModel belongs to TheMan. Also, assume that you want to find out where (in the past) a particular instance of MyModel was updated in regards to it's association to TheMan. You could write that as:

model.versions.keep_if { |m| m.the_man != current_man }.last

However, this will not work. This is because MyModel::Version does not belong to TheMan. You could compare ids here, or you could patch MyModel::Version to belong to TheMan like:

class MyModel
  acts_as_versioned
  belongs_to :the_men
  #some stuff

  class Version
    belongs_to :the_men
  end
end

About

This originated as a fork of Jason Whitehorn's code, plus some fixes.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Ruby 100.0%