我有一个使用两者的模型:Carrierwave用于商店照片,PaperTrail
用于版本控制。
我还使用config.remove_previously_stored_files_after_update = false
问题是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'
........
如何修复此问题以在照片版本中存储简单的字符串?
答案 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 names或create 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