Class: GeneratedPdf

Inherits:
ApplicationRecord show all
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.

%w[generated edited reconstructed].freeze
STALE_AFTER =

Stale after.

48.hours

Constants included from Schedulable

Schedulable::SIMPLE_FORM_OPTIONS

Instance Attribute Summary collapse

Belongs to collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#kindObject (readonly)



60
# File 'app/models/generated_pdf.rb', line 60

validates :kind,   inclusion: { in: KINDS }

#statusObject (readonly)



59
# File 'app/models/generated_pdf.rb', line 59

validates :status, inclusion: { in: STATUSES }

Class Method Details

.importedActiveRecord::Relation<GeneratedPdf>

A relation of GeneratedPdfs that are imported. Active Record Scope

Returns:

See Also:



63
# File 'app/models/generated_pdf.rb', line 63

scope :imported, -> { where(status: 'imported') }

.pendingActiveRecord::Relation<GeneratedPdf>

A relation of GeneratedPdfs that are pending. Active Record Scope

Returns:

See Also:



62
# File 'app/models/generated_pdf.rb', line 62

scope :pending,  -> { where(status: 'pending') }

.rejectedActiveRecord::Relation<GeneratedPdf>

A relation of GeneratedPdfs that are rejected. Active Record Scope

Returns:

See Also:



64
# File 'app/models/generated_pdf.rb', line 64

scope :rejected, -> { where(status: 'rejected') }

.staleActiveRecord::Relation<GeneratedPdf>

A relation of GeneratedPdfs that are stale. Active Record Scope

Returns:

See Also:



65
# File 'app/models/generated_pdf.rb', line 65

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.

Parameters:

  • bytes (String)

    raw PDF data

  • filename (String)


104
105
106
107
108
109
110
111
112
113
114
# File 'app/models/generated_pdf.rb', line 104

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_byAccount

Returns:

See Also:



50
# File 'app/models/generated_pdf.rb', line 50

belongs_to :created_by,         class_name: 'Account', optional: true

#file_stored?Boolean

Returns:

  • (Boolean)


94
95
96
# File 'app/models/generated_pdf.rb', line 94

def file_stored?
  file_uid.present? && file.present?
end

#file_urlObject

App-served URL for embedding the staged PDF in the review viewer.



88
89
90
91
92
# File 'app/models/generated_pdf.rb', line 88

def file_url
  file&.url
rescue StandardError
  nil
end

#imported?Boolean

Returns:

  • (Boolean)


68
# File 'app/models/generated_pdf.rb', line 68

def imported? = status == 'imported'

#pdf_bytesObject

The staged PDF as raw bytes (nil if nothing stored).



72
73
74
# File 'app/models/generated_pdf.rb', line 72

def pdf_bytes
  file&.data
end

#pending?Boolean

Returns:

  • (Boolean)


67
# File 'app/models/generated_pdf.rb', line 67

def pending?  = status == 'pending'

#rejected?Boolean

Returns:

  • (Boolean)


69
# File 'app/models/generated_pdf.rb', line 69

def rejected? = status == 'rejected'

#source_publicationItem

Returns:

See Also:



48
# File 'app/models/generated_pdf.rb', line 48

belongs_to :source_publication, class_name: 'Item', optional: true

#source_recordSourceRecord

Returns:

  • (SourceRecord)

See Also:



49
# File 'app/models/generated_pdf.rb', line 49

belongs_to :source_record,      polymorphic: true,  optional: true

#to_temp_pathString?

Materialize the staged PDF to a local temp path (for re-processing / import).
Caller is responsible for deleting the returned path.

Returns:

  • (String, nil)


79
80
81
82
83
84
85
# File 'app/models/generated_pdf.rb', line 79

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