class Para::Cloneable::AttachmentsCloner
Constants
- ATTACHMENTS_RELATION_REGEX
Handle both one and many attachment relations
Attributes
clone[R]
dictionary[R]
original[R]
Public Class Methods
new(original, clone, dictionary)
click to toggle 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
clone!()
click to toggle 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
clone_attachment(name, original_attachment)
click to toggle 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
clone_attachment_if_needed(name, attachment)
click to toggle 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
find_cloned_attachment(attachment_target, original_blob)
click to toggle source
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.
# 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
store_cloned(source, clone)
click to toggle source
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
# 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
store_key_for(attachment)
click to toggle source
# File lib/para/cloneable/attachments_cloner.rb, line 101 def store_key_for(attachment) attachment.class.name.tableize.to_sym end