Papertrail和Carrierwave

我有一个使用两者的模型: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' ........ 

如何修复此问题以在照片版本中存储简单的字符串?

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

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

添加@ 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下存储修订。

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

如果对版本使用可选的object_changes列,则需要第二个方法覆盖。 如果你覆盖上传器中的filename ,那么它对于carrierwave +雾有点奇怪的方式,旧的值将来自云,而新的值来自本地文件名,但在我的情况下它没关系。

此外,我还没有检查恢复旧版本时它是否正常工作。

 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 

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

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

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

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

您可以在文件名中使用时间戳,也可以为所有版本化文件创建随机和唯一的文件名 。

更新

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

我现在正在使用它:

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

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

@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