Snaps
Revisioning and tagging of ActiveRecord models.
Installation
Add this line to your application's Gemfile:
gem 'snaps'
And then execute:
$ bundle
Run the install generator:
$ rake snaps:install:migrations
Migrate your database to create the snaps_tags
table:
$ rake db:migrate
Basics
Snaps maintains a table of "named pointers" called Tags to other models. Implemented as a polymorphic association. Revisions are kept in the original table and identified by a perma_id
field.
Snaps provides methods to create copies (snapshots) of models and tag these snapshots as needed.
Usage Examples
Basic Usage
A model that is to be revisioned with snaps needs an integer field called perma_id
and must include the mixin created by Snaps.revision
.
# db/migrate/xxx_create_posts.rb
create_table :posts do |t|
t.integer(:perma_id)
t.string(:title)
t.text(:body)
end
# app/models/post.rb
class Post < ActiveRecord::Base
include Snaps.revision
end
Now you can create snapshots of your post instances.
post = Posts.create(body: 'Some text')
post_snapshot = post.snapshot!
post.perma_id == post_snapshot.perma_id # => true
Using Snaps Tags you can assign one revision to be a 'draft'.
post.perma_id # => 25
post.snaps_tag!(:draft)
draft = Post.with_snaps_tag(:draft).find_by_perma_id(25)
Using a default Tag
It's easy to create a workflow in which a new post will be tagged by default.
Also it's convenient to hide calls to with_snaps_tag
in a scope on your domain models.
# app/models/post.rb
class Post < ActiveRecord::Base
include Snaps.revision(default_tag: :draft)
scope :drafts, -> { with_snaps_tag(:draft) }
end
# in controller
draft = Post.create(body: 'Some Text')
existing_draft = Post.drafts.find_by_perma_id(25)
draft.update(body: "New text")
draft.snapshot!
Managing Lifecycle of records with tags
# app/models/post.rb
class Post < ActiveRecord::Base
include Snaps.revision(default_tag: :draft)
scope :drafts, -> { with_snaps_tag(:draft) }
scope :published, -> { with_snaps_tag(:published) }
def publish
snapshot!(tag: :published)
end
end
# in controller
draft = Post.drafts.find_by_perma_id(25)
draft.publish
all_published_posts = Post.published
Revisioning composite models
# db/migrate/xxx_create_sections.rb
create_table :sections do |t|
t.integer(:perma_id)
t.references(:post)
t.text(:body)
end
# app/models/section.rb
class Section < ActiveRecord::Base
include Snaps.revision
belongs_to :post
end
# app/models/post.rb
class Post < ActiveRecord::Base
include Snaps.revision(default_tag: :draft,
components: [:sections])
has_many :sections
scope :drafts, -> { with_snaps_tag(:draft) }
scope :published, -> { with_snaps_tag(:published) }
def publish
snapshot!(tag: :published)
end
end
# in controller
draft = Post.drafts.find_by_perma_id(25)
draft.sections.create(body: "Section text")
post = draft.snapshot!
# snapshots of sections have been created
draft.sections.first.id != post.sections.first.id
draft.sections.first.body == post.sections.first.body
Accessing other revisions of a record
post.snaps_revisions.where('created_at < ?', post.created_at)