Papertrail和Carrierwave

时间:2012-02-23 23:44:29

标签: ruby-on-rails ruby-on-rails-3 carrierwave paper-trail-gem

我有一个使用两者的模型:Carrierwave用于商店照片,PaperTrail用于版本控制。

我还使用config.remove_previously_stored_files_after_update = false

在更新时(因为我想对照片进行版本更新)为商店不同的文件配置了Carrierwave

问题是PaperTrail尝试从照片(CarrierWave Uploader)存储整个Ruby对象而不是简单的字符串(这将是它的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 个答案:

答案 0 :(得分:11)

您可以在版本化模型上覆盖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

不确定它是否是最佳解决方案,但它似乎有效。

答案 1 :(得分:6)

添加@ 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下。

答案 2 :(得分:3)

以下是来自@rabusmar的monkeypatch的一个更新版本,我在/config/initializers/paper_trail.rb中将它用于rails 4.2.0和paper_trail 4.0.0.beta2。

如果对版本使用可选的object_changes列,则需要第二个方法覆盖。如果你在上传器中覆盖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

答案 3 :(得分:1)

这实际上对我有用,把它放在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

然后编制一些例程来不时清除旧文件,祝你好运

答案 4 :(得分:0)

我想在以前的答案中添加以下内容:

您可能会上传具有相同名称的不同文件,这可能会覆盖您之前的文件,因此您将无法恢复旧文件。

您可以use a timestamp in file namescreate random and unique filenames for all versioned files

<强>更新

在单个请求请求中将多个文件分配给同一个对象时,这似乎不适用于所有边缘情况。

我现在正在使用它:

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

这似乎有效,因为再次为每次上传生成@cache_id(对于上述链接中提供的想法,情况并非如此)。

答案 5 :(得分:0)

@Sjors Provoost

我们还需要覆盖 PaperTrail :: Model :: InstanceMethods 模块

中的 pt_recordable_object 方法
  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