Skocz do zawartości

Tworzenie i edycja zagnieżdżonych zasobów (REST-podobnych)


Konrad-GM

Rekomendowane odpowiedzi

Cześć, mam pewien problem z tworzeniem a zwłaszcza edycją zagnieżdżonych zasobów w usłudze podobnej do RESTa. Otóż tworzę pewien panel z podstawowymi funkcjami CRUD, w którym mam zagnieżdżone zasoby, które to wyglądają mniej-więcej tak:

 

Ankieta <1-N> Pytanie <1-N> Odpowiedź

Gdzie <1-N> to relacja 1 do wielu.

 

I mój problem polega na tym, że tworzę formularz:

[Tytuł]

[Opis]
[Pytanie[]]

  - [Odpowiedź[]]
  - [Odpowiedź[]]
  - [Odpowiedź[]]

[Pytanie[]]
  - [Odpowiedź[]]
  - [Odpowiedź[]]

itd.

 

I przy tworzeniu to jest ok, wysyłam najpierw Ankietę, dostaję ID zasobu, tworzę kolejne zasoby - pytanie i dostaję kolejne ID zasobów a na koniec tworzę odpowiedzi, następne pytanie itd. a wygląda to tak:

(ANKIETA => SERWER)
[SERWER => ANKIETA => ZAPISZ DO ZMIENNEJ]
(PYTANIE => SERWER)
[SERWER => PYTANIE => ZAPISZ DO ZMIENNEJ]
(ODPOWIEDŻ => SERWER)
[SERWER => ODPOWIEDŹ => ZAPISZ DO ZMIENNEJ]
(ODPOWIEDŻ => SERWER)
[SERWER => ODPOWIEDŹ => ZAPISZ DO ZMIENNEJ]
(ODPOWIEDŻ => SERWER)
[SERWER => ODPOWIEDŹ => ZAPISZ DO ZMIENNEJ]
(PYTANIE => SERWER)
[SERWER => PYTANIE => ZAPISZ DO ZMIENNEJ]
(ODPOWIEDŻ => SERWER)
[SERWER => ODPOWIEDŹ => ZAPISZ DO ZMIENNEJ]
(ODPOWIEDŻ => SERWER)
[SERWER => ODPOWIEDŹ => ZAPISZ DO ZMIENNEJ]

Gdzie (...) to request, a [...] to response.

 

Teraz mam problem przy edytowaniu takich zasobów, otóż dodam kilka pytań, odpowiedzi, niektóre usunę i muszę potem przeszukać starą listę i nową, tworząc tak jakby `diff` zmian, np.:
 

{action: "select", type: "survey", from: {...MODEL ANKIETY}}
{action: "remove", type: "question", from: {...MODEL PYTANIA}}
{action: "remove", type: "question", from: {...MODEL PYTANIA}}
{action: "add", type: "question", from: null, to: {...NOWY MODEL PYTANIA}}
{action: "update", type: "question", from: {...MODEL PYTANIA}, to: {...NOWY MODEL PYTANIA}}
{action: "select", type: "question", from: {...MODEL PYTANIA}}
{action: "add", type: "answer", from: null, to: {...NOWY MODEL ODPOWIEDZI}}

Tylko, ze to wygląda mega skomplikowanie i ciężko potem wprowadzać modyfikacje czy nawet debuggować w razie problemów. Czy ktoś może pomóc mi z rozwiązaniem takiego problemu z designem? :P Może z doświadczenia ktoś zna rozwiązanie, albo po prostu ma pomysł jak z takim czymś sobie poradzić.

 

Dzięki.

Odnośnik do komentarza
Udostępnij na innych stronach

  • Administratorzy

Jak zmienisz liczbę pytań to diff będzie niemal niemozliwy.

Jedyna opcja jaka mi przychodzi to dawać losowy numer (GUID?) Każdemu pytaniu to potem odnajdziesz starą wersję tego samego po tym nawet jak się kolejność zmieni. Zapisuj też ich pozycje to będzie widać które były gdzie przesuniete - ale idealnego diffa nie uzyskasz. Nawet w gicie jak przedstawisz tekst 5 linijek dalej to on je widzi jako usunięte i nowe a nie różnice.

Odnośnik do komentarza
Udostępnij na innych stronach

Hej @gnysek dzięki za odpowiedź. Problem z rozpoznawaniem zasobu po GUID rozwiązuje mi posiadane przez każdy zasób ID wygenerowany AUTO_INCREMENT-em już po stronie serwera, więc chyba zastosuję się do Twojej propozycji i będę trackował zmiany po ID tych zasobów zamiast pozycji jak w `diff`. W zasadzie wtedy nowe elementy mogę też identyfikować po tym, że ID mają ustawione na 0. Kolejność zasobów też mam w bazie oznaczone polem 'order' więc w jakiej kolejności będę aktualizował same dane to chyba też bez znaczenia, bo nie muszę znać kolejności zasobów na serwerze a jedynie ustawiać pole `order`. Tylko tutaj stworzenie jakiejś metody generic diff chyba odpada, bo nie każdy model danych może mieć pole o nazwie `id`. może jakaś lambda/arrow function jako parametr by ten problem rozwiązała.

 

Generalnie rozbiłem też trochę swój problem na mniejsze elementy, bo sprawdzam `diff`em tylko Pytania i tworzę RxJS-em obserwatory, do każdego zasobu Pytanie dołączam drugi `diff` ale już dla modyfikacji zasobów Odpowiedzi i łączę je do tego samego strumienia obserwatora RxJS co zasób Pytanie. Stąd mi też będzie trzymać kolejność wykonywanych requestów.

 

Tylko, że problem z tym jest taki, że w razie jakby w połowie przesyłania danych coś się nie udało wysłać, np. serwer odrzuci któryś request POST, PATCH, czy DELETE, to wtedy przerwie mi zmiany w połowie aktualizacji. Będę musiał to pewnie rozwiązać jeszcze implementując jakiś mechanizm obsługi błędów który wznawiałby przerwane requesty :P

Odnośnik do komentarza
Udostępnij na innych stronach

  • Administratorzy

Auto increment i tak można zostawić, zawsze to łatwiej intem potem pobierać rekord.

Co do przerwaia requestu - musisz aktualizować jednym requestem w takim razie. Przesyłać całą tablicę. Albo jakiś JS i dla kazdego elementu wywali "błąd" gdy się nie zapisze, żeby user ponowił zapis (pętla nie ma sensu, bo jak net padnie to będzie napierdzielać co sekundę :P).

Odnośnik do komentarza
Udostępnij na innych stronach

Racja, nie mam możliwości żeby w backendzie coś zmienić teraz, dlatego pozostanę przy tej opcji z tworzeniem sekwencji requestów i dodam jakiś modal w razie problemów. Ale chyba tak zrobię jak proponujesz, dodam przycisk np. "Renew save" w modalu "Failed to save survey" :P

 

Ok, stworzyłem w TS generic "diff" modeli. kompletnie pomija mi pozycje elementów w tablicy, ale chyba na moje potrzeby będzie działać ok.

export enum DiffResultAction {
  Create = 'create',
  Update = 'update',
  Delete = 'delete'
}

export interface DiffResult<T> {
  action: DiffResultAction;
  from: T;
  to: T;
}

export function diff<T>(from: T[], to: T[], hasSameId: (from: T, to: T) => boolean): DiffResult<T>[] {
  return to.map(dest => diffElement(dest, from, hasSameId)).concat(diffReduce(from, to, hasSameId));
}

function diffElement<T>(dest: T, sourceArray: T[], hasSameId: (from: T, to: T) => boolean): DiffResult<T> {
  const foundElement = sourceArray.find(element => hasSameId(element, dest));

  if (foundElement) {
    return { action: DiffResultAction.Update, from: foundElement, to: dest };
  } else {
    return { action: DiffResultAction.Create, from: null, to: dest };
  }
}

function diffReduce<T>(from: T[], to: T[], hasSameId: (from: T, to: T) => boolean): DiffResult<T>[] {
  return from.filter(source => !to.some(dest => hasSameId(source, dest))).map<DiffResult<T>>(
    removedElement => ({ action: DiffResultAction.Delete, from: removedElement, to: null })
  );
}

 

Odnośnik do komentarza
Udostępnij na innych stronach

Jeśli chcesz dodać odpowiedź, zaloguj się lub zarejestruj nowe konto

Jedynie zarejestrowani użytkownicy mogą komentować zawartość tej strony.

Zarejestruj nowe konto

Załóż nowe konto. To bardzo proste!

Zarejestruj się

Zaloguj się

Posiadasz już konto? Zaloguj się poniżej.

Zaloguj się
  • Ostatnio przeglądający   0 użytkowników

    • Brak zarejestrowanych użytkowników przeglądających tę stronę.
×
×
  • Dodaj nową pozycję...