Plug-and-Play Versioning for Rails
Ruby Shell
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
bin Initial commit Aug 27, 2015
lib Bump version to 1.0.1 Oct 11, 2016
spec Remove an unnecessary test that happens to fail in Rails 5 Sep 9, 2016
.gitignore Add gems to gitignore Sep 16, 2015
.rspec Initial commit Aug 27, 2015
.travis.yml
Gemfile Initial commit Aug 27, 2015
README.md Document blacklisted attributes in the README Sep 9, 2016
Rakefile Initial commit Aug 27, 2015
active_versioning.gemspec Add specific version to shoulda-matchers (2.8.0) Dec 28, 2015
circle.yml Specify ruby version for Circle CI Sep 9, 2016

README.md

ActiveVersioning

Gem Version Code Climate Test Coverage Circle CI

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.version_author # => '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

Code At Viget

Visit code.viget.com to see more projects from Viget.