cubesystems/releaf

View on GitHub
releaf-content/app/services/releaf/content/node/copy.rb

Summary

Maintainability
A
35 mins
Test Coverage
module Releaf
  module Content::Node
    class Copy
      include Releaf::Content::Node::Service
      attribute :parent_id, Integer, strict: false

      def call
        prevent_infinite_copy_loop
        begin
          new_node = nil
          node.class.transaction do
            new_node = make_copy
          end
        rescue ActiveRecord::RecordInvalid
          add_error_and_raise 'descendant invalid'
        else
          node.update_settings_timestamp
          new_node
        end
      end

      def prevent_infinite_copy_loop
        return if node.self_and_descendants.find_by_id(parent_id).blank?
        add_error_and_raise("source or descendant node can't be parent of new node")
      end

      def make_copy
        new_node = duplicate_under

        node.children.each do |child|
          self.class.new(node: child, parent_id: new_node.id).make_copy
        end

        new_node
      end

      def duplicate_under
        new_node = nil
        node.class.transaction do
          new_node = node.class.new
          new_node.assign_attributes_from(node)
          new_node.content_id = duplicate_content.try(:id)
          new_node.prevent_auto_update_settings_timestamp do
            Releaf::Content::Node::SaveUnderParent.call(node: new_node, parent_id: parent_id)
          end
        end

        new_node
      end

      def duplicate_content
        if node.content.present?
          new_content = duplicate_object(node.content)
          new_content.save!
          new_content
        else
          nil
        end
      end

      def duplicate_object object
        object.deep_clone include: duplicatable_associations(object.class) do |original, copy|
          supplement_object_duplication(original, copy)
        end
      end

      def supplement_object_duplication(original, copy)
        duplicate_dragonfly_attachments(original, copy)
      end

      def duplicatable_associations(owner_class)
        Releaf::ResourceBase.new(owner_class).associations.collect do |association|
          { association.name => duplicatable_associations(association.klass) }
        end
      end

      def duplicate_dragonfly_attachments(original, copy)
        attachment_keys = original.dragonfly_attachments.keys
        return unless attachment_keys.present?

        # during the dup() call the copy object has its dragonfly_attachments property duplicated from the original.
        # here it gets set to nil to force its reinitialization on next access
        copy.instance_variable_set(:@dragonfly_attachments, nil)

        # all uids must be cleared in a separate loop before accessing any of the attachments
        # (accessing any of the accessors regenerates the dragonfly_attachments collection and loads all uids)
        attachment_keys.each { |accessor| copy.send("#{accessor}_uid=", nil) }

        # once all uids have been reset, each attachment may be reassigned from the original.
        # reassignment forces dragonfly to internally treat the new attachment as a copy
        attachment_keys.each do |accessor|
          attachment = original.send(accessor)

          if attachment.present?
            begin
              attachment.path  # verify that the file exists
            rescue Dragonfly::Job::Fetch::NotFound
              attachment = nil
            end
          end

          copy.send("#{accessor}=", attachment)
        end
      end
    end
  end
end