Class: GeneratedPdf
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- GeneratedPdf
- Defined in:
- app/models/generated_pdf.rb
Overview
An AI-generated / reconstructed PDF staging record — the PDF analogue of
GeneratedImage. Created by +PdfGenerationWorker+ (or the Sunny pdf_generate
tool) after a successful render, pending user review in the PDF studio.
The user reviews the staged PDF (generated_pdfs#show) where they can:
- Edit the +layout+ and regenerate (iterate)
- Import it into the publication library — promotes to a Literature upload +
publication Item via GeneratedPdfImporter (which also persists +layout+
onto the Literature's +source_layout+ so it can be re-opened later) - Discard it
Stale pending records (> 48 h) are reaped by +GeneratedPdfCleanupWorker+.
The staged PDF bytes live in the Dragonfly +:secure+ store (same store as
Upload), so promotion re-materializes them into a real Literature upload.
The +layout+ JSONB is the declarative source-of-truth for iteration.
== Schema Information
Table name: generated_pdfs
Database name: primary
id :bigint not null, primary key
byte_size :integer
content_type :string default("application/pdf"), not null
error :text
file_name :string
file_uid :string
instructions :text
kind :string default("generated"), not null
layout :jsonb not null
page_count :integer
source_record_type :string
status :string default("pending"), not null
suggested_title :string
title :string
created_at :datetime not null
updated_at :datetime not null
created_by_id :bigint
source_publication_id :bigint
source_record_id :bigint
Constant Summary collapse
- STATUSES =
Statuses.
%w[pending imported rejected].freeze
- KINDS =
Kinds — how the PDF was produced.
translated= language variant of a
source publication (provider provenance in layout jsonb, imported with locales:). %w[generated edited reconstructed translated].freeze
- STALE_AFTER =
Stale after.
48.hours
- SKU_LANG_MARKERS_RE =
Language/region markers already present in SKUs (stripped before inserting
the target locale, so re-translating a translation doesn't stack markers). /-(?:FRENCH|SPANISH|GERMAN|ITALIAN|DUTCH|POLISH|SWEDISH|FRANCAIS|ESPAGNOL|CANADA|CANADIAN|USA|CA|EN-US|EN-CA|FR-CA|FR|ES|DE|IT|NL|PL|SV)(?=-[A-Z]$|$)/
Constants included from Models::Schedulable
Models::Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
-
#kind ⇒ Object
readonly
Validates kind.
-
#status ⇒ Object
readonly
Validates status.
Belongs to collapse
-
#created_by ⇒ Account?
The created by this record belongs to.
-
#source_publication ⇒ Item?
The source publication this record belongs to.
-
#source_record ⇒ SourceRecord?
The source record this record belongs to.
Class Method Summary collapse
-
.imported ⇒ ActiveRecord::Relation<GeneratedPdf>
A relation of GeneratedPdfs that are imported.
-
.pending ⇒ ActiveRecord::Relation<GeneratedPdf>
A relation of GeneratedPdfs that are pending.
-
.rejected ⇒ ActiveRecord::Relation<GeneratedPdf>
A relation of GeneratedPdfs that are rejected.
-
.stale ⇒ ActiveRecord::Relation<GeneratedPdf>
A relation of GeneratedPdfs that are stale.
Instance Method Summary collapse
-
#assign_pdf(bytes, filename:) ⇒ Object
Set the staged PDF content and derive byte size in one call.
-
#derived_translation_sku ⇒ String?
Proposed SKU for a +translated+ staged PDF: the source SKU with the target locale inserted before the revision letter, bumped (A → B → …) until unique across publications.
-
#file_stored? ⇒ Boolean
Whether the record file stored.
-
#file_url ⇒ Object
App-served URL for embedding the staged PDF in the review viewer.
-
#imported? ⇒ Boolean
Whether the record imported.
-
#pdf_bytes ⇒ Object
The staged PDF as raw bytes (nil if nothing stored).
-
#pending? ⇒ Boolean
Whether the record pending.
-
#rejected? ⇒ Boolean
Whether the record rejected.
-
#to_temp_path ⇒ String?
Materialize the staged PDF to a local temp path (for re-processing / import).
-
#translated_base_name ⇒ String?
The LLM-translated publication base name staged by PublicationTranslationWorker (+translated+ kind only) — the import form pre-fills this as the publication name so the catalog name and slug land in the target language.
-
#translation_lang_out ⇒ String?
Target locale staged by PublicationTranslationWorker (+translated+ kind only).
Methods inherited from ApplicationRecord
ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation
Methods included from Models::Schedulable
Methods included from Models::AfterCommittable
Methods included from Models::EventPublishable
Instance Attribute Details
Class Method Details
.imported ⇒ ActiveRecord::Relation<GeneratedPdf>
A relation of GeneratedPdfs that are imported. Active Record Scope
69 |
# File 'app/models/generated_pdf.rb', line 69 scope :imported, -> { where(status: 'imported') } |
.pending ⇒ ActiveRecord::Relation<GeneratedPdf>
A relation of GeneratedPdfs that are pending. Active Record Scope
68 |
# File 'app/models/generated_pdf.rb', line 68 scope :pending, -> { where(status: 'pending') } |
.rejected ⇒ ActiveRecord::Relation<GeneratedPdf>
A relation of GeneratedPdfs that are rejected. Active Record Scope
70 |
# File 'app/models/generated_pdf.rb', line 70 scope :rejected, -> { where(status: 'rejected') } |
.stale ⇒ ActiveRecord::Relation<GeneratedPdf>
A relation of GeneratedPdfs that are stale. Active Record Scope
71 |
# File 'app/models/generated_pdf.rb', line 71 scope :stale, -> { pending.where(created_at: ...STALE_AFTER.ago) } |
Instance Method Details
#assign_pdf(bytes, filename:) ⇒ Object
Set the staged PDF content and derive byte size in one call. Mirrors
+Upload.uploadify_from_data+: the custom dragonfly_accessor's after_assign
hook needs a named file (a raw string has no name), so write a temp file and
assign it. The temp file is read by Dragonfly at save time.
156 157 158 159 160 161 162 163 164 165 166 |
# File 'app/models/generated_pdf.rb', line 156 def assign_pdf(bytes, filename:) name = filename.to_s name = "#{name}.pdf" unless name.downcase.end_with?('.pdf') path = Upload.temp_location("genpdf_#{SecureRandom.hex(8)}_#{File.basename(name)}") File.binwrite(path, bytes) self.file = File.new(path) self.file_name = name # normalize (after_assign sets the temp basename) self.content_type = 'application/pdf' self.byte_size = bytes.bytesize end |
#created_by ⇒ Account?
Returns the created by this record belongs to.
53 |
# File 'app/models/generated_pdf.rb', line 53 belongs_to :created_by, class_name: 'Account', optional: true |
#derived_translation_sku ⇒ String?
Proposed SKU for a +translated+ staged PDF: the source SKU with the target
locale inserted before the revision letter, bumped (A → B → …) until
unique across publications.
TZ-MANUAL-A (fr) → TZ-MANUAL-FR-A
X-SELL-A (fr-CA) → X-SELL-FR-CA-A (taken) → X-SELL-FR-CA-B
107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'app/models/generated_pdf.rb', line 107 def derived_translation_sku return nil unless kind == 'translated' source = source_publication return nil unless source marker = translation_lang_out.to_s.upcase.presence || 'INTL' base = source.sku.to_s.sub(SKU_LANG_MARKERS_RE, '').sub(/-[A-Z]$/, '') ('A'..'Z').each do |letter| candidate = "#{base}-#{marker}-#{letter}" return candidate unless Item.publications.exists?(sku: candidate) end nil end |
#file_stored? ⇒ Boolean
Returns whether the record file stored.
146 147 148 |
# File 'app/models/generated_pdf.rb', line 146 def file_stored? file_uid.present? && file.present? end |
#file_url ⇒ Object
App-served URL for embedding the staged PDF in the review viewer.
139 140 141 142 143 |
# File 'app/models/generated_pdf.rb', line 139 def file_url file&.url rescue StandardError nil end |
#imported? ⇒ Boolean
Returns whether the record imported.
76 77 |
# File 'app/models/generated_pdf.rb', line 76 def imported? = status == 'imported' # @return [Boolean] whether the record rejected |
#pdf_bytes ⇒ Object
The staged PDF as raw bytes (nil if nothing stored).
123 124 125 |
# File 'app/models/generated_pdf.rb', line 123 def pdf_bytes file&.data end |
#pending? ⇒ Boolean
Returns whether the record pending.
74 75 |
# File 'app/models/generated_pdf.rb', line 74 def pending? = status == 'pending' # @return [Boolean] whether the record imported |
#rejected? ⇒ Boolean
Returns whether the record rejected.
78 |
# File 'app/models/generated_pdf.rb', line 78 def rejected? = status == 'rejected' |
#source_publication ⇒ Item?
Returns the source publication this record belongs to.
49 |
# File 'app/models/generated_pdf.rb', line 49 belongs_to :source_publication, class_name: 'Item', optional: true |
#source_record ⇒ SourceRecord?
Returns the source record this record belongs to.
51 |
# File 'app/models/generated_pdf.rb', line 51 belongs_to :source_record, polymorphic: true, optional: true |
#to_temp_path ⇒ String?
Materialize the staged PDF to a local temp path (for re-processing / import).
Caller is responsible for deleting the returned path.
130 131 132 133 134 135 136 |
# File 'app/models/generated_pdf.rb', line 130 def to_temp_path return nil unless file_stored? path = Rails.application.config.x.temp_storage_path.join("generated_pdf_#{id || SecureRandom.hex(4)}.pdf") File.binwrite(path, file.data) path.to_s end |
#translated_base_name ⇒ String?
The LLM-translated publication base name staged by
PublicationTranslationWorker (+translated+ kind only) — the import form
pre-fills this as the publication name so the catalog name and slug land
in the target language.
91 92 93 |
# File 'app/models/generated_pdf.rb', line 91 def translated_base_name layout.to_h.dig('translation', 'translated_base_name') end |
#translation_lang_out ⇒ String?
Target locale staged by PublicationTranslationWorker (+translated+ kind only).
82 83 84 |
# File 'app/models/generated_pdf.rb', line 82 def translation_lang_out layout.to_h.dig('translation', 'lang_out') end |