196d685f8d
Consider blobs attached to any public records accessible to anyone with the URL.
152 lines
4.6 KiB
Ruby
152 lines
4.6 KiB
Ruby
class Card < ApplicationRecord
|
|
include Assignable, Attachments, Broadcastable, Closeable, Colored, Entropic, Eventable,
|
|
Exportable, Golden, Mentions, Multistep, Pinnable, Postponable, Promptable,
|
|
Readable, Searchable, Stallable, Statuses, Storage::Tracked, Taggable, Triageable, Watchable
|
|
|
|
belongs_to :account, default: -> { board.account }
|
|
belongs_to :board
|
|
belongs_to :creator, class_name: "User", default: -> { Current.user }
|
|
|
|
has_many :comments, dependent: :destroy
|
|
has_one_attached :image, dependent: :purge_later
|
|
|
|
has_rich_text :description
|
|
|
|
before_save :set_default_title, if: :published?
|
|
before_create :assign_number
|
|
|
|
after_save -> { board.touch }, if: :published?
|
|
after_touch -> { board.touch }, if: :published?
|
|
after_update :handle_board_change, if: :saved_change_to_board_id?
|
|
|
|
scope :reverse_chronologically, -> { order created_at: :desc, id: :desc }
|
|
scope :chronologically, -> { order created_at: :asc, id: :asc }
|
|
scope :latest, -> { order last_active_at: :desc, id: :desc }
|
|
scope :with_users, -> { preload(creator: [ :avatar_attachment, :account ], assignees: [ :avatar_attachment, :account ]) }
|
|
scope :preloaded, -> { with_users.preload(:column, :tags, :steps, :closure, :goldness, :activity_spike, :image_attachment, board: [ :entropy, :columns ], not_now: [ :user ]).with_rich_text_description_and_embeds }
|
|
|
|
scope :indexed_by, ->(index) do
|
|
case index
|
|
when "stalled" then stalled
|
|
when "postponing_soon" then postponing_soon
|
|
when "closed" then closed
|
|
when "not_now" then postponed.latest
|
|
when "golden" then golden
|
|
when "draft" then drafted
|
|
else all
|
|
end
|
|
end
|
|
|
|
scope :sorted_by, ->(sort) do
|
|
case sort
|
|
when "newest" then reverse_chronologically
|
|
when "oldest" then chronologically
|
|
when "latest" then latest
|
|
else latest
|
|
end
|
|
end
|
|
|
|
delegate :accessible_to?, to: :board
|
|
|
|
def publicly_accessible?
|
|
published? && board.publicly_accessible?
|
|
end
|
|
|
|
def card
|
|
self
|
|
end
|
|
|
|
def to_param
|
|
number.to_s
|
|
end
|
|
|
|
def move_to(new_board)
|
|
transaction do
|
|
card.update!(board: new_board)
|
|
card.events.update_all(board_id: new_board.id)
|
|
end
|
|
end
|
|
|
|
def filled?
|
|
title.present? || description.present?
|
|
end
|
|
|
|
private
|
|
STORAGE_BATCH_SIZE = 1000
|
|
|
|
# Override to include comments, but only load comments that have attachments.
|
|
# Cards can have thousands of comments; most won't have attachments.
|
|
# Streams in batches to avoid loading all IDs into memory at once.
|
|
def storage_transfer_records
|
|
comment_ids_with_attachments = storage_comment_ids_with_attachments
|
|
|
|
if comment_ids_with_attachments.any?
|
|
[ self, *comments.where(id: comment_ids_with_attachments).to_a ]
|
|
else
|
|
[ self ]
|
|
end
|
|
end
|
|
|
|
def storage_comment_ids_with_attachments
|
|
direct = []
|
|
rich_text_map = {}
|
|
|
|
# Stream comment IDs in batches to avoid loading all into memory
|
|
comments.in_batches(of: STORAGE_BATCH_SIZE) do |batch|
|
|
batch_ids = batch.pluck(:id)
|
|
|
|
direct.concat \
|
|
ActiveStorage::Attachment
|
|
.where(record_type: "Comment", record_id: batch_ids)
|
|
.distinct
|
|
.pluck(:record_id)
|
|
|
|
ActionText::RichText
|
|
.where(record_type: "Comment", record_id: batch_ids)
|
|
.pluck(:id, :record_id)
|
|
.each { |rt_id, comment_id| rich_text_map[rt_id] = comment_id }
|
|
end
|
|
|
|
embed_comment_ids = if rich_text_map.any?
|
|
rich_text_map.keys.each_slice(STORAGE_BATCH_SIZE).flat_map do |batch_ids|
|
|
ActiveStorage::Attachment
|
|
.where(record_type: "ActionText::RichText", record_id: batch_ids)
|
|
.distinct
|
|
.pluck(:record_id)
|
|
end.filter_map { |rt_id| rich_text_map[rt_id] }
|
|
else
|
|
[]
|
|
end
|
|
|
|
(direct + embed_comment_ids).uniq
|
|
end
|
|
|
|
def set_default_title
|
|
self.title = "Untitled" if title.blank?
|
|
end
|
|
|
|
def handle_board_change
|
|
old_board = account.boards.find_by(id: board_id_before_last_save)
|
|
|
|
transaction do
|
|
update! column: nil
|
|
track_board_change_event(old_board.name)
|
|
grant_access_to_assignees unless board.all_access?
|
|
end
|
|
|
|
remove_inaccessible_notifications_later
|
|
end
|
|
|
|
def track_board_change_event(old_board_name)
|
|
track_event "board_changed", particulars: { old_board: old_board_name, new_board: board.name }
|
|
end
|
|
|
|
def grant_access_to_assignees
|
|
board.accesses.grant_to(assignees)
|
|
end
|
|
|
|
def assign_number
|
|
self.number ||= account.increment!(:cards_count).cards_count
|
|
end
|
|
end
|