Продолжая последний пост, я собираюсь написать о заполнении поддокумента в GraphQL.
Контекст
Итак, в прошлый раз я построил две схемы, а затем вложил одну из них в массив другой. Чтобы дать вам немного больше о контексте этого программного обеспечения, это программное обеспечение POS, в котором есть несколько продуктов, и каждый платеж имеет набор продуктов, оплаченных клиентом.
// models/Transaction.js
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
const itemSchema = new Schema({
name: { type: String },
amount: { type: Number },
});
const transactionSchema = new Schema(
{
price: { type: Number },
method: { type: String, default: 'VISA' },
cardNumber: { type: String },
paidTime: { type: Date, default: new Date() },
items: [itemSchema], // we will change this part
}
);
export const Transaction = mongoose.model('Transaction', transactionSchema);
На этот раз я бы построил совершенно новую модель и ссылку на нее, которая будет действовать как внешний ключ.
Обзор
Что я собираюсь сделать, так это сохранить идентификаторы в массиве, а не хранить весь поддокумент. От чего-то вроде левого к правому.
- Создайте новую модель, Продукт
// models/Product.js
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
const productSchema = new Schema({ name: { type: String }, price: { type: Number }, category: { type: String }, });
export const Product = mongoose.model('Product', productSchema);
- Ссылка на дочерний элемент из родительского документа.
// models/Transaction.js
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
const transactionSchema = new Schema( { price: { type: Number }, method: { type: String, default: 'VISA' }, cardNumber: { type: String }, paidTime: { type: Date, default: new Date() }, // it used to be items: [itemSchema], items: [{ type: Schema.Types.ObjectId, ref: 'Product' }], } );
export const Transaction = mongoose.model('Transaction', transactionSchema);
Поскольку нам больше не нужна itemSchema, я удалил их и сослался на модель Product по ее идентификатору, указав тип свойства, которое я собираюсь хранить в элементах, и его имя модели в качестве ссылки.
{type: id, ref: model name}
TypeDefs
Если мы изменили нашу модель с помощью mongoose, нам также нужно сообщить typeDefs об изменениях, чтобы это работало в graphQL.
// typeDefs.js // Transaction type and input type Transaction { id: ID! price: Float! method: String! cardNumber: String! paidTime: Date! items: [Product] } input TransactionInput { price: Float method: String cardNumber: String items: [ID] } // Product type input ProductInput { name: String price: Float category: String } type Product { id: ID! name: String price: Float category: String } type Mutation { createTransaction(TransactionInput: TransactionInput!): Transaction deleteTransaction(transactionID: ID!): Transaction // resolver functions for product and item, will explain soon. createItem(productId: String, transactionId: ID): Transaction deleteItem(productId: String, transactionId: ID): Transaction createProduct(ProductInput: ProductInput): Product deleteProduct(productId: ID!): Product }
Модель продукта
Модель продукта будет иметь определенные продукты, которые у нас есть. Представьте, что вы работаете в магазине сэндвичей, и у него есть два продукта, и всякий раз, когда он заказывается, идентификатор элемента будет вставлен в массив в транзакции. Итак, что я собираюсь сделать, так это создать несколько продуктов, которые будут использоваться в поле элементов транзакции.
Продукт Resolver
Мы собираемся создать преобразователь продуктов, и не забудьте подключить преобразователь к вашему серверу apollo.
// resolvers/product.js import { Product } from '../models/Product'; export const productResolver = { Query: { query for product }, Mutation: { createProduct: async (_, { ProductInput }) => { try { const newProduct = await Product.create(ProductInput); await newProduct.save() return newProduct } catch (error) { console.error(error.message) } }, deleteProduct: async (_, { productId }) => { const deleteProduct = await Product.findByIdAndDelete(productId) if (deleteProduct) { console.log(`Product ${productId} deleted.`) } else { console.log(`Product ${productId} doesn't exist`) } }
Я сделал две мутации продукта, которые эквивалентны POST и DELETE.
В createProduct новый продукт будет создан с передачей ввода продукта и сохранен.
В deleteProduct он найдет продукт для удаления с его идентификатором, а затем будет удален.
Однако мы еще не закончили. Если вы попытаетесь получить транзакцию, в поле элементов останется пустой массив.
Почему не работает?
Причина в том, что транзакция не знает, что делать с полем items. В typeDefs мы объявили, что поле items в Transaction будет иметь объект Product, но только предоставили преобразователю с идентификатором!
Тип операции
Чтобы решить проблему с пустым массивом поля элемента, нам нужно сообщить транзакции, что они с ним делают, точно указав поле элементов!
// resolver/transaction.js export const transactionResolver = { Transaction: { items: async (transaction) => { return (await transaction.populate('items').execPopulate()).items; }, }, Mutation: {... some mutation functions ...}, Query: {... some query functions ...},
Транзакция здесь такая же, как и у typeDefs.js, и мы собираемся указать ее поле items с помощью асинхронной функции. Эта функция распознавателя унаследована от сервера Apollo, и функция принимает 4 аргумента, (parent, args, context, info)
. Но я собираюсь использовать только аргумент parent, который возвращает значение преобразователя для родительского поля этого поля, в данном случае transaction, родительского поля элементов.
Теперь нам нужно создать преобразователь для заполнения элементов.
// resolver/transaction.js const transactionResolver = { ... previous code ... createItem: async (_, { productId, transactionId }) => { try{ const transaction = await Transaction.findById(transactionId); await transaction.items.push(productId); const savedTransaction = await transaction.save(); return savedTransaction; }catch(error){ console.error(error.message) } },
Для заполнения:
- найти транзакцию для заполнения по ее идентификатору
- вставить идентификатор товара в поле товаров
- затем сохраните изменение
- вернуть сохранить транзакцию
Вот как заполнить вложенный документ, на который ссылается его идентификатор. Мне нужно будет привыкнуть к этому, поскольку мне пришлось внести немало изменений. Спасибо за чтение, я надеюсь, что это также помогло тем из вас, кто хочет реализовать подобную архитектуру схемы.