Module: Collavre::Creative::Describable

Extended by:
ActiveSupport::Concern
Included in:
Collavre::Creative
Defined in:
app/models/collavre/creative/describable.rb

Instance Method Summary collapse

Instance Method Details

#attachment_node_html(blob) ⇒ Object

HTML for embedding a blob inline, branching on content type. The proxy path MUST match what extract_signed_ids_from_description scans and what the sanitizer allows.



79
80
81
82
83
84
85
86
87
88
89
# File 'app/models/collavre/creative/describable.rb', line 79

def attachment_node_html(blob)
  url = "/public-assets/blobs/#{blob.signed_id}/#{blob.filename.sanitized}"
  name = ERB::Util.html_escape(blob.filename.to_s)
  if blob.content_type.to_s.start_with?("image/")
    %(<img src="#{url}" alt="#{name}">)
  elsif blob.content_type.to_s.start_with?("video/")
    %(<video controls src="#{url}"></video>)
  else
    %(<a href="#{url}" download="#{name}" data-filesize="#{blob.byte_size}">#{name}</a>)
  end
end

#attachments_embeddable?Boolean

Read-only-source creatives reject any description change (description_cannot_change_if_read_only_source), so embedding would raise and orphan the blob. Callers MUST check this before creating the blob.

Returns:

  • (Boolean)


56
57
58
# File 'app/models/collavre/creative/describable.rb', line 56

def attachments_embeddable?
  !effective_origin.read_only_source?
end

#creative_snippetObject



49
50
51
# File 'app/models/collavre/creative/describable.rb', line 49

def creative_snippet
  CGI.unescapeHTML(ActionController::Base.helpers.strip_tags(effective_origin.description || "")).truncate(24, omission: "...")
end

#effective_description(variation_id = nil, html = true) ⇒ Object

Linked Creative의 description을 안전하게 반환



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'app/models/collavre/creative/describable.rb', line 30

def effective_description(variation_id = nil, html = true)
  if variation_id.present?
    # `find_by` on a loaded association still round-trips; the browse tree
    # preloads tags per level and reaches here once per node.
    variation_tag = if tags.loaded?
      tags.find { |tag| tag.label_id.to_s == variation_id.to_s }
    else
      tags.find_by(label_id: variation_id)
    end
    return variation_tag.value if variation_tag&.value.present?
  end
  description_val = origin_id.nil? ? description : origin.description
  if html
    description_val&.to_s || ""
  else
    ActionController::Base.helpers.strip_tags(description_val&.to_s || "")
  end
end

#embed_attachment_blob!(blob) ⇒ Object

Append an attachment node and save; after_save reconcile attaches the blob to creative.files. Linked creatives can't change their own description (it lives on the origin), so embed on effective_origin — otherwise the save raises and orphans the blob.



64
65
66
67
68
69
70
71
72
73
74
# File 'app/models/collavre/creative/describable.rb', line 64

def embed_attachment_blob!(blob)
  target = effective_origin
  return target.embed_attachment_blob!(blob) unless target == self

  node = attachment_node_html(blob)
  new_html = "#{description}#{node}"
  # Markdown-mode creatives derive description from markdown_source; demote
  # to HTML so the embedded node is the persisted source of truth.
  self.content_type_input = "html" if data&.dig("content_type") == "markdown"
  update!(description: new_html)
end

#remove_attachment!(signed_id) ⇒ Object

Remove the attachment for signed_id. HTML is the source of truth, so strip the node and let after_save reconcile detach + safe-purge. A blob that's attached but not embedded (legacy, not yet backfilled) is detached directly. Returns true if an attachment was present.



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'app/models/collavre/creative/describable.rb', line 95

def remove_attachment!(signed_id)
  target = effective_origin
  return target.remove_attachment!(signed_id) unless target == self

  blob = ActiveStorage::Blob.find_signed(signed_id)
  return false unless blob

  attachment = files.attachments.find_by(blob_id: blob.id)
  return false unless attachment

  stripped = description_without_attachment_node(blob.signed_id)
  if stripped
    # Demote markdown -> html so the stripped HTML is the persisted source
    # of truth (mirrors embed_attachment_blob!).
    self.content_type_input = "html" if data&.dig("content_type") == "markdown"
    update!(description: stripped)
  else
    detach_and_maybe_purge(attachment)
  end
  true
end