Files
nairobi/app/models/document.rb
Javi Martín 930bb753c5 Use a rake task to delete cached attachments
Our previous system to delete cached attachments didn't work for
documents because the `custom_hash_data` is different for files created
from a file and files created from cached attachments.

When creating a document attachment, the name of the file is taken into
account to calculate the hash. Let's say the original file name is
"logo.pdf", and the generated hash is "123456". The cached attachment
will be "123456.pdf", so the generated hash using the cached attachment
will be something different, like "28af3". So the file that will be
removed will be "28af3.pdf", and not "123456.pdf", which will still be
present.

Furthermore, there are times where users choose a file and then they
close the browser or go to a different page. In those cases, we weren't
deleting the cached attachments either.

So we're adding a rake task to delete these files once a day. This way
we can simplify the logic we were using to destroy cached attachments.

Note there's related a bug in documents: when editing a record (for
example, a proposal), if the title of the document changes, its hash
changes, and so it will be impossible to generate a link to that
document. Changing the way this hash is generated is not an option
because it would break links to existing files. We'll try to fix it when
moving to Active Storage.
2021-07-30 17:30:11 +02:00

105 lines
3.8 KiB
Ruby

class Document < ApplicationRecord
include DocumentsHelper
include DocumentablesHelper
has_attached_file :attachment, url: "/system/:class/:prefix/:style/:hash.:extension",
hash_data: ":class/:style/:custom_hash_data",
use_timestamp: false,
hash_secret: Rails.application.secrets.secret_key_base
attr_accessor :cached_attachment
belongs_to :user
belongs_to :documentable, polymorphic: true
# Disable paperclip security validation due to polymorphic configuration
# Paperclip do not allow to use Procs on valiations definition
do_not_validate_attachment_file_type :attachment
validate :attachment_presence
validate :validate_attachment_content_type, if: -> { attachment.present? }
validate :validate_attachment_size, if: -> { attachment.present? }
validates :title, presence: true
validates :user_id, presence: true
validates :documentable_id, presence: true, if: -> { persisted? }
validates :documentable_type, presence: true, if: -> { persisted? }
before_save :set_attachment_from_cached_attachment, if: -> { cached_attachment.present? }
scope :admin, -> { where(admin: true) }
def set_cached_attachment_from_attachment
self.cached_attachment = if Paperclip::Attachment.default_options[:storage] == :filesystem
attachment.path
else
attachment.url
end
end
def set_attachment_from_cached_attachment
self.attachment = if Paperclip::Attachment.default_options[:storage] == :filesystem
File.open(cached_attachment)
else
URI.parse(cached_attachment)
end
end
Paperclip.interpolates :prefix do |attachment, style|
attachment.instance.prefix(attachment, style)
end
Paperclip.interpolates :custom_hash_data do |attachment, _style|
attachment.instance.custom_hash_data(attachment)
end
def prefix(attachment, _style)
if !attachment.instance.persisted?
"cached_attachments/user/#{attachment.instance.user_id}"
else
":attachment/:id_partition"
end
end
def custom_hash_data(attachment)
original_filename = if !attachment.instance.persisted?
attachment.instance.attachment_file_name
else
attachment.instance.title
end
"#{attachment.instance.user_id}/#{original_filename}"
end
def humanized_content_type
attachment_content_type.split("/").last.upcase
end
private
def documentable_class
documentable_type.constantize if documentable_type.present?
end
def validate_attachment_size
if documentable_class.present? &&
attachment_file_size > documentable_class.max_file_size
errors.add(:attachment, I18n.t("documents.errors.messages.in_between",
min: "0 Bytes",
max: "#{max_file_size(documentable_class)} MB"))
end
end
def validate_attachment_content_type
if documentable_class &&
!accepted_content_types(documentable_class).include?(attachment_content_type)
accepted_content_types = documentable_humanized_accepted_content_types(documentable_class)
message = I18n.t("documents.errors.messages.wrong_content_type",
content_type: attachment_content_type,
accepted_content_types: accepted_content_types)
errors.add(:attachment, message)
end
end
def attachment_presence
if attachment.blank? && cached_attachment.blank?
errors.add(:attachment, I18n.t("errors.messages.blank"))
end
end
end