Module: Shrine::Plugins::KithePersistedDerivatives::AttacherMethods

Defined in:
lib/shrine/plugins/kithe_persisted_derivatives.rb

Instance Method Summary collapse

Instance Method Details

#add_persisted_derivatives(local_files, **options) ⇒ Object

Like the shrine ‘add_derivatives` method, but also persists the derivatives (saves to db), in a realiably concurrency-safe way.

Generally can take any options that shrine ‘add_derivatives` can take, including custom `storage` or `metadata` arguments.

Like shrine add_derivatives, it will assume the files passed in are temporary, and delete them for you. If you want to disable this behavior:

attacher.add_persisted_derivatives({key: io}, delete: false)

In some cases the derivatives can’t be persisted because the underlying database has changed such that they would not be applicable. In those cases ‘false` will be return value, otherwise returns the new derivatives just as shrine `add_derivatives`

Because the concurrent-safe persistence method will save the associated model – and save without ActiveRecord validation – it is not safe to add_persisted_derivatives on a model with other unsaved changes. The method will by default refuse to do so, throwing a TypeError. If you’d like to force it, pass ‘allow_other_changes: true` as an argument.

Also takes care of deleting any replaced derivative files, that are no longer referenced by the model. Shrine by default does not do this: github.com/shrinerb/shrine/issues/468

All deletions are inline. In general this could be a fairly expensive operation, it can be wise to do it in a bg job.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/shrine/plugins/kithe_persisted_derivatives.rb', line 54

def add_persisted_derivatives(local_files, **options)
  other_changes_allowed = !!options.delete(:allow_other_changes)
  if record && !other_changes_allowed && record.changed?
    raise TypeError.new("Can't safely add_persisted_derivatives on model with unsaved changes. Pass `allow_other_changes: true` to force.")
  end

  existing_derivative_files = nil

  # upload to storage
  new_derivatives = upload_derivatives(local_files, **options)

  begin
    atomic_persist do |reloaded_attacher|
      # record so we can delete any replaced ones...
      existing_derivative_files = map_derivative(reloaded_attacher.derivatives).collect { |path, file| file }

      # make sure we don't override derivatives created in other jobs, by
      # first using the current up-to-date derivatives from db,
      # then merging our changes in on top.
      set_derivatives(reloaded_attacher.derivatives)
      merge_derivatives(new_derivatives)
    end
  rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound => e
    # underlying file has changed or model has been deleted, inappropriate
    # to add the derivatives, we can just silently drop them, but clean
    # up after ourselves.
    delete_derivatives(local_files) unless options[:delete] == false
    delete_derivatives(new_derivatives)

    return false
  rescue StandardError => e
    # unexpected error, clean up our files and re-raise
    delete_derivatives(local_files) unless options[:delete] == false
    delete_derivatives(new_derivatives)
    raise e
  end

  # Take care of deleting from storage any derivatives that were replaced.
  current_derivative_files = map_derivative(derivatives).collect { |path, file| file }
  replaced_files = existing_derivative_files - current_derivative_files
  delete_derivatives(replaced_files)

  new_derivatives
end

#create_persisted_derivatives(*args, storage: nil, allow_other_changes: false, **options) ⇒ Object

Like the shrine ‘create_derivatives` method, but persists the created derivatives to the database in a concurrency-safe way.

Can take all options that shrine ‘create_derivatives` can take, including custom processors, custom storage key, and arbitrary custom processor arguments.

asset.file_attacher.create_persisted_derivatives
asset.file_attacher.create_persisted_derivatives(storage: :custom_key)
asset.file_attacher.create_persisted_derivatives(:kithe_derivatives)
asset.file_attacher.create_persisted_derivatives(:kithe_derivatives, some_arg: "value")
asset.file_attacher.create_persisted_derivatives(:kithe_derivatives, alternate_source_file)

Also has an ‘allow_other_changes` argument, see #add_persisted_derivatives.



112
113
114
115
116
117
# File 'lib/shrine/plugins/kithe_persisted_derivatives.rb', line 112

def create_persisted_derivatives(*args, storage: nil, allow_other_changes: false, **options)
  return false unless file

  local_files = process_derivatives(*args, **options)
  add_persisted_derivatives(local_files, storage: storage, allow_other_changes: allow_other_changes)
end

#remove_persisted_derivatives(*paths, **options) ⇒ Object

Kind of like built-in Shrine #remove_derivatives, but also takes care of persisting AND deleting the removed derivative file from storage – all in concurrency-safe way, including not making sure to overwrite any unrelated derivatives someone else was adding.

Can take the same sorts of path arguments as Shrine derivative #remove_derivatives

asset.file_attacher.remove_persisted_derivatives(:small_thumb)
asset.file_attacher.remove_persisted_derivatives(:small_thumb, :large_thumb)
asset.file_attacher.remove_persisted_derivatives(:small_thumb, :large_thumb, allow_other_changes: true)


129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/shrine/plugins/kithe_persisted_derivatives.rb', line 129

def remove_persisted_derivatives(*paths, **options)
  return if paths.empty?

  other_changes_allowed = !!options.delete(:allow_other_changes)
  if record && !other_changes_allowed && record.changed?
    raise TypeError.new("Can't safely add_persisted_derivatives on model with unsaved changes. Pass `allow_other_changes: true` to force.")
  end

  removed_derivatives = nil
  atomic_persist do |reloaded_attacher|
    set_derivatives(reloaded_attacher.derivatives)
    removed_derivatives = remove_derivatives(*paths, delete: false)
  end

  if removed_derivatives
    map_derivative(removed_derivatives) do |_, derivative|
      derivative.delete if derivative
    end
  end

  removed_derivatives
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
  # original was already deleted or changed, the derivatives wer'e trying to delete.
  # It should be fine to do nothing, the process that deleted or changed
  # the model should already have deleted all these derivatives.
  # But we'll return false as a signel.
  return false
end