8

Mam model, który używa zarówno Carrierwave do przechowywania zdjęć i PaperTrail dla wersji.Papertrail and Carrierwave

ja również skonfigurowany Carrierwave do przechowywania plików zmierzających gdy aktualizacje (to dlatego, że chcę do wersji zdjęcia) z config.remove_previously_stored_files_after_update = false

Problem polega na tym, że PaperTrail spróbować zapisać cały Ruby obiekt ze zdjęcia (CarrierWave Uploader) zamiast po prostu ciąg (który będzie jego adres URL)

(tabela wersja, obiekt kolumna)

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

Jak mogę rozwiązać ten problem do przechowywania prosty ciąg w zdjęcie ver Sion?

Odpowiedz

11

Można zastąpić item_before_change w swoim modelu z wersjami, aby nie wywoływać bezpośrednio elementu wysyłającego i zamiast niego używać write_attribute. Alternatywnie, ponieważ możesz zrobić dla kilku modeli, można monkey-patch metody bezpośrednio, jak to:

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 

Nie jestem pewien, czy jest to najlepsze rozwiązanie, ale wydaje się działać.

+0

Nienawidzę cię! xD, działa – eveevans

+1

Szkoda, że ​​to nie jest część PaperTrail - dobra poprawka – John

+0

Próbowałem wstawić ten kod do pliku /config/initializers/papertrail.rb, ale wciąż dodaje on pełny obiekt do przesyłania. To jest z Rails 4.1. –

5

Dodanie komentarza @ beardedd jako odpowiedzi, ponieważ uważam, że jest to lepszy sposób na rozwiązanie problemu.

Nazwa kolumn baz coś picture_filename a następnie w modelu zamontować uploader używając:

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

nadal używać atrybutu user.picture.url dostępu do modelu, ale PaperTrail będzie przechowywać wersje pod picture_filename.

+0

Jest to bardzo przydatne, dziękuję! –

+0

Czy to naprawdę dobry sposób? Czy zmieni się tylko nazwa pliku podczas wymiany przesłanego pliku, czy też będzie dużo informacji meta takich jak szerokość, wysokość itp.? Zostałoby to utracone, gdy śledzona jest tylko nazwa pliku, a podczas przywracania starej wersji informacje byłyby nieprawidłowe. –

+0

Zrobiłem szybki test: Porównałem zrzuty dwóch obiektów z pełną siłą nośną (jedna z przesłanym plikiem 'avatar.jpg' i jedna z' nayeli.jpg'). Wydaje się, że tak naprawdę tylko znaczniki czasu i nazwy plików są różne, z wyjątkiem jednej linii (# 237), w której zapisana jest nazwa pliku wcześniej przesłanego pliku ('url.jpg', który był plikiem przed' avatar.jpg'). O ile widzę, twoje rozwiązanie powinno zasadniczo działać, a linia 237 byłaby niewłaściwa podczas przywracania wersji, ponieważ nie jest poprawnie śledzona. Zobacz wyniki tutaj: https://github.com/jmuheim/base/commit/c5f93b261efa02ff70265ef7397dfd77aecb644e –

3

Oto nieco zaktualizowana wersja monkeypatch z @rabusmar, używam go dla szyn 4.2.0 i paper_trail 4.0.0.beta2, w /config/initializers/paper_trail.rb.

Drugie zastąpienie metody jest wymagane w przypadku korzystania z opcjonalnej kolumny object_changes dla wersji. Działa to w nieco dziwny sposób dla carrierwave + fog, jeśli przesłonisz filename w uploaderze, stara wartość będzie pochodzić z chmury, a nowa z lokalnej, ale w moim przypadku jest w porządku.

Również nie sprawdziłem, czy działa poprawnie podczas przywracania starej wersji.

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

Dzięki, to fajne :) – eveevans

+0

Nie zapobiega to zastąpieniu poprzedniego pliku nowym plikiem o tej samej nazwie. –

+0

Przywracanie starej wersji działa dla mnie. Dziękuję Ci! –

0

chcę dodać do poprzednich odpowiedzi, co następuje:

To może się zdarzyć, że przesyłanie różnych plików o tej samej nazwie, a to może nadpisać poprzednią pliku, więc nie będzie mógł przywrócić starą.

Możesz use a timestamp in file names lub create random and unique filenames for all versioned files.

Aktualizacja

nie wydają się działać we wszystkich przypadkach brzegowych dla mnie, gdy przypisanie więcej niż jednego pliku do tego samego obiektu w ramach jednego wniosku żądania.

używam to teraz:

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

To wydaje się działać, jak @cache_id jest generowany dla każdego załadować ponownie (co nie jest, jak się wydaje do idei zawartych w powyższe linki).

0

@Sjors Provoost

Musimy również zastąpić pt_recordable_object metodę w PaperTrail :: model :: InstanceMethods modułu

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

to co faktycznie funkcje dla mnie, umieścić to na 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 

Ten przesłania metodę zreifikować pracować na S3 + Heroku

Dla uploaderów zachować stare pliki ze zaktualizowanych lub usuniętych rekordów zrobić w przesyłającej

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

Następnie uzupełnić pewną rutynę, aby usunąć stare pliki ze od czasu do czasu, powodzenia

+0

'model.send (" # {k} "). Restoreate_versions!' Nie wydaje się przywracać obiektu pliku dla mnie :( –

+1

Właściwie, myślę, że to działa.Trzeba tylko zadzwonić zapisać na modelu, gdzie byłem testowanie tego. Dzięki! –

Powiązane problemy