Class: RivetCms::DocumentRevision

Inherits:
ApplicationRecord show all
Defined in:
app/models/rivet_cms/document_revision.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.copy_owned_into(source, target) ⇒ Object

Snapshots carry kept-field data only: a soft-deleted field's values may linger on the draft (undiscard recovers them), but copying them would fail the clone's field association, which resolves through Field's kept-only default scope.



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'app/models/rivet_cms/document_revision.rb', line 98

def self.copy_owned_into(source, target)
  source.content_values.where(field_id: Field.select(:id)).find_each do |cv|
    clone = target.content_values.build(field_id: cv.field_id, media_asset_id: cv.media_asset_id)
    ContentValue::VALUE_COLUMNS.each { |col| clone[col] = cv[col] }
    clone.save!
  end

  # Same shape as the kept-field filter: a trashed target would fail the
  # clone's required belongs_to, so it is dropped rather than raising.
  # For restore_owned_into that drop is permanent: the copied draft loses
  # the link even if the target later leaves the trash. A rollback UI
  # should count these and tell the user, as DraftWriter does.
  source.relations.where(field_id: Field.select(:id), target_document_id: Document.select(:id)).find_each do |relation|
    target.relations.create!(
      field_id: relation.field_id,
      target_document_id: relation.target_document_id,
      position: relation.position
    )
  end

  source.component_instances.where(field_id: Field.select(:id)).find_each do |instance|
    clone = target.component_instances.create!(
      field_id: instance.field_id,
      component_id: instance.component_id,
      position: instance.position
    )
    copy_owned_into(instance, clone)
  end
end

.restore_owned_into(source, target) ⇒ Object

Replaces the target draft's owned records with the source's. copy_owned_into alone cannot do this: content values would collide on their per-field uniqueness and relations and component instances would silently duplicate. This is the primitive a revision-history rollback builds on.

requires_new opens a savepoint so a failed copy cannot leave the target empty when a caller rescues inside its own transaction. Values for soft-deleted fields are left alone, since the copy cannot restore them and undiscard is expected to recover them.

Raises:

  • (ArgumentError)


81
82
83
84
85
86
87
88
89
90
91
92
# File 'app/models/rivet_cms/document_revision.rb', line 81

def self.restore_owned_into(source, target)
  raise ArgumentError, "cannot restore a revision into itself" if source.id == target.id
  raise ArgumentError, "restore target must be a draft revision" unless target.draft?

  transaction(requires_new: true) do
    target.content_values.where(field_id: Field.select(:id)).destroy_all
    target.relations.where(field_id: Field.select(:id)).destroy_all
    target.component_instances.where(field_id: Field.select(:id)).destroy_all
    copy_owned_into(source, target)
  end
  target
end

Instance Method Details

#publish!(publisher: nil, publisher_name: nil) ⇒ Object

publisher attributes the snapshot to whoever published it, which is not always the person who last saved the draft.

Raises:



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'app/models/rivet_cms/document_revision.rb', line 41

def publish!(publisher: nil, publisher_name: nil)
  raise TrashedEntryError, "entry is in the trash; restore it before publishing" if document.nil?
  raise RemovedContentTypeError, "content type was removed; restore it before publishing" unless ContentType.exists?(id: document.content_type_id)

  validator = ContentValidator.new(self).validate
  raise ContentInvalidError, validator.errors unless validator.valid?

  snapshot = transaction do
    published = document.revisions.create!(
      locale: locale,
      schema_version: schema_version,
      author: publisher_name.present? ? publisher : author,
      author_name: publisher_name.presence || author_name,
      state: :published,
      published_at: Time.current
    )
    self.class.copy_owned_into(self, published)
    document.update!(published_revision: published)
    # keep_ids protects the source: republishing an old snapshot as a
    # rollback should not destroy the snapshot it rolled back to.
    RevisionPruner.new(document, keep_ids: [ id ]).prune!
    published
  end

  # Hooks fire only once the write is durable, even under an outer
  # transaction; the gem requires Rails 7.2+ for this guarantee
  ActiveRecord.after_all_transactions_commit { Hooks.run(:publish, snapshot) }
  snapshot
end