Papertrail и Carrierwave - PullRequest
       10

Papertrail и Carrierwave

8 голосов
/ 24 февраля 2012

У меня есть модель, которая использует и Carrierwave для хранения фотографий, и PaperTrail для управления версиями.

Я также настроил Carrierwave для хранения разных файлов при обновлении (потому что я хочу сделать версии фотографий)config.remove_previously_stored_files_after_update = false

Проблема в том, что PaperTrail пытается сохранить весь объект Ruby из фотографии (CarrierWave Uploader) вместо простой строки (это будет ее URL)

(версиятаблица, столбец объекта)

---
first_name: Foo
last_name: Bar
photo: !ruby/object:PhotoUploader
  model: !ruby/object:Bla
    attributes:
      id: 2
      first_name: Foo1
      segundo_nombre: 'Bar1'
      ........

Как исправить это, чтобы сохранить простую строку в версии для фотографий?

Ответы [ 6 ]

11 голосов
/ 25 февраля 2012

Вы можете переопределить item_before_change в вашей версионной модели, чтобы не вызывать аксессор загрузчика напрямую и вместо этого использовать write_attribute. В качестве альтернативы, поскольку вам может потребоваться сделать это для нескольких моделей, вы можете напрямую подключить метод к обезьянке, например:

module PaperTrail
  module Model
    module InstanceMethods
      private
        def item_before_change
          previous = self.dup
          # `dup` clears timestamps so we add them back.
          all_timestamp_attributes.each do |column|
            previous[column] = send(column) if respond_to?(column) && !send(column).nil?
          end
          previous.tap do |prev|
            prev.id = id
            changed_attributes.each do |attr, before|
              if defined?(CarrierWave::Uploader::Base) && before.is_a?(CarrierWave::Uploader::Base)
                prev.send(:write_attribute, attr, before.url && File.basename(before.url))
              else
                prev[attr] = before
              end
            end
          end
        end
    end
  end
end

Не уверен, что это лучшее решение, но, похоже, оно работает.

6 голосов
/ 25 февраля 2015

Добавление комментария @ beardedd в качестве ответа, потому что я думаю, что это лучший способ решить проблему.

Назовите столбцы базы данных как picture_filename, а затем в вашей модели смонтируйте загрузчик, используя:

class User < ActiveRecord::Base has_paper_trail mount_uploader :picture, PictureUploader, mount_on: :picture_filename end

Вы по-прежнему используете атрибут user.picture.url для доступа к вашей модели, но PaperTrail будет хранить ревизии в picture_filename.

3 голосов
/ 07 апреля 2015

Вот немного обновленная версия monkeypatch от @rabusmar, я использую ее для rails 4.2.0 и paper_trail 4.0.0.beta2, в /config/initializers/paper_trail.rb.

Второе переопределение метода требуется, если вы используете необязательный столбец object_changes для версий. Это работает немного странным образом для carrierwave + fog, если вы переопределите filename в загрузчике, старое значение будет из облака, а новое из локального имени файла, но в моем случае это нормально.

Также я не проверял, работает ли он правильно при восстановлении старой версии.

module PaperTrail
  module Model
    module InstanceMethods
      private

      # override to keep only basename for carrierwave attributes in object hash
      def item_before_change
        previous = self.dup
        # `dup` clears timestamps so we add them back.
        all_timestamp_attributes.each do |column|
          if self.class.column_names.include?(column.to_s) and not send("#{column}_was").nil?
            previous[column] = send("#{column}_was")
          end
        end
        enums = previous.respond_to?(:defined_enums) ? previous.defined_enums : {}
        previous.tap do |prev|
          prev.id = id # `dup` clears the `id` so we add that back
          changed_attributes.select { |k,v| self.class.column_names.include?(k) }.each do |attr, before|
            if defined?(CarrierWave::Uploader::Base) && before.is_a?(CarrierWave::Uploader::Base)
              prev.send(:write_attribute, attr, before.url && File.basename(before.url))
            else
              before = enums[attr][before] if enums[attr]
              prev[attr] = before
            end
          end
        end
      end

      # override to keep only basename for carrierwave attributes in object_changes hash
      def changes_for_paper_trail
        _changes = changes.delete_if { |k,v| !notably_changed.include?(k) }
        if PaperTrail.serialized_attributes?
          self.class.serialize_attribute_changes(_changes)
        end
        if defined?(CarrierWave::Uploader::Base)
          Hash[
              _changes.to_hash.map do |k, values|
                [k, values.map { |value| value.is_a?(CarrierWave::Uploader::Base) ? value.url && File.basename(value.url) : value }]
              end
          ]
        else
          _changes.to_hash
        end
      end

    end
  end
end
1 голос
/ 20 декабря 2016

Это то, что на самом деле работает для меня, поместите это в config / initializers / paper_trail / .rb

module PaperTrail
  module Reifier
    class << self
      def reify_attributes(model, version, attrs)
        enums = model.class.respond_to?(:defined_enums) ? model.class.defined_enums : {}
        AttributeSerializers::ObjectAttribute.new(model.class).deserialize(attrs)
        attrs.each do |k, v|

          is_enum_without_type_caster = ::ActiveRecord::VERSION::MAJOR < 5 && enums.key?(k)

          if model.send("#{k}").is_a?(CarrierWave::Uploader::Base)
            if v.present?
               model.send("remote_#{k}_url=", v["#{k}"][:url])
               model.send("#{k}").recreate_versions!
            else
               model.send("remove_#{k}!")
            end
          else
              if model.has_attribute?(k) && !is_enum_without_type_caster
                model[k.to_sym] = v
              elsif model.respond_to?("#{k}=")
                model.send("#{k}=", v)
              elsif version.logger
                version.logger.warn(
                  "Attribute #{k} does not exist on #{version.item_type} (Version id: #{version.id})."
                )
              end
            end
        end
      end
    end
  end
end

Это переопределяет метод reify для работы на S3 + heroku

Для загрузчиковчтобы сохранить старые файлы от обновленных или удаленных записей, сделайте это в загрузчике

configure do |config|
   config.remove_previously_stored_files_after_update = false
end
def remove!
   true
end

Затем создайте некоторую подпрограмму, чтобы время от времени очищать старые файлы, удачи

0 голосов
/ 11 февраля 2016

@ Sjors Provoost

Нам также необходимо переопределить pt_recordable_object метод в PaperTrail :: Model :: InstanceMethods module

  def pt_recordable_object
    attr = attributes_before_change
    object_attrs = object_attrs_for_paper_trail(attr)

    hash = Hash[
        object_attrs.to_hash.map do |k, value|
          [k, value.is_a?(CarrierWave::Uploader::Base) ? value.url && File.basename(value.url) : value ]
        end
    ]

    if self.class.paper_trail_version_class.object_col_is_json?
      hash
    else
      PaperTrail.serializer.dump(hash)
    end
  end
0 голосов
/ 12 апреля 2015

Я хочу добавить к предыдущим ответам следующее:

Может случиться, что вы загружаете разные файлы с одинаковым именем, и это может перезаписать ваш предыдущий файл, поэтому вы не сможете восстановить старый.

Вы можете использовать отметку времени в именах файлов или создавать случайные и уникальные имена файлов для всех версий файлов .

Обновление

Мне кажется, что это работает не во всех случаях, когда одному и тому же запросу присваивается более одного файла одному объекту.

Я использую это прямо сейчас:

def filename
  [@cache_id, original_filename].join('-') if original_filename.present?
end

Это похоже на работу, так как @cache_id генерируется для каждой загрузки снова (что не так, как кажется для идей, представленных в ссылках выше).

...