Normalizr - Как генерировать slug/id, связанный с родительским объектом

Как назначить id/slug, связанный с родителем объекта, с помощью normalizr?

Пример:

Ответ API для вызова пользователя:

{
  id: '12345',
  firstName: 'John',
  images: [
    {
      url: 'https://www.domain.com/image0',
      name: 'image0'
    },
    {
      url: 'https://www.domain.com/image1',
      name: 'image1'
    }
  ]
}

Я мог бы определить свои схемы следующим образом:

const image = new Schema('images');
const user = new Schema('users');

user.define({
  images: arrayOf(image)
})

Проблема заключается в том, что изображения не имеют свойства id, поэтому normalizr не сможет отличить их, если мы не предоставим свойство id. Конечно, мы могли бы сделать что-то вроде

const image = new Schema('images', { idAttribute: uuid.v4() });

и генерировать уникальные идентификаторы.

Предположим, мы получили обновление для пользователей, и имя изображения было обновлено. Поскольку мы генерируем уникальные идентификаторы в каждой нормировке, мы не можем идентифицировать и обновлять существующее изображение.

Мне нужен способ ссылки на родительский объект (пользователь) в объекте изображения (либо в его id/slug, как 12345-image0, 12345-image1, либо как отдельное свойство.

Каким будет оптимальный способ достичь этого?

Ответ 1

Проблема

Использование uuid так, как вы показываете, не будет работать.

const image = new Schema('images', { idAttribute: uuid.v4() });

uuid.v4() возвращает строку, допустимое значение для idAttribute, но теперь все ваши images будут иметь одинаковый uid. Не то, что вы хотите.

В идеале это сработало бы:

const image = new Schema('images', { idAttribute: () => uuid.v4() });

К сожалению, idAttribute будет вызываться несколько раз, как упомянуто в этом выпуске. Это нарушит любые отношения между сущностями. В вашем примере изображения будут иметь разные uids, чем пользовательский объект ссылается на них как.

Пример вывода:

users: {
  '12345': {
    id: '12345',
    firstName: 'John',
    images: [
      "cj20qq7zl00053j5ws9enz4w6",
      "cj20q44vj00053j5wauawlr4u"
    ],
  }
};
images: {
  cj20q44v100003j5wglj6c5h8: {
    url: 'https://www.example.org/image0',
    name: 'image0'
  },
  cj20q44vg00013j5whajs12ed: {
    url: 'https://www.example.org/image1',
    name: 'image1'
  }
};

Решение

Работа для этого заключается в том, чтобы мутировать входное значение в обратном вызове processStrategy, указывая ему атрибут uid.

const getUid = value => {
  if (!Object.prototype.hasOwnProperty.call(value, 'uid')) value.uid = uuid.v4();
  return {...value};
};

const image = new Schema('images', { processStrategy: getUid, idAttribute: 'uid'});

Теперь вы изменяете значение, так что отстой, но параметр idAttribute использует входное значение, а не обработанное значение.

В качестве альтернативы вы можете изменить значение в обратном вызове idAttribute, тогда вы не добавите поле uid к выходному значению.

sidenote: я бы рекомендовал использовать пакет cuid npm вместо uuid

Ответ 2

idAttribute может быть функцией, которая получает объект, родительский элемент и ключ, который ссылается на срез состояния:

const image = new Schema('images', {
  idAttribute: (entity, parent) => `${parent.id}-${entity.name}`
});