Module: Memories

Defined in:
lib/memories/base.rb,
lib/memories/annotation.rb,
lib/memories/attachment.rb,
lib/memories/versions_proxy.rb,
lib/memories/milestones_proxy.rb

Overview

Simply “include Memories” in your CouchRest::Model::Base derived classes to add versioning to your document.

Defined Under Namespace

Modules: ClassMethods Classes: Annotation, Attachment, MilestoneProxy, MilestonesProxy, VersionProxy, VersionsProxy

Constant Summary collapse

VERSION_REGEX =

:nodoc:

/(?:rev-)?(\d+)-[a-zA-Z0-9]+/

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/memories/base.rb', line 3

def self.included(base)
  base.property(:milestone_memories) do |milestone_memory|
    milestone_memory.property :version
    milestone_memory.property :annotations, 
      Memories::Annotation, 
      :init_method => proc { |value| 
        a = Memories::Annotation.new
        value.keys.each do |key|
          a.send key, value[key]
        end
        a
      }
  end

  base.before_update :add_version_attachment
  base.after_save :decode_attachments
  base.send :extend, ClassMethods
  base.alias_method_chain :save, :destroying_logical_version_and_revision
  base.alias_method_chain :save!, :destroying_logical_version_and_revision
end

Instance Method Details

#attachments_to_forgetObject

Returns a list of attachments it should not version



140
141
142
143
144
145
146
147
148
# File 'lib/memories/base.rb', line 140

def attachments_to_forget
  return [] unless self.class.remember_attachments?
  (self.database.get(self.id)["_attachments"] || {}).keys.reject do |a| 
    a.match(VERSION_REGEX) || 
      (self.class.remember_attachments.map { |attachment_name_pattern|
        a.match attachment_name_pattern
      }.inject(false) {|b, sum| sum || b})
  end 
end

#attachments_to_rememberObject

Returns a list of attachments it should remember.



129
130
131
132
133
134
135
136
137
# File 'lib/memories/base.rb', line 129

def attachments_to_remember
  return [] unless self.class.remember_attachments?
  (self.database.get(self.id)["_attachments"] || {}).keys.reject do |a| 
    a.match(VERSION_REGEX) || 
      !(self.class.remember_attachments.map { |attachment_name_pattern|
        a.match attachment_name_pattern
      }.inject(false) {|b, sum| sum || b})
  end 
end

#current_versionObject

Returns a simple version number (integer) corresponding to the current revision. For example, suppose the current revision (_rev) is: “4-jkfdlsi9432943wklrejwalr94302”.

my_doc.current_version #==> 4


225
226
227
# File 'lib/memories/base.rb', line 225

def current_version
  version_number rev
end

#latest_milestoneObject

Returns the metadata (version, annotations) for the latest milestone created.



314
315
316
# File 'lib/memories/base.rb', line 314

def latest_milestone
  self.milestones.last
end

#logical_revisionObject

When you soft revert a document, you can ask what the logical revision of it is. For example, suppose you soft revert a document with 10 versions to version 2.

@doc.revert_to 2

When you ask the logical revision, you’ll receive the revision number of version 2:

@doc.logical_revision #==> 'rev-2-kfdlsa432890432890432'

However, as soon as you save the document, the logical_revision will simply mirror the actual revision

@doc.save
@doc.rev #==> '11-qwerty1234567890'
@doc.logical_revision #==> 'rev-11-qwerty1234567890'


337
338
339
# File 'lib/memories/base.rb', line 337

def logical_revision
  @logical_revision || "rev-" + self.rev 
end

#logical_version_numberObject

When you soft revert a document, you can ask what the logical version number of it is. For example, suppose you soft revert a document with 10 versions to version 2.

@doc.revert_to 2

When you ask the logical version number, you’ll receive 2:

@doc.logical_version_number #==> 2

However, as soon as you save the document, the logical_revision will simply mirror the actual revision

@doc.save
@doc.current_version #==> 11
@doc.logical_version_number #==> 11


350
351
352
# File 'lib/memories/base.rb', line 350

def logical_version_number
  @logical_version_number || self.current_version
end

#milestone!(&block) ⇒ Object

Flag the current version as a milestone. You can optionally annotate the milestone by passing a do block to the method.

some_article.milestone! do
  notes "Passed first round of editing."
  approved_by "Joe the editor."
end

You may annotate with whatever properties you desire. “notes” and “approved_by” were simply examples.



247
248
249
250
251
252
# File 'lib/memories/base.rb', line 247

def milestone!(&block)
  annotations = Memories::Annotation.new
  annotations.instance_eval(&block) if block
  self.milestone_memories << {:version => self.rev, :annotations => annotations.to_hash}
  self.save
end

#milestone?Boolean

Returns true if this instance represents a milestone

Returns:

  • (Boolean)


319
320
321
# File 'lib/memories/base.rb', line 319

def milestone?
  self.milestones.collect(&:version_number).include? self.logical_version_number
end

#milestone_commit?Boolean

Returns true if this instance is the version made right after a milestone, to denote the previous version as a milestone.

Returns:

  • (Boolean)


324
325
326
# File 'lib/memories/base.rb', line 324

def milestone_commit?
  self.milestones.collect(&:version_number).include? self.logical_version_number - 1
end

#milestonesObject

As of version 0.2.0, Memories also supports milestones. Milestones are special versions that you want to flag in some way. For example, suppose you were creating a content management system, and every time someone publishes an article to the website, you want to flag the version they published as a milestone.

class Article < CouchRest::Model::Base
  include Memories
  use_database SOME_DATABASE

  property :title
  property :author
  property :body

  def publish!
    # .... publishing logic
  end
end

a = Article.create(
  :title => "Memories gem makes versioning simple", 
  :author => "moonmaster9000", 
  :body => <<-ARTICLE
    Check it out at http://github.com/moonmaster9000/memories
  ARTICLE
)
a.save
a.publish!
a.current_version #==> 1 
a.milestone! do
  name "First publish."
  notes "Passed all relevant editing. Signed off by moonmaster10000"
end

Notice that we annotated our milestone; we gave it a name, and some notes. You can annotate with whatever properties you desire. The annotation do block is entirely optional. Now that we’ve created a milestone, let’s inspect it via the ‘milestones` array:

a.milestones.count #==> 1
a.milestones.last.version # ==> 1
a.milestones.last.version # ==> 1
a.milestones.last.annotations.name ==> "First publish."
a.milestones.last.annotations.notes ==> "Passed all relevant editing. Signed off by moonmaster10000"

Now, let’s imagine that we’ve made some more edits / saves to the document, but they don’t get approved. Now we want to revert to the version the document was at at the first milestone. How do we do that? Simple!

a.revert_to_milestone! 1

And now our document properties are back to the where they were when we first published the document.

If you want to access the data from a milestone, simply use the “data” method:

a.milestones.first.data.title #==> returns the "title" attribute on the first milestone
a.milestones.each do |m|
  puts "Version: " + m.version
  puts "Title: " + m.data.title
end


309
310
311
# File 'lib/memories/base.rb', line 309

def milestones
  @milestones_proxy ||= MilestonesProxy.new self
end

#previous_versionObject

Shortcut for:

my_doc.current_version - 1


218
219
220
# File 'lib/memories/base.rb', line 218

def previous_version
  current_version - 1
end

#revert_to(version) ⇒ Object

Same as #revert_to!, except that it doesn’t save.



160
161
162
163
# File 'lib/memories/base.rb', line 160

def revert_to(version)
  revert version
  self
end

#revert_to!(version) ⇒ Object

Revert the document to a specific version and save. You can provide either a complete revision number (“1-u54abz3948302sjjej3jej300rj”, or “rev-1-u54abz3948302sjjej3jej300rj”) or simply a number (e.g, 1, 4, 100, etc.).

my_doc.revert_to! 3 # ==> would revert your document "my_doc" to version 3.


154
155
156
157
# File 'lib/memories/base.rb', line 154

def revert_to!(version)
  revert version, :hard
  self
end

#revert_to_milestone(n) ⇒ Object

Same as #revert_to_milestone!, except it doesn’t save.



185
186
187
188
# File 'lib/memories/base.rb', line 185

def revert_to_milestone(n)
  verify_milestone_exists n
  self.revert_to self.milestones[n.to_i-1].version
end

#revert_to_milestone!(n) ⇒ Object

Revert to a given milestone and save. Milestones are stored in the .milestones array. Reverting to milestone “n” reverts to milestone represented in the nth element in the milestones array.



179
180
181
182
# File 'lib/memories/base.rb', line 179

def revert_to_milestone!(n)
  verify_milestone_exists n
  self.revert_to! self.milestones[n.to_i-1].version
end

#rollbackObject

Same as #rollback!, but doesn’t save.



166
167
168
# File 'lib/memories/base.rb', line 166

def rollback
  self.revert_to self.previous_version
end

#rollback!Object

Revert to the previous version, and resave the document. Shortcut for:

my_doc.revert_to! my_doc.previous_version


172
173
174
# File 'lib/memories/base.rb', line 172

def rollback!
  self.revert_to! self.previous_version
end

#rollback_to_latest_milestoneObject

Same as #rollback_to_latest_milestone, but doesn’t save.



191
192
193
# File 'lib/memories/base.rb', line 191

def rollback_to_latest_milestone
  self.revert_to_milestone self.milestones.count
end

#rollback_to_latest_milestone!Object

Reverts to the latest milestone. Shortcut for:

my_doc.revert_to_milestone! my_doc.milestones.count


197
198
199
# File 'lib/memories/base.rb', line 197

def rollback_to_latest_milestone!
  self.revert_to_milestone! self.milestones.count
end

#save_with_destroying_logical_version_and_revision(options = {}) ⇒ Object



354
355
356
357
358
# File 'lib/memories/base.rb', line 354

def save_with_destroying_logical_version_and_revision(options={})
  @logical_version_number = nil
  @logical_revision = nil
  save_without_destroying_logical_version_and_revision(options)
end

#save_with_destroying_logical_version_and_revision!Object



360
361
362
363
364
# File 'lib/memories/base.rb', line 360

def save_with_destroying_logical_version_and_revision!
  @logical_version_number = nil
  @logical_revision = nil
  save_without_destroying_logical_version_and_revision!
end

#version_id(version_num) ⇒ Object

Retrieve the entire revision number, given an integer. For example, suppose my doc has 5 versions.

my_doc.version_id 4 #==> "rev-4-74fj838r838fhjkdfklasdjrieu4839493"


204
205
206
# File 'lib/memories/base.rb', line 204

def version_id(version_num)
  self["_attachments"].keys.select {|a| a.match /^rev-#{version_num}-.*$/}.first if self["_attachments"]
end

#version_number(version_id) ⇒ Object

Retrieve the version number, given a revision id. For example,

my_doc.version_number "rev-5-kjfldsjaiu932489023rewar" #==> 5
my_doc.version_number "4-jkfldsjli3290843029irelajfldsa" # ==> 4


212
213
214
# File 'lib/memories/base.rb', line 212

def version_number(version_id)
  version_id.gsub(VERSION_REGEX, '\1').to_i
end

#versionsObject

Provides array-like and hash-like access to the versions of your document.

@doc.versions[1] # ==> returns version 1 of your document
@doc.versions['rev-1-kjfdsla3289430289432'] # ==> returns version 1 of your document
@doc.versions[5..20] # ==> returns versions 5 through 20 of your document
@doc.versions.count # ==> returns the number of versions of your document
@doc.versions.last # ==> returns the latest version of your document
@doc.versions.first # ==> returns the first version of your document


236
237
238
# File 'lib/memories/base.rb', line 236

def versions
  @versions ||= VersionsProxy.new self
end