diff options
Diffstat (limited to 'app/models/node.rb')
| -rw-r--r-- | app/models/node.rb | 89 |
1 files changed, 74 insertions, 15 deletions
diff --git a/app/models/node.rb b/app/models/node.rb index a440c2f..7a93e79 100644 --- a/app/models/node.rb +++ b/app/models/node.rb | |||
| @@ -4,7 +4,14 @@ class Node < ApplicationRecord | |||
| 4 | 4 | ||
| 5 | # Associations | 5 | # Associations |
| 6 | has_many :pages, -> { order("revision ASC") }, :dependent => :destroy | 6 | has_many :pages, -> { order("revision ASC") }, :dependent => :destroy |
| 7 | |||
| 8 | # Entries where this node is the primary subject (fast-path column). | ||
| 9 | # For a *complete* history -- including subtree trash/destroy entries | ||
| 10 | # recorded at an ancestor -- use participated_actions instead. | ||
| 7 | has_many :node_actions, :dependent => :nullify | 11 | has_many :node_actions, :dependent => :nullify |
| 12 | has_many :action_participations, :class_name => "ActionParticipant", :as => :subject | ||
| 13 | has_many :participated_actions, :through => :action_participations, :source => :node_action | ||
| 14 | |||
| 8 | 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 |
| 9 | 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 |
| 10 | # 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 |
| @@ -312,14 +319,14 @@ class Node < ApplicationRecord | |||
| 312 | was_published = head_id.present? | 319 | was_published = head_id.present? |
| 313 | final_published_at = head&.published_at | 320 | final_published_at = head&.published_at |
| 314 | 321 | ||
| 315 | demoted = 0 | 322 | subtree = [self] + descendants.to_a |
| 316 | ([self] + descendants.to_a).each do |node| | 323 | demoted_nodes = subtree.select do |node| |
| 317 | next unless node.head_id | 324 | next false unless node.head_id |
| 318 | former = node.head | 325 | former = node.head |
| 319 | node.head = nil | 326 | node.head = nil |
| 320 | node.draft_id = former.id if node.draft_id.nil? | 327 | node.draft_id = former.id if node.draft_id.nil? |
| 321 | node.save! | 328 | node.save! |
| 322 | demoted += 1 | 329 | true |
| 323 | end | 330 | end |
| 324 | 331 | ||
| 325 | self.reload | 332 | self.reload |
| @@ -332,9 +339,10 @@ class Node < ApplicationRecord | |||
| 332 | metadata = { :path => { "from" => path_before, "to" => unique_name } } | 339 | metadata = { :path => { "from" => path_before, "to" => unique_name } } |
| 333 | metadata[:was_published] = true if was_published | 340 | metadata[:was_published] = true if was_published |
| 334 | metadata[:final_published_at] = final_published_at.iso8601 if final_published_at | 341 | metadata[:final_published_at] = final_published_at.iso8601 if final_published_at |
| 335 | metadata[:demoted_heads] = demoted if demoted > 0 | 342 | metadata[:demoted_heads] = demoted_nodes.size if demoted_nodes.any? |
| 336 | 343 | ||
| 337 | NodeAction.record!(:node => self, :user => current_user, :action => "trash", **metadata) | 344 | NodeAction.record!(:participants => subtree, :user => current_user, |
| 345 | :action => "trash", **metadata) | ||
| 338 | self | 346 | self |
| 339 | end | 347 | end |
| 340 | end | 348 | end |
| @@ -382,7 +390,8 @@ class Node < ApplicationRecord | |||
| 382 | metadata = { :path => unique_name } | 390 | metadata = { :path => unique_name } |
| 383 | metadata[:destroyed_descendants] = doomed.size - 1 if doomed.size > 1 | 391 | metadata[:destroyed_descendants] = doomed.size - 1 if doomed.size > 1 |
| 384 | 392 | ||
| 385 | NodeAction.record!(:node => self, :user => current_user, :action => "destroy", **metadata) | 393 | NodeAction.record!(:participants => doomed, :user => current_user, |
| 394 | :action => "destroy", **metadata) | ||
| 386 | doomed.each(&:destroy!) | 395 | doomed.each(&:destroy!) |
| 387 | end | 396 | end |
| 388 | end | 397 | end |
| @@ -442,8 +451,65 @@ class Node < ApplicationRecord | |||
| 442 | end | 451 | end |
| 443 | end | 452 | end |
| 444 | 453 | ||
| 454 | # Attaches an asset to every current lifecycle row -- head, draft and | ||
| 455 | # autosave -- that does not already carry it. Attachments are page- | ||
| 456 | # scoped content (RelatedAsset belongs_to :page; drafts and autosaves | ||
| 457 | # are wholesale clones), so attaching to a single layer is how an | ||
| 458 | # attachment gets silently lost when another layer replaces it at | ||
| 459 | # publish or save. This is the out-of-band counterpart to the | ||
| 460 | # in-editor attach UI; it refuses when someone else holds the editing | ||
| 461 | # lock. Attaching to a head row changes the public page immediately, | ||
| 462 | # by design -- same reasoning as formalizing an already-existing | ||
| 463 | # editorial link. | ||
| 464 | # | ||
| 465 | # headline is a node-level decision: the flag is set on the newly | ||
| 466 | # created joins only when no current row has a headline yet and the | ||
| 467 | # asset is eligible; otherwise the asset is attached plain and the | ||
| 468 | # result says why, so the caller can point the editor at the star in | ||
| 469 | # the editor instead. | ||
| 470 | # | ||
| 471 | # Returns { :attached => n, :already => n, | ||
| 472 | # :headline => nil | :set | :kept_existing | :not_eligible } | ||
| 473 | def attach_asset! asset, user:, headline: false | ||
| 474 | if in_trash? || trash_node? | ||
| 475 | raise ActiveRecord::RecordInvalid.new(self), "Cannot attach assets to a node in the Trash" | ||
| 476 | end | ||
| 477 | |||
| 478 | if lock_owner && lock_owner != user | ||
| 479 | raise( | ||
| 480 | LockedByAnotherUser, | ||
| 481 | "Page is locked by another user who is working on it! " \ | ||
| 482 | "Last modification: #{(self.autosave || self.draft || self.head).updated_at.to_fs(:db)}" | ||
| 483 | ) | ||
| 484 | end | ||
| 485 | |||
| 486 | rows = [head, draft, autosave].compact | ||
| 487 | to_attach = rows.reject { |row| row.related_assets.exists?(:asset_id => asset.id) } | ||
| 488 | |||
| 489 | headline_state = | ||
| 490 | if headline && to_attach.any? | ||
| 491 | if !(asset.image? || asset.pdf?) | ||
| 492 | :not_eligible | ||
| 493 | elsif rows.any? { |row| row.headline_asset.present? } | ||
| 494 | :kept_existing | ||
| 495 | else | ||
| 496 | :set | ||
| 497 | end | ||
| 498 | end | ||
| 499 | |||
| 500 | ActiveRecord::Base.transaction do | ||
| 501 | to_attach.each do |row| | ||
| 502 | row.related_assets.create!(:asset => asset, :headline => headline_state == :set) | ||
| 503 | end | ||
| 504 | end | ||
| 505 | |||
| 506 | { :attached => to_attach.size, | ||
| 507 | :already => rows.size - to_attach.size, | ||
| 508 | :headline => headline_state } | ||
| 509 | end | ||
| 510 | |||
| 445 | def title | 511 | def title |
| 446 | head ? head.title : draft.title | 512 | editable_page&.title |
| 447 | end | 513 | end |
| 448 | 514 | ||
| 449 | def update_unique_names? | 515 | def update_unique_names? |
| @@ -533,13 +599,6 @@ class Node < ApplicationRecord | |||
| 533 | throw :abort | 599 | throw :abort |
| 534 | end | 600 | end |
| 535 | 601 | ||
| 536 | def self.recently_changed | ||
| 537 | includes(:head).where( | ||
| 538 | "pages.updated_at < ? AND pages.updated_at > ? AND nodes.parent_id IS NOT NULL", | ||
| 539 | Time.now, Time.now - 14.days | ||
| 540 | ).order("pages.updated_at desc").references(:head) | ||
| 541 | end | ||
| 542 | |||
| 543 | protected | 602 | protected |
| 544 | def lock_for! current_user | 603 | def lock_for! current_user |
| 545 | self.lock_owner = current_user | 604 | self.lock_owner = current_user |
