8

J'ai un modèle qui utilise à la fois: Carrierwave pour stocker des photos, et PaperTrail pour le versionnage.Papertrail et Carrierwave

J'ai aussi configuré Carrierwave pour les fichiers diférents de magasin lorsque les mises à jour (C'est parce que je veux à la version des photos) avec config.remove_previously_stored_files_after_update = false

Le problème est que PaperTrail essayer de stocker l'ensemble d'objets Ruby de la photo (CarrierWave Uploader) au lieu de simplement une chaîne (qui correspondrait à son URL)

(tableau version, objet de colonne)

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

Comment puis-je résoudre ce problème pour stocker une chaîne simple dans la photo ver sion?

Répondre

11

Vous pouvez remplacer item_before_change sur votre modèle versionné de sorte que vous n'appelez pas l'accesseur de téléchargement directement et que vous utilisiez write_attribute à la place. Sinon, puisque vous pouvez le faire pour plusieurs modèles, vous pouvez singe-patcher la méthode directement, comme ceci:

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 

Je ne sais pas si elle est la meilleure solution, mais il semble fonctionner.

+0

Vous détestez! xD, ça marche – eveevans

+1

Dommage que ça ne fasse pas partie de PaperTrail - bonne correction – John

+0

J'ai essayé de mettre ce code dans /config/initializers/papertrail.rb, mais il ajoute toujours l'objet upload complet. C'est avec Rails 4.1. –

5

Ajout du commentaire de @ beardedd en guise de réponse car je pense que c'est une meilleure façon de gérer le problème.

Nom de votre base de données colonnes quelque chose comme picture_filename puis dans votre modèle monter le Uploader en utilisant:

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

Vous utilisez encore l'attribut user.picture.url pour accéder à votre modèle, mais Papertrail va stocker les révisions sous picture_filename.

+0

Ceci est très utile, merci! –

+0

Est-ce vraiment un bon moyen? Est-ce que le nom du fichier ne sera modifié que lors du remplacement d'un fichier téléchargé, ou y aura-t-il aussi beaucoup de méta-informations comme la largeur de l'image, la hauteur, etc.? Ce serait perdu lors du suivi du nom de fichier, et lors de la restauration d'une ancienne version, les infos seraient incorrectes. –

+0

J'ai fait un test rapide: j'ai comparé les dumps de deux objets fullwavewave (un avec un fichier uploadé avatar.jpg et un avec 'nayeli.jpg'). Il semble que seuls les timestamps et les noms de fichiers soient réellement différents, à l'exception d'une ligne (# 237) où le nom du fichier précédemment téléchargé est stocké ('url.jpg', qui était le fichier avant' avatar.jpg'). Pour autant que je puisse voir, votre solution devrait fonctionner, alors que la ligne 237 serait erronée lors de la restauration des révisions car elle n'est pas suivie correctement. Voir les résultats ici: https://github.com/jmuheim/base/commit/c5f93b261efa02ff70265ef7397dfd77aecb644e –

3

Voici une version de monkeypatch de @rabusmar, mise à jour, je l'utilise pour les rails 4.2.0 et paper_trail 4.0.0.beta2, en /config/initializers/paper_trail.rb.

Le remplacement de la deuxième méthode est obligatoire si vous utilisez la colonne object_changes facultative pour les versions. Cela fonctionne d'une manière un peu étrange pour carrierwave + fog si vous remplacez filename dans l'uploader, l'ancienne valeur sera du cloud et une nouvelle du nom de fichier local, mais dans mon cas c'est ok.

Aussi je n'ai pas vérifié si cela fonctionne correctement lorsque vous restaurez l'ancienne version.

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 
+0

Merci, c'est cool :) – eveevans

+0

Cela n'empêche pas un fichier précédent d'être remplacé par un nouveau fichier du même nom. –

+0

Restaurer une ancienne version fonctionne pour moi. Je vous remercie! –

0

Je veux ajouter aux réponses précédentes suivantes:

Il peut arriver que vous téléchargez des fichiers différents avec le même nom, et cela peut remplacer votre fichier précédent, de sorte que vous ne serez pas en mesure restaurer l'ancien.

Vous pouvez use a timestamp in file names ou create random and unique filenames for all versioned files.

Mise à jour

Cela ne semble pas fonctionner dans tous les cas de garde pour moi, lors de l'attribution plus d'un seul fichier au même objet dans une demande de requête unique.

J'utilise ce moment:

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

Cela semble fonctionner, comme @cache_id est généré pour chaque retélécharger (ce qui est le cas comme il semble les idées fournies dans les liens ci-dessus).

0

@Sjors Provoost

Nous avons également besoin de passer outre méthode pt_recordable_object dans 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 
1

C'est ce qui fonctionne réellement pour moi, mettre ce sur 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 

Cette opération remplace la méthode reify pour travailler sur S3 + heroku

Pour uploaders de garder les anciens fichiers de dossiers mis à jour ou supprimés faire dans le Uploader

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

Ensuite, faire un peu de routine pour effacer les anciens fichiers de temps en temps, bonne chance

+0

'model.send (" # {k} "). Recreate_versions!' Ne semble pas réintégrer l'objet fichier pour moi :( –

+1

En fait, je pense que cela fonctionne .J'avais juste besoin d'appeler enregistrer sur le modèle où j'étais testant ceci Merci. –