summaryrefslogtreecommitdiff
path: root/app/models
diff options
context:
space:
mode:
Diffstat (limited to 'app/models')
-rw-r--r--app/models/node.rb107
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