ActiveVersioning
ActiveVersioning provides out-of-the-box versioning functionality in Rails. ActiveVersioning serializes attributes when records are saved and allows for version and draft management.
Installation
Add this line to your application's Gemfile:
gem 'active_versioning'
And then execute:
$ bundle
Or install it yourself as:
$ gem install active_versioning
Once installed, generate the necessary files and run migrations:
$ rails generate active_versioning:install
$ bundle exec rake db:migrate
Setup
To set up versioning in your Rails app, include the following module in each model you'd like to version:
class Post < ActiveRecord::Base
include ActiveVersioning::Model::Versioned
end
Working with Drafts
ActiveVersioning::Model::Versioned
provides a number of methods for accessing and working with versions.
To access the current draft of a record, use...
draft = post.current_draft
This returns a proxy object to a draft version of the record, so you can treat it like the post
itself -- making changes and either saving or updating.
draft.title = 'New Title'
draft.save
# or..
draft.update(title: 'New Title')
Both save
and update
will make changes to the draft version of the post.
When you are ready to overwrite the record with its draft, use...
draft.commit(committer: 'Bob', commit_message: 'Update post title.')
This changes our post's attributes to match those of the draft and then marks the draft as a committed version.
If you want to throw away a draft:
post.destroy_draft
Working with Versions
A draft is just a version with a particular state. To access all the versions for a particular record, use...
post.versions # => All versions, whether the version state is 'create', 'draft', or 'commit'
post.versions.draft # => All draft versions
post.versions.committed # => All non-draft versions
post.versions.newest_first # => All versions starting with the most recently created
You can use any existing version to create a new draft:
old_version = post.versions.committed.first
post.create_draft_from_version(old_version.id)
This will set post.current_draft
's attributes to the attributes stored in the given version's record. Returns boolean based on the save's success.
Capturing Version Metadata
In addition to manually committing a version with a committer and commit message...
post.current_draft.commit(committer: 'Bob', commit_message: 'Update post title.')
ActiveVersioning provides a version_author
accessor on any versioned model, so you can capture the author for a record's initial create:
post = Post.create(title: 'Title', body: 'Body text.', version_author: 'Bob')
post.versions.first. # => 'Bob'
Viewing and Modifying Versioned Attributes
If you want to see the attributes the are versioned, use...
post.versioned_attributes # => { 'id' => 1, 'title' => 'Default Title' }
By default, ActiveVersioning blacklists the following attributes:
ActiveVersioning::VersionManager::BLACKLISTED_ATTRIBUTES = %w(
created_at
updated_at
published
)
If you require additional versioned attributes, overwrite the versioned_attribute_names
method in your model:
class Post < ActiveRecord::Base
private
def versioned_attribute_names
super + %w(photo_id)
end
end
Handling Incompatible Versions
In the case of a versioned model that undergoes a schema change, all previous versions may reference attributes that no longer exist.
In ActiveVersioning, we consider these incompatible versions. An attempt to create a draft from an incompatible version will raise an error:
incompatible_version = post.versions.committed.last
incompatible_version.object
# => { 'deleted_attribute' => value }
post.create_draft_from_version(incompatible_version.id)
# => ActiveVersioning::Errors::IncompatibleVersion:
# The given version contains attributes that are no longer compatible with the current schema: deleted_attribute.
When rescued, the error object contains a reference to the record and the incompatible version:
begin
post.create_draft_from_version(incompatible_version.id)
rescue ActiveVersioning::Errors::IncompatibleVersion => error
error.record # => our `post` record
error.version # => our `incompatible_version`
end
Visit code.viget.com to see more projects from Viget.