Module: Docsmith::Versionable

Defined in:
lib/docsmith/versionable.rb

Overview

ActiveRecord mixin that adds full versioning to any model.

Usage:

class Article < ApplicationRecord
include Docsmith::Versionable
docsmith_config { content_field :body; content_type :markdown }
end

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object



12
13
14
15
# File 'lib/docsmith/versionable.rb', line 12

def self.included(base)
  base.extend(ClassMethods)
  base.after_save(:_docsmith_auto_save_callback)
end

Instance Method Details

#add_comment!(version:, body:, author:, anchor: nil, parent: nil) ⇒ Docsmith::Comments::Comment

Adds a comment to a specific version of this document.

Parameters:

  • version (Integer)

    version_number

  • body (String)
  • author (Object)

    polymorphic author

  • anchor (Hash, nil) (defaults to: nil)

    { start_offset:, end_offset: } for inline range comments

  • parent (Comments::Comment, nil) (defaults to: nil)

    parent comment for threading

Returns:



164
165
166
167
168
169
170
171
172
173
# File 'lib/docsmith/versionable.rb', line 164

def add_comment!(version:, body:, author:, anchor: nil, parent: nil)
  Comments::Manager.add!(
    _docsmith_document,
    version_number: version,
    body:           body,
    author:         author,
    anchor:         anchor,
    parent:         parent
  )
end

#auto_save_version!(author: nil) ⇒ Docsmith::DocumentVersion?

Debounced auto-save. Returns nil if debounce window has not elapsed OR content is unchanged. Both non-save cases return nil. auto_save: false in config causes this to always return nil.

Parameters:

  • author (Object, nil) (defaults to: nil)

Returns:



61
62
63
64
65
66
67
# File 'lib/docsmith/versionable.rb', line 61

def auto_save_version!(author: nil)
  config = self.class.docsmith_resolved_config
  return nil unless config[:auto_save]

  _sync_docsmith_content!
  Docsmith::AutoSave.call(_docsmith_document, author: author, config: config)
end

#commentsActiveRecord::Relation<Docsmith::Comments::Comment>

Returns all comments across all versions of this document.

Returns:



178
179
180
181
182
# File 'lib/docsmith/versionable.rb', line 178

def comments
  doc = _docsmith_document
  Comments::Comment.joins(:version)
                   .where(docsmith_versions: { document_id: doc.id })
end

#comments_on(version:, type: nil) ⇒ ActiveRecord::Relation<Docsmith::Comments::Comment>

Returns comments on a specific version, optionally filtered by anchor type.

Parameters:

  • version (Integer)

    version_number

  • type (Symbol, nil) (defaults to: nil)

    :document or :range to filter; nil = all

Returns:



189
190
191
192
193
194
195
# File 'lib/docsmith/versionable.rb', line 189

def comments_on(version:, type: nil)
  doc = _docsmith_document
  dv  = Docsmith::DocumentVersion.find_by!(document: doc, version_number: version)
  rel = Comments::Comment.where(version: dv)
  rel = rel.where(anchor_type: type.to_s) if type
  rel
end

#current_versionDocsmith::DocumentVersion?

Returns latest version.

Returns:



75
76
77
# File 'lib/docsmith/versionable.rb', line 75

def current_version
  _docsmith_document.current_version
end

#diff_between(from_version, to_version) ⇒ Docsmith::Diff::Result

Computes a diff between two named versions.

Parameters:

  • from_version (Integer)
  • to_version (Integer)

Returns:

Raises:

  • (ActiveRecord::RecordNotFound)

    if either version does not exist



149
150
151
152
153
154
# File 'lib/docsmith/versionable.rb', line 149

def diff_between(from_version, to_version)
  doc    = _docsmith_document
  v_from = Docsmith::DocumentVersion.find_by!(document: doc, version_number: from_version)
  v_to   = Docsmith::DocumentVersion.find_by!(document: doc, version_number: to_version)
  Docsmith::Diff.between(v_from, v_to)
end

#diff_from(version_number) ⇒ Docsmith::Diff::Result

Computes a diff from version N to the current (latest) version.

Parameters:

  • version_number (Integer)

Returns:

Raises:

  • (ActiveRecord::RecordNotFound)

    if version_number does not exist



136
137
138
139
140
141
# File 'lib/docsmith/versionable.rb', line 136

def diff_from(version_number)
  doc    = _docsmith_document
  v_from = Docsmith::DocumentVersion.find_by!(document: doc, version_number: version_number)
  v_to   = Docsmith::DocumentVersion.where(document_id: doc.id).order(version_number: :desc).first!
  Docsmith::Diff.between(v_from, v_to)
end

#migrate_comments!(from:, to:) ⇒ void

This method returns an undefined value.

Migrates top-level comments from one version to another.

Parameters:

  • from (Integer)

    source version_number

  • to (Integer)

    target version_number



209
210
211
# File 'lib/docsmith/versionable.rb', line 209

def migrate_comments!(from:, to:)
  Comments::Migrator.migrate!(_docsmith_document, from: from, to: to)
end

#restore_version!(number, author:) ⇒ Docsmith::DocumentVersion

Restore to a previous version. Creates a new version with the old content. Syncs restored content back to the model's content_field via update_column (bypasses after_save to prevent a duplicate auto-save). Never mutates existing versions.

Parameters:

  • number (Integer)

    version_number to restore from

  • author (Object, nil)

Returns:

Raises:



94
95
96
97
98
99
100
101
102
103
104
# File 'lib/docsmith/versionable.rb', line 94

def restore_version!(number, author:)
  result = Docsmith::VersionManager.restore!(
    _docsmith_document,
    version: number,
    author:  author,
    config:  self.class.docsmith_resolved_config
  )
  field = self.class.docsmith_resolved_config[:content_field]
  update_column(field, _docsmith_document.reload.content)
  result
end

#save_version!(author:, summary: nil) ⇒ Docsmith::DocumentVersion?

Create a new DocumentVersion snapshot of this record's content. Returns nil if content is identical to the latest version. Raises Docsmith::InvalidContentField if content_field returns a non-String and no content_extractor is configured.

Parameters:

  • author (Object, nil)
  • summary (String, nil) (defaults to: nil)

Returns:



45
46
47
48
49
50
51
52
53
# File 'lib/docsmith/versionable.rb', line 45

def save_version!(author:, summary: nil)
  _sync_docsmith_content!
  Docsmith::VersionManager.save!(
    _docsmith_document,
    author:  author,
    summary: summary,
    config:  self.class.docsmith_resolved_config
  )
end

#tag_version!(number, name:, author:) ⇒ Docsmith::VersionTag

Tag a specific version. Names are unique per document.

Parameters:

  • number (Integer)

    version_number to tag

  • name (String)
  • author (Object, nil)

Returns:



111
112
113
114
# File 'lib/docsmith/versionable.rb', line 111

def tag_version!(number, name:, author:)
  Docsmith::VersionManager.tag!(
    _docsmith_document, version: number, name: name, author: author)
end

#tagged_version(tag_name) ⇒ Docsmith::DocumentVersion?

Parameters:

  • tag_name (String)

Returns:



118
119
120
121
# File 'lib/docsmith/versionable.rb', line 118

def tagged_version(tag_name)
  tag = _docsmith_document.version_tags.find_by(name: tag_name)
  tag&.version
end

#unresolved_commentsActiveRecord::Relation<Docsmith::Comments::Comment>

Returns all unresolved comments across all versions.

Returns:



200
201
202
# File 'lib/docsmith/versionable.rb', line 200

def unresolved_comments
  comments.merge(Comments::Comment.unresolved)
end

#version(number) ⇒ Docsmith::DocumentVersion?

Parameters:

  • number (Integer)

    1-indexed version_number

Returns:



81
82
83
# File 'lib/docsmith/versionable.rb', line 81

def version(number)
  _docsmith_document.document_versions.find_by(version_number: number)
end

#version_tags(number) ⇒ Array<String>

Returns tag names on that version.

Parameters:

  • number (Integer)

    version_number

Returns:

  • (Array<String>)

    tag names on that version



125
126
127
128
129
# File 'lib/docsmith/versionable.rb', line 125

def version_tags(number)
  ver = version(number)
  return [] unless ver
  ver.version_tags.pluck(:name)
end

#versionsActiveRecord::Relation<Docsmith::DocumentVersion>

Returns ordered by version_number.

Returns:



70
71
72
# File 'lib/docsmith/versionable.rb', line 70

def versions
  _docsmith_document.document_versions
end