class Para::Cloneable::AttachmentsCloner
Constants
- ATTACHMENTS_RELATION_REGEX
-
Handle both one and many attachment relations
Attributes
Public Class Methods
Source
# File lib/para/cloneable/attachments_cloner.rb, line 9 def initialize(original, clone, dictionary) @original = original @clone = clone @dictionary = dictionary end
Public Instance Methods
Source
# File lib/para/cloneable/attachments_cloner.rb, line 15 def clone! return unless defined?(ActiveStorage) attachment_reflections = original.class.reflections.select do |name, reflection| name.to_s.match(ATTACHMENTS_RELATION_REGEX) && reflection.options[:class_name] == 'ActiveStorage::Attachment' end attachment_reflections.each do |name, reflection| association_target = original.send(name) next unless association_target if reflection.collection? association_target.each do |attachment| clone_attachment_if_needed(name, attachment) end else clone_attachment_if_needed(name, association_target) end end end
Source
# File lib/para/cloneable/attachments_cloner.rb, line 48 def clone_attachment(name, original_attachment) association_name = name.gsub(ATTACHMENTS_RELATION_REGEX, '') original_blob = original_attachment.blob # Handle missing file in storage service by bypassing the attachment cloning return unless ActiveStorage::Blob.service.exist?(original_blob&.key) Para::ActiveStorageDownloader.new(original_attachment).download_blob_to_tempfile do |tempfile| attachment_target = clone.send(association_name) cloned_blob = ActiveStorage::Blob.create_and_upload!( io: tempfile, filename: original_blob.filename, content_type: original_blob.content_type ) attachment_target.attach(cloned_blob) cloned_attachment = find_cloned_attachment(attachment_target, original_blob) # Store the cloned attachment and blob into the deep_cloneable dictionary used # by the `deep_clone` method to ensure that, if needed during the cloning # operation, they won't be cloned once more and are accessible for processing store_cloned(original_attachment, cloned_attachment) store_cloned(original_blob, cloned_blob) end end
Source
# File lib/para/cloneable/attachments_cloner.rb, line 37 def clone_attachment_if_needed(name, attachment) return if attachment.nil? store_key = store_key_for(attachment) # If the attachment has already been cloned, we don't need to clone it again return if dictionary[store_key]&.key?(attachment) clone_attachment(name, attachment) end
Source
# File lib/para/cloneable/attachments_cloner.rb, line 79 def find_cloned_attachment(attachment_target, original_blob) attachments = if attachment_target.attachments.any? attachment_target.attachments else [attachment_target.attachment] end attachment = attachments.find do |att| att.blob.checksum == original_blob.checksum end end
Seemlessly handle one and many attachment relations return values and fetch the attachment that we just cloned by comparing blobs checksum, as depending which ActiveStorage version we’re on (Rails 5.2 or 6), the ‘#attach` method doesn’t always return the same, so for now we still handle the Rails 5.2 case.
Source
# File lib/para/cloneable/attachments_cloner.rb, line 94 def store_cloned(source, clone) store_key = store_key_for(source) dictionary[store_key] ||= {} dictionary[store_key][source] = clone end
This stores the source and clone resources into the deep_clone dictionary, which simulates what the deep_cloneable gem does when it clones a resource
Source
# File lib/para/cloneable/attachments_cloner.rb, line 101 def store_key_for(attachment) attachment.class.name.tableize.to_sym end