Tag: Rails

Archiwizacja modelu czyli moje własne Acts as Archivable

Wczoraj pisząc projekt, wpadłem na pomysł wersjonowania tekstów. Postanowiłem że każda zmiana zawartości musi być równoznaczna ze stworzeniem kopii zapasowej. Funkcjonalność tą postanowiłem wydzielić do pluginu, z racji tego że może przydać się w innych modelach.

Jak to zrealizowałem? Postanowiłem skorzystać z pluginu acts_as_tree. Implikuje to niestety to, że jeśli chcemy skorzystać ze struktury drzewa i backupu to niestety musimy poszukać innego pluginu ;)

Struktura całego mechanizmu ma następującą postać:

Tekst (czy też inny obiekt)

  1. Kopia 1
  2. Kopia 2
  3. Kopia 3
  4. etc

Kopie wykonywane są w momencie aktualizacji modelu. Wykorzystany jest do tego callback before_update.

Interfejs pluginu składa się z trzech metod instancyjnych i jednej klasowej. Metody instancyjne to:

  • backup! - Robi kopie samego siebie do archiwum
  • restore!(id) - przywraca kopię o wybranym id lub też jesli wywołane na kopii to przywraca kopię z której wywołaliśmy
  • cleanup! - usuwa wszystkie kopie poza najnowszą

Metoda klasowa:

  • cleanup! - usuwa wszystkie kopie ze wszystkich elementów, pozostawiając tylko po 1 na element

Problemem na jaki natrafiłem, przy tworzeniu tego pluginu, był Paperclip. Wywołując metodę clone lub po prostu:

target.update_attributes(source.attributes)

Obiekt paperclipa nie było kopiowany. Aby to obejść zastosowałem inne rozwiązanie które wykrywa czy dany obiekt posiada w bazie elementy charakterystyczne dla paperclipa, czyli:

  1. _content_type
  2. _file_size
  3. _file_name

Na podstawie przedrostka rozpoznaje jak te obiekty się nazywają, następnie wywołując dla nich metodę przypisania:

        paperclip_obj.each { |obj|
          sym = "#{obj}=".to_sym
          target.send(sym, self.send(obj.to_s))
        }

Dzięki czemu, kopiowane są wszystkie atrybuty oraz obiekty paperclipa (niezależnie od ich ilości). Dzięki czemu archiwizujemy nie tylko sam przykładowo tekst, ale i powiedzmy miniaturkę. Oczywiście ma to swoje minusy - ilość miejsca zajmowanego przez kopie rośnie bardzo szybko. Na szczęście jest wyżej wspomniana metoda cleanup!

A oto jak zastosować plugin:
Przykładowa klasa:

class CoolClass < ActiveRecord::Base
    acts_as_tree
    acts_as_archivable
end

Następnie zrobienie 10 kopii w odstępach 1sekundowych:

    model = CoolClass.create
    10.times {
      model.content += "a"
      sleep(1)
      model.save
    }

I już mamy nasz model + kopię zapasową każdej wersji.

Teraz przywróćmy powiedzmy drugą wersję (czyli tą "prawie" najdawniejszą):

model.children.second.restore!
# Musimy przeładować model bo zmieniają się jego parametry
model.reload

Z racji tego że zmieniają się parametry obiektu w bazie, musieliśmy wykonać przeładowanie.

Przywrócenie można zrobić także na drugi sposób, wywołując metodę restore! na obiekcie w stosunku do którego chcemy wykonać przywrócenie:

model.restore(model.children.second.id)

Jeśli tak wywołamy przywracanie, nie musimy wywoływać metody reload, ponieważ wywoływana jest automatycznie z poziomu metody restore!

Aby wyczyścić kopie zapasowe, pozostawiając tylko najnowszą, wykonamy:

model.cleanup!

Lub też jeśli chcemy posprzątać wszystkie modele:

CoolClass.cleanup!

Tyle :) aby wyświetlić kopie zapasowe, wystarczy przeiterować dzieci w taki sposób:

model.children.each {
# Jakiś kod :)
}

Plugin do pobrania tutaj: acts_as_archivable

Acts As Random – plugin do generowania losowych ID zamiast autoincrement

O tym dlaczego ważne jest generowanie losowych ID, pisałem już w innym poście. Tutaj opiszę jak działa najważniejsza część mojego pluginu. Do pobrania jest tutaj.

Jak go dodać do klasy:

class CoolClass < ActiveRecord::Base
    acts_as_random
end

I od tego momentu generuje nam losowe wartości naszych id. W każdej klasie dziedziczącej po ActiveRecord, dodając acts_as_random, otrzymamy pseudolosowe id.

A działa to tak:

      def before_create
        range = 2147483647
        id = rand(range)
        while self.class.exists?(id) do
          id = rand(range)
        end
        self.id = id
      end

Malutko prawda? :)
Najpierw mamy deklarację zakresu INT(11) w (nawiasem mówiąc, taka sama jest w Postgresie. Wynika to z rozmiaru INTa a nie specyfikacji danego systemu bazodanowego). Następnie losujemy id z naszego zakresu, czyli od 0 do range. Sprawdzamy czy taki wpis istnieje i jeśli tak, to dopóki wpisy o ID takim jak losujemy istnieją, to musimy losować inne ID. Kiedy już mamy ID to po prostu je przydzielamy do naszego obiektu.

Proste, w miarę czytelne, niewielkie, ale swoje robi. Plusem tego pluginu jest to, że można go podpinać pod już istniejące projekty mające autoincrement. Stare elementy będą inkrementowane, ale nowe już nie.

Copyright © 2025 Closer to Code

Theme by Anders NorenUp ↑