Class: Dscf::Core::FileAttachment

Inherits:
ApplicationRecord show all
Defined in:
app/models/dscf/core/file_attachment.rb

Overview

FileAttachment model - stores file metadata with polymorphic association This is the central table for ALL file attachments across ALL engines

Examples:

Direct usage (rarely needed)

attachment = Dscf::Core::FileAttachment.find(1)
attachment.download     # => binary data
attachment.url          # => "/dscf/core/files/..."
attachment.purge        # => deletes from MinIO and database

Querying across all attachments

Dscf::Core::FileAttachment.images          # => all image attachments
Dscf::Core::FileAttachment.pdfs            # => all PDF attachments
Dscf::Core::FileAttachment.sum(:size)      # => total storage used

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.count_by_modelObject



72
73
74
# File 'app/models/dscf/core/file_attachment.rb', line 72

def count_by_model
  group(:attachable_type).count
end

.ransackable_associations(_auth_object = nil) ⇒ Object



60
61
62
# File 'app/models/dscf/core/file_attachment.rb', line 60

def ransackable_associations(_auth_object = nil)
  %w[attachable uploaded_by]
end

.ransackable_attributes(_auth_object = nil) ⇒ Object

Ransack configuration for filtering



55
56
57
58
# File 'app/models/dscf/core/file_attachment.rb', line 55

def ransackable_attributes(_auth_object = nil)
  %w[id name filename content_type size attachable_type attachable_id
     uploaded_by_type uploaded_by_id position created_at updated_at]
end

.storage_by_content_typeObject



68
69
70
# File 'app/models/dscf/core/file_attachment.rb', line 68

def storage_by_content_type
  group(:content_type).sum(:size)
end

.storage_by_modelObject



64
65
66
# File 'app/models/dscf/core/file_attachment.rb', line 64

def storage_by_model
  group(:attachable_type).sum(:size)
end

.total_storageObject



50
51
52
# File 'app/models/dscf/core/file_attachment.rb', line 50

def total_storage
  sum(:size) || 0
end

Instance Method Details

#as_json(_options = {}) ⇒ Hash

Serialize for JSON API responses

Returns:

  • (Hash)


170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'app/models/dscf/core/file_attachment.rb', line 170

def as_json(_options = {})
  {
    id: id,
    file_key: file_key,
    filename: filename,
    content_type: content_type,
    size: size,
    human_size: human_size,
    extension: extension,
    is_image: image?,
    is_pdf: pdf?,
    is_video: video?,
    url: url,
    position: position,
    created_at: created_at&.iso8601,
    metadata: 
  }
end

#attached?Boolean

Check if attachment is properly persisted

Returns:

  • (Boolean)


150
151
152
# File 'app/models/dscf/core/file_attachment.rb', line 150

def attached?
  persisted? && file_key.present?
end

#downloadString

Download the actual file content from MinIO

Returns:

  • (String)

    binary file data



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

def download
  result = storage_client.download(file_key)
  result[:data]
rescue FileStorage::Client::DownloadError => e
  Rails.logger.error("Failed to download file #{file_key}: #{e.message}")
  nil
end

#exists_in_storage?Boolean

Check if the file exists in MinIO storage

Returns:

  • (Boolean)


107
108
109
110
111
# File 'app/models/dscf/core/file_attachment.rb', line 107

def exists_in_storage?
  storage_client.exists?(file_key)
rescue StandardError
  false
end

#extensionString?

Get the file extension

Returns:

  • (String, nil)


115
116
117
118
119
120
# File 'app/models/dscf/core/file_attachment.rb', line 115

def extension
  return nil unless filename

  ext = File.extname(filename).delete_prefix(".")
  ext.presence
end

#human_sizeString?

Get human-readable file size

Returns:

  • (String, nil)


124
125
126
127
128
# File 'app/models/dscf/core/file_attachment.rb', line 124

def human_size
  return nil unless size

  ActiveSupport::NumberHelper.number_to_human_size(size)
end

#image?Boolean

Check if this is an image file

Returns:

  • (Boolean)


132
133
134
# File 'app/models/dscf/core/file_attachment.rb', line 132

def image?
  content_type&.start_with?("image/")
end

#pdf?Boolean

Check if this is a PDF file

Returns:

  • (Boolean)


138
139
140
# File 'app/models/dscf/core/file_attachment.rb', line 138

def pdf?
  content_type == "application/pdf"
end

#purgeBoolean

Delete file from MinIO and destroy the record

Returns:

  • (Boolean)


156
157
158
159
# File 'app/models/dscf/core/file_attachment.rb', line 156

def purge
  purge_from_storage
  destroy
end

#purge!Boolean

Delete file from MinIO and destroy the record (bang version)

Returns:

  • (Boolean)


163
164
165
166
# File 'app/models/dscf/core/file_attachment.rb', line 163

def purge!
  purge_from_storage
  destroy!
end

#url(_expires_in: 1.hour) ⇒ String

Get the download URL for this attachment Works across all engines that mount dscf-core

Parameters:

  • _expires_in (ActiveSupport::Duration) (defaults to: 1.hour)

    URL expiration time (reserved for future signed URLs)

Returns:

  • (String)

    the download URL



91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'app/models/dscf/core/file_attachment.rb', line 91

def url(_expires_in: 1.hour)
  # Try to get URL through main app's mounted engine routes (most reliable)
  if defined?(Rails.application) && Rails.application.routes.named_routes.key?(:dscf_core)
    Rails.application.routes.url_helpers.dscf_core.file_download_path(file_key: file_key)
  else
    # Fallback: Use engine routes directly (works when engine is the main app or in tests)
    Dscf::Core::Engine.routes.url_helpers.file_download_path(file_key: file_key)
  end
rescue StandardError
  # Last resort fallback with configurable base path
  base_path = Dscf::Core.file_download_base_path
  "#{base_path}/#{file_key}"
end

#video?Boolean

Check if this is a video file

Returns:

  • (Boolean)


144
145
146
# File 'app/models/dscf/core/file_attachment.rb', line 144

def video?
  content_type&.start_with?("video/")
end