Evmorov
Evmorov

Reputation: 1219

Carrierwave gem. How to rename uploaded image versions after recreating them?

I have similar models as described in RailsCasts:

app/models/resident.rb:

class Resident < ActiveRecord::Base
  include PhotoConcern
end

app/models/employee.rb:

class Employee < ActiveRecord::Base
  include PhotoConcern
end

app/models/concerns/photo_concern.rb:

module PhotoConcern
  extend ActiveSupport::Concern

  included do
    mount_uploader :photo, PhotoUploader

    attr_accessor :photo_crop_x, :photo_crop_y, :photo_crop_w, :photo_crop_h

    after_save :crop_photo

    def crop_photo
      photo.recreate_versions! if photo_crop_x.present?
    end
  end
end

app/uploaders/photo_uploader.rb:

class PhotoUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick

  storage :file

  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  version :cropped do
    process :crop
  end

  version :thumb, from_version: :cropped do
    process resize_to_fill: [100, 100]
  end

  version :avatar, from_version: :cropped do
    process resize_to_fill: [200, 200]
  end

  def crop
    return if model.photo_crop_x.blank?

    resize_to_limit(500, nil)
    resize_to_fit(500, nil)

    manipulate! do |img|
      size = model.photo_crop_w << 'x' << model.photo_crop_h
      offset = '+' << model.photo_crop_x << '+' << model.photo_crop_y

      img.crop("#{size}#{offset}")
      img
    end
  end
end

app/views/employees/show.slim

= image_tag (@employee.photo.present? ? @employee.photo.url(:avatar) : "client_#{@employee.sex}.png"), class: 'img-circle img-responsive'

I want to rename version files after cropping so my users don't struggle with a cache. It's described in CarrierWave wiki how to rename files but also it's written "In order to save the newly generated filename you have to call save! on the model after recreate_versions!".

How can I rename version files? I can't call save! in my Employee's after_save again because there are more hooks that shouldn't be called twice. Also, PhotoConcern is included into another class.

Related wiki articles:

Upvotes: 13

Views: 1829

Answers (2)

konyak
konyak

Reputation: 11706

For those looking to change the uploaded filename after it was saved and you somehow renamed the file on disk. You could change the record directly in the database without callbacks and then reload the activerecord.

For example,

photo.update_column(:attachment_file_name, "new_name.jpg")
photo.reload

Upvotes: -1

fabOnReact
fabOnReact

Reputation: 5942

In order to save the newly generated filename you have to call save! on the model after recreate_versions!.

So I believe the answer to your doubts is included in the Carrierwave rubydocumentation

recreate_versions!(*versions) ⇒ Object

Recreate versions and reprocess them. This can be used to recreate versions if their parameters somehow have changed.

This method will store the *versions if they are not ommitted, otherwise the cached file will be stored.

# File 'lib/carrierwave/uploader/versions.rb', line 216

def recreate_versions!(*versions)
  # Some files could possibly not be stored on the local disk. This
  # doesn't play nicely with processing. Make sure that we're only
  # processing a cached file
  #
  # The call to store! will trigger the necessary callbacks to both
  # process this version and all sub-versions
  if versions.any?
    file = sanitized_file if !cached?
    # the file will be stored
    store_versions!(file, versions)
  else
    cache! if !cached?
    # If new_file is omitted, a previously cached file will be stored.
    store!
  end

What does store! do?

This is the rubydoc page about store!

store!(new_file = nil) ⇒ Object

Stores the file by passing it to this Uploader's storage engine. If new_file is omitted, a previously cached file will be stored

This method is included in your class PhotoUploader < CarrierWave::Uploader::Base and it uses with_callbacks to store your file using the callback :store. The callback triggers this method.

# File 'lib/carrierwave/uploader/store.rb', line 53

def store!(new_file=nil)
  cache!(new_file) if new_file && ((@cache_id != parent_cache_id) || @cache_id.nil?)
  if !cache_only and @file and @cache_id
    with_callbacks(:store, new_file) do
      new_file = storage.store!(@file)
      if delete_tmp_file_after_storage
        @file.delete unless move_to_store
        cache_storage.delete_dir!(cache_path(nil))
      end
      @file = new_file
      @cache_id = nil
    end
  end
end

What does store_versions! method do?

def store_versions!(new_file, versions=nil)
  if versions
    active = Hash[active_versions]
    versions.each { |v| active[v].try(:store!, new_file) } unless active.empty?
  else
    active_versions.each { |name, v| v.store!(new_file) }
  end
end

What are the Carrierwave callbacks and how to use them?

 after :store, :store_versions!

This question on SO explains and the wiki explain how callbacks work, by doing after :store, :my_method inside your version :low do block, you will execute my_method only on after :store callback (only for that version).

The :store callback corresponds to the execution of store!.

And what is the @filename attribute? How does recreate_versions! encode the filename?

@filename is defined with method filename in lib/carrierwave/uploader/store.rb

##
# Override this in your Uploader to change the filename.
#
# Be careful using record ids as filenames. If the filename is stored in the database
# the record id will be nil when the filename is set. Don't use record ids unless you
# understand this limitation.
#
# Do not use the version_name in the filename, as it will prevent versions from being
# loaded correctly.
#
# === Returns
#
# [String] a filename
#
def filename
  @filename
end

The guide from carrierwave suggest to use def filename to recreate unique filenames when recreating versions with recreate_version!.

This method does not save to the Database, to save to the database you need to calle save! on the appropriata Carrierwave callbacks, without breaking you Carrierwave GEM

I don't have the solution to this issue, but there is no documentation on this and we should start building it.

Upvotes: 6

Related Questions