简体   繁体   中英

How do you seed a mongodb database such that the Keystone 5 CMS recognizes the many-to-many relationships?

Let's say I have two objects: Product and Seller

Products can have multiple Sellers. A single Seller can sell multiple Products.

The goal is to write a seeding script that successfully seeds my MongoDB database such that Keystone.js's CMS recognizes the many-to-many relationship.

Schemas

Product.ts

import { text, relationship } from "@keystone-next/fields";
import { list } from "@keystone-next/keystone/schema";

export const Product = list({
  fields: {
    name: text({ isRequired: true }),
    sellers: relationship({
      ref: "Seller.products",
      many: true,
    }),
  },
});

Seller.ts

import { text, relationship } from "@keystone-next/fields";
import { list } from "@keystone-next/keystone/schema";

export const Product = list({
  fields: {
    name: text({ isRequired: true }),
    products: relationship({
      ref: "Product.sellers",
      many: true,
    }),
  },
});

KeystoneJS config

My keystone.ts config, shortened for brevity, looks like this:

import { insertSeedData } from "./seed-data"
...
db: {
  adapter: "mongoose",
  url: databaseURL,
  async onConnect(keystone) {
    console.log("Connected to the database!");
    if (process.argv.includes("--seed-data")) {
      await insertSeedData(keystone);
    }
  },
},
lists: createSchema({
  Product,
  Seller,
}),
...

Seeding Scripts (these are the files I expect to change)

I have a script that populates the database ( seed-data/index.ts ):

import { products } from "./data";
import { sellers } from "./data";

export async function insertSeedData(ks: any) {

  // setup code
  const keystone = ks.keystone || ks;
  const adapter = keystone.adapters?.MongooseAdapter || keystone.adapter;
  const { mongoose } = adapter;
  mongoose.set("debug", true);

  // adding products to DB
  for (const product of products) {
    await mongoose.model("Product").create(product);
  }

  // adding sellers to DB
  for (const seller of sellers) {
    await mongoose.model("Seller").create(seller);
  }
}

And finally, data.ts looks something like this:

export const products = [
  {
    name: "apple",
    sellers: ["Joe", "Anne", "Duke", "Alicia"],
  },
  {
    name: "orange",
    sellers: ["Duke", "Alicia"],
  },
  ...
];
export const sellers = [
  {
    name: "Joe",
    products: ["apple", "banana"],
  },
  {
    name: "Duke",
    products: ["apple", "orange", "banana"],
  },
  ...
];

The above setup does not work for a variety of reasons. The most obvious is that the sellers and products attributes of the Product and Seller objects (respectively) should reference objects ( ObjectId ) and not names (eg "apple", "Joe").

I'll post a few attempts below that I thought would work, but did not:

Attempt 1

I figured I'd just give them temporary ids (the id attribute in data.ts below) and then, once MongoDB assigns an ObjectId , I'll use those.

seed-data/index.ts

...
  const productIdsMapping = [];
...
  // adding products to DB
  for (const product of products) {
    const productToPutInMongoDB = { name: product.name };
    const { _id } = await mongoose.model("Product").create(productToPutInMongoDB);
    productIdsMapping.push(_id);
  }

  // adding sellers to DB (using product IDs created by MongoDB)
  for (const seller of sellers) {
    const productMongoDBIds = [];
    for (const productSeedId of seller.products) {
      productMongoDBIds.push(productIdsMapping[productSeedId]);
    const sellerToPutInMongoDB = { name: seller.name, products: productMongoDBIds };
    await mongoose.model("Seller").create(sellerToPutInMongoDB);
  }
...

data.ts

export const products = [
  {
    id: 0,
    name: "apple",
    sellers: [0, 1, 2, 3],
  },
  {
    id: 1,
    name: "orange",
    sellers: [2, 3],
  },
  ...
];
export const sellers = [
  {
    id: 0
    name: "Joe",
    products: [0, 2],
  },
  ...
  {
    id: 2
    name: "Duke",
    products: [0, 1, 2],
  },
  ...
];

Output (attempt 1):

It just doesn't seem to care about or acknowledge the products attribute.

Mongoose: sellers.insertOne({ _id: ObjectId("$ID"), name: 'Joe', __v: 0}, { session: null })
{
  results: {
    _id: $ID,
    name: 'Joe',
    __v: 0
  }
}

Attempt 2

I figured maybe I just didn't format it correctly, for some reason, so maybe if I queried the products and shoved them directly into the seller object, that would work.

seed-data/index.ts

...
  const productIdsMapping = [];
...
  // adding products to DB
  for (const product of products) {
    const productToPutInMongoDB = { name: product.name };
    const { _id } = await mongoose.model("Product").create(productToPutInMongoDB);
    productIdsMapping.push(_id);
  }

  // adding sellers to DB (using product IDs created by MongoDB)
  for (const seller of sellers) {
    const productMongoDBIds = [];
    for (const productSeedId of seller.products) {
      productMongoDBIds.push(productIdsMapping[productSeedId]);
    }
    const sellerToPutInMongoDB = { name: seller.name };
    const { _id } = await mongoose.model("Seller").create(sellerToPutInMongoDB);
    const resultsToBeConsoleLogged = await mongoose.model("Seller").findByIdAndUpdate(
      _id,
      {
        $push: {
          products: productMongoDBIds,
        },
      },
      { new: true, useFindAndModify: false, upsert: true }
    );
  }
...

data.ts

Same data.ts file as attempt 1.

Output (attempt 2):

Same thing. No luck on the products attribute appearing.

Mongoose: sellers.insertOne({ _id: ObjectId("$ID"), name: 'Joe', __v: 0}, { session: null })
{
  results: {
    _id: $ID,
    name: 'Joe',
    __v: 0
  }
}

So, now I'm stuck. I figured attempt 1 would Just Work™ like this answer:

https://stackoverflow.com/a/52965025

Any thoughts?

I figured out a solution. Here's the background:

When I define the schema, Keystone creates corresponding MongoDB collections. If there is a many-to-many relationship between object A and object B, Keystone will create 3 collections: A, B, and A_relationshipToB_B_relationshipToA.

That 3rd collection is the interface between the two. It's just a collection with pairs of ids from A and B.

Hence, in order to seed my database with a many-to-many relationship that shows up in the Keystone CMS, I have to seed not only A and B, but also the 3rd collection: A_relationshipToB_B_relationshipToA.

Hence, seed-data/index.ts will have some code that inserts into that table:

...
for (const seller of sellers) {
    const sellerToAdd = { name: seller.name };
    const { _id } = await mongoose.model("Seller").create(sellerToAdd);

    // Product_sellers_Seller_products Insertion
    for (const productId of seller.products) {
      await mongoose
        .model("Product_sellers_Seller_products")
        .create({
          Product_left_id: productIds[productId], // (data.ts id) --> (Mongo ID)
          Seller_right_id: _id,
        });
    }
  }
...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM