Skip to content

When recored is duped and file in duped record is changed, then original file is deleted #580

@legendetm

Description

@legendetm

Brief Description

We have cases in code where we dup the existing record and then will update the some of the attributes on duped record. Unfortunately when shrine file is changed in the duped record, then original record's image is deleted from the store.

In our case, we also want to make new copy from existing files, that where not changed. So if a record is deleted, the images of all other records as still present. Previously we used :copy plugin what was removed.

Expected behavior

I expect the original record file to still be present after I have changed the file in duped record.

Actual behavior

When changing the file in duped record, then original record file is deleted.

Simplest self-contained example code to demonstrate issue

require "active_record"
require "shrine"
require "shrine/storage/memory"
require "sqlite3"
require "down"

Shrine.storages = {
  cache: Shrine::Storage::Memory.new,
  store: Shrine::Storage::Memory.new,
}

Shrine.plugin :activerecord

class DocumentUploader < Shrine
  plugin :determine_mime_type
end

ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.connection.create_table(:documents) { |t| t.text :image_data }

class Document < ActiveRecord::Base
  include DocumentUploader[:image]
end

document = Document.create(image: Down.download("https://picsum.photos/10"))
document2 = document.dup

document2.update!(image: Down.download("https://picsum.photos/10"))
puts document.image.exists? #=> false
puts document2.image.exists? #=> true

Current workaround

require "active_record"
require "shrine"
require "shrine/storage/memory"
require "sqlite3"
require "down"

Shrine.storages = {
  cache: Shrine::Storage::Memory.new,
  store: Shrine::Storage::Memory.new,
}

Shrine.plugin :activerecord

class DocumentUploader < Shrine
  plugin :determine_mime_type

  class Attacher < Shrine::Attacher
    def duped!
      @duped = dup if file
    end

    def duped?
      @duped.present? || false
    end

    def copy_duped_file?
      duped? && @duped.file == file
    end

    def changed?
      super || copy_duped_file?
    end

    def save
      # Copy the image when duped record is saved, but the image remained the same
      attach(file, storage: file.storage_key) if copy_duped_file?
      super
    end

    def destroy_previous
      # Do not delete the previous file, when it was duped
      return if duped? && @duped.file == @previous&.file

      super
    end
  end
end

ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.connection.create_table(:documents) { |t| t.text :image_data }

class Document < ActiveRecord::Base
  include DocumentUploader[:image]

  def initialize_dup(other)
    super
    image_attacher.duped!
  end
end

document = Document.create(image: Down.download("https://picsum.photos/10"))
document2 = document.dup

document2.update!(image: Down.download("https://picsum.photos/10"))
puts document.image.exists? #=> true
puts document2.image.exists? #=> true

document3 = document.dup
puts document.image == document3.image #=> true
document3.save! # image is copied when duped record is saved
puts document.image == document3.image #=> false

System configuration

Ruby version: 3.0.4

Shrine version: 3.4.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions