diff options
Diffstat (limited to 'app/models')
| -rw-r--r-- | app/models/node.rb | 107 |
1 files changed, 51 insertions, 56 deletions
diff --git a/app/models/node.rb b/app/models/node.rb index f5b76b45..a401ed69 100644 --- a/app/models/node.rb +++ b/app/models/node.rb | |||
| @@ -6,8 +6,8 @@ class Node < ApplicationRecord | |||
| 6 | has_many :pages, -> { order("revision ASC") }, :dependent => :destroy | 6 | has_many :pages, -> { order("revision ASC") }, :dependent => :destroy |
| 7 | 7 | ||
| 8 | # Entries where this node is the primary subject (fast-path column). | 8 | # Entries where this node is the primary subject (fast-path column). |
| 9 | # For a *complete* history -- including subtree trash/destroy entries | 9 | # For a *complete* history, including subtree trash/destroy entries |
| 10 | # recorded at an ancestor -- use participated_actions instead. | 10 | # recorded at an ancestor, use participated_actions instead. |
| 11 | has_many :node_actions, :dependent => :nullify | 11 | has_many :node_actions, :dependent => :nullify |
| 12 | has_many :action_participations, :class_name => "ActionParticipant", :as => :subject | 12 | has_many :action_participations, :class_name => "ActionParticipant", :as => :subject |
| 13 | has_many :participated_actions, :through => :action_participations, :source => :node_action | 13 | has_many :participated_actions, :through => :action_participations, :source => :node_action |
| @@ -15,7 +15,7 @@ class Node < ApplicationRecord | |||
| 15 | belongs_to :head, :class_name => "Page", :foreign_key => :head_id, optional: true | 15 | belongs_to :head, :class_name => "Page", :foreign_key => :head_id, optional: true |
| 16 | belongs_to :draft, :class_name => "Page", :foreign_key => :draft_id, optional: true | 16 | belongs_to :draft, :class_name => "Page", :foreign_key => :draft_id, optional: true |
| 17 | # Autosave pages carry no node_id, so has_many :pages does not cover | 17 | # Autosave pages carry no node_id, so has_many :pages does not cover |
| 18 | # them -- this dependent: :destroy is their only cleanup on node destroy. | 18 | # them. This dependent: :destroy is their only cleanup on node destroy. |
| 19 | belongs_to :autosave, :class_name => "Page", :foreign_key => :autosave_id, :dependent => :destroy, optional: true | 19 | belongs_to :autosave, :class_name => "Page", :foreign_key => :autosave_id, :dependent => :destroy, optional: true |
| 20 | 20 | ||
| 21 | has_many :events, :dependent => :destroy | 21 | has_many :events, :dependent => :destroy |
| @@ -44,8 +44,8 @@ class Node < ApplicationRecord | |||
| 44 | :if => :default_template_name_changed? | 44 | :if => :default_template_name_changed? |
| 45 | 45 | ||
| 46 | # Everything outside the Trash subtree, the Trash node included. | 46 | # Everything outside the Trash subtree, the Trash node included. |
| 47 | # Relies on unique_name being authoritative for tree position -- | 47 | # Relies on unique_name being authoritative for tree position. |
| 48 | # the same trust public routing places in it. | 48 | # The same trust public routing places in it. |
| 49 | scope :not_in_trash, -> { | 49 | scope :not_in_trash, -> { |
| 50 | where.not(:unique_name => CccConventions::TRASH_SLUG) | 50 | where.not(:unique_name => CccConventions::TRASH_SLUG) |
| 51 | .where("unique_name NOT LIKE ?", "#{CccConventions::TRASH_SLUG}/%") | 51 | .where("unique_name NOT LIKE ?", "#{CccConventions::TRASH_SLUG}/%") |
| @@ -93,7 +93,7 @@ class Node < ApplicationRecord | |||
| 93 | # Instance Methods | 93 | # Instance Methods |
| 94 | 94 | ||
| 95 | # Acquires (or reaffirms) the editing lock without creating a draft or | 95 | # Acquires (or reaffirms) the editing lock without creating a draft or |
| 96 | # an autosave -- both are now deferred until there is real content to | 96 | # an autosave, both are now deferred until there is real content to |
| 97 | # hold. | 97 | # hold. |
| 98 | def lock_for_editing! current_user | 98 | def lock_for_editing! current_user |
| 99 | if self.lock_owner.nil? || self.lock_owner == current_user | 99 | if self.lock_owner.nil? || self.lock_owner == current_user |
| @@ -133,11 +133,11 @@ class Node < ApplicationRecord | |||
| 133 | 133 | ||
| 134 | # Promotes the current autosave into the draft (creating the draft if | 134 | # Promotes the current autosave into the draft (creating the draft if |
| 135 | # none exists yet) and destroys the autosave afterward. This is what | 135 | # none exists yet) and destroys the autosave afterward. This is what |
| 136 | # the explicit "Save" action does; it never creates a new revision -- | 136 | # the explicit "Save" action does; it never creates a new revision, |
| 137 | # same as any other in-place draft edit. The new draft is created via | 137 | # same as any other in-place draft edit. The new draft is created via |
| 138 | # self.pages.create! rather than by repointing the autosave's own | 138 | # self.pages.create! rather than by repointing the autosave's own |
| 139 | # node_id, because acts_as_list assigns the revision number at create | 139 | # node_id, because acts_as_list assigns the revision number at create |
| 140 | # time, scoped to node_id -- a page created with node_id nil and | 140 | # time, scoped to node_id, a page created with node_id nil and |
| 141 | # reassigned afterward would carry a wrong or missing revision number. | 141 | # reassigned afterward would carry a wrong or missing revision number. |
| 142 | def save_draft! current_user | 142 | def save_draft! current_user |
| 143 | assert_locked_by! current_user | 143 | assert_locked_by! current_user |
| @@ -178,7 +178,7 @@ class Node < ApplicationRecord | |||
| 178 | 178 | ||
| 179 | # Which layer-pairs are meaningful to compare right now, given this | 179 | # Which layer-pairs are meaningful to compare right now, given this |
| 180 | # node's actual state. Head vs autosave only shows up when no draft | 180 | # node's actual state. Head vs autosave only shows up when no draft |
| 181 | # sits between them -- with a draft present, autosave is compared | 181 | # sits between them, with a draft present, autosave is compared |
| 182 | # against the draft, never past it straight to head. | 182 | # against the draft, never past it straight to head. |
| 183 | def available_layer_pairs | 183 | def available_layer_pairs |
| 184 | pairs = [] | 184 | pairs = [] |
| @@ -201,8 +201,8 @@ class Node < ApplicationRecord | |||
| 201 | self.draft.reload | 201 | self.draft.reload |
| 202 | end | 202 | end |
| 203 | 203 | ||
| 204 | # Discards exactly the topmost non-empty layer -- autosave if present, | 204 | # Discards exactly the topmost non-empty layer: autosave if present, |
| 205 | # else draft -- and reveals whatever's beneath it. Releases the lock | 205 | # else draft, and reveals whatever's beneath it. Releases the lock |
| 206 | # only once nothing is left to protect (no draft survives); leaves it | 206 | # only once nothing is left to protect (no draft survives); leaves it |
| 207 | # alone whenever a draft remains, since #edit still has real content | 207 | # alone whenever a draft remains, since #edit still has real content |
| 208 | # open. | 208 | # open. |
| @@ -313,7 +313,7 @@ class Node < ApplicationRecord | |||
| 313 | # Moves this node and its subtree into the Trash. Demotes every head | 313 | # Moves this node and its subtree into the Trash. Demotes every head |
| 314 | # in the subtree first (aggregators and search operate on heads | 314 | # in the subtree first (aggregators and search operate on heads |
| 315 | # regardless of tree position); where a node has no draft, the former | 315 | # regardless of tree position); where a node has no draft, the former |
| 316 | # head becomes its draft so content stays editable and restorable -- | 316 | # head becomes its draft so content stays editable and restorable, |
| 317 | # otherwise the former head remains a plain revision. One log entry, | 317 | # otherwise the former head remains a plain revision. One log entry, |
| 318 | # at the root, carrying the leaving-public-view snapshot. | 318 | # at the root, carrying the leaving-public-view snapshot. |
| 319 | def trash! current_user = nil | 319 | def trash! current_user = nil |
| @@ -475,28 +475,17 @@ class Node < ApplicationRecord | |||
| 475 | end | 475 | end |
| 476 | end | 476 | end |
| 477 | 477 | ||
| 478 | # Attaches an asset to every current lifecycle row -- head, draft and | 478 | # Attaches an asset to the node's draft, creating one from the head if |
| 479 | # autosave -- that does not already carry it. Attachments are page- | 479 | # none exists. |
| 480 | # scoped content (RelatedAsset belongs_to :page; drafts and autosaves | ||
| 481 | # are wholesale clones), so attaching to a single layer is how an | ||
| 482 | # attachment gets silently lost when another layer replaces it at | ||
| 483 | # publish or save. This is the out-of-band counterpart to the | ||
| 484 | # in-editor attach UI; it refuses when someone else holds the editing | ||
| 485 | # lock. Attaching to a head row changes the public page immediately, | ||
| 486 | # by design -- same reasoning as formalizing an already-existing | ||
| 487 | # editorial link. | ||
| 488 | # | 480 | # |
| 489 | # headline is a node-level decision: the flag is set on the newly | 481 | # Refuses when an autosave exists: it predates this attach and would |
| 490 | # created joins only when no current row has a headline yet and the | 482 | # overwrite the draft at save_draft!, silently dropping the join. |
| 491 | # asset is eligible; otherwise the asset is attached plain and the | 483 | # Refuses when someone else holds the lock, so an editor's work is not |
| 492 | # result says why, so the caller can point the editor at the star in | 484 | # altered under them. |
| 493 | # the editor instead. | ||
| 494 | # | 485 | # |
| 495 | # Returns { :attached => n, :already => n, | 486 | # Returns { :attached => n, :already => n, :draft_created => bool, |
| 496 | # :headline => nil | :set | :kept_existing | :not_eligible } | 487 | # :headline => nil | :set | :kept_existing | :not_eligible } |
| 497 | def attach_asset! asset, user:, headline: false | 488 | def attach_asset! asset, user:, headline: false |
| 498 | guard_live_change!(user) | ||
| 499 | |||
| 500 | if in_trash? || trash_node? | 489 | if in_trash? || trash_node? |
| 501 | errors.add(:base, :attach_in_trash) | 490 | errors.add(:base, :attach_in_trash) |
| 502 | raise ActiveRecord::RecordInvalid.new(self) | 491 | raise ActiveRecord::RecordInvalid.new(self) |
| @@ -510,37 +499,43 @@ class Node < ApplicationRecord | |||
| 510 | ) | 499 | ) |
| 511 | end | 500 | end |
| 512 | 501 | ||
| 513 | rows = [head, draft, autosave].compact | 502 | if autosave |
| 514 | to_attach = rows.reject { |row| row.related_assets.exists?(:asset_id => asset.id) } | 503 | errors.add(:base, :attach_with_autosave) |
| 504 | raise ActiveRecord::RecordInvalid.new(self) | ||
| 505 | end | ||
| 515 | 506 | ||
| 516 | headline_state = | 507 | source = draft || head |
| 517 | if headline && to_attach.any? | 508 | if source && source.related_assets.exists?(:asset_id => asset.id) |
| 518 | if !(asset.image? || asset.pdf?) | 509 | return { :attached => 0, |
| 519 | :not_eligible | 510 | :already => 1, |
| 520 | elsif rows.any? { |row| row.headline_asset.present? } | 511 | :draft_created => false, |
| 521 | :kept_existing | 512 | :headline => nil } |
| 522 | else | 513 | end |
| 523 | :set | 514 | |
| 524 | end | 515 | draft_created = draft.nil? |
| 525 | end | ||
| 526 | 516 | ||
| 527 | ActiveRecord::Base.transaction do | 517 | ActiveRecord::Base.transaction do |
| 528 | to_attach.each do |row| | 518 | create_new_draft(user) if draft_created |
| 529 | row.related_assets.create!(:asset => asset, :headline => headline_state == :set) | 519 | page = draft.reload |
| 530 | end | ||
| 531 | 520 | ||
| 532 | if to_attach.any? | 521 | headline_state = |
| 533 | metadata = { :asset_name => asset.name, | 522 | if headline |
| 534 | :path => asset.upload.url.sub(/\?\d+$/, "") } | 523 | if !(asset.image? || asset.pdf?) |
| 535 | metadata[:headline] = true if headline_state == :set | 524 | :not_eligible |
| 536 | NodeAction.record!(:node => self, :participants => [self, asset], | 525 | elsif page.headline_asset.present? |
| 537 | :user => user, :action => "asset_attach", **metadata) | 526 | :kept_existing |
| 538 | end | 527 | else |
| 539 | end | 528 | :set |
| 529 | end | ||
| 530 | end | ||
| 531 | |||
| 532 | page.related_assets.create!(:asset => asset, :headline => headline_state == :set) | ||
| 540 | 533 | ||
| 541 | { :attached => to_attach.size, | 534 | { :attached => 1, |
| 542 | :already => rows.size - to_attach.size, | 535 | :already => 0, |
| 543 | :headline => headline_state } | 536 | :draft_created => draft_created, |
| 537 | :headline => headline_state } | ||
| 538 | end | ||
| 544 | end | 539 | end |
| 545 | 540 | ||
| 546 | def title | 541 | def title |
