skip to Main Content

I’m trying to seed data for my db but both my schema reference each other so I’m not sure how it’s possible to seed one before the other.

I want to use fakerjs to have some data to play with, but I’m a bit confused how I’d approach it.

Here are my Schemas, they’re super simple and both ref each other:

import mongoose from "mongoose";
import { loadType } from "mongoose-currency";

const Schema = mongoose.Schema;
loadType(mongoose);

const CategorySchema = new Schema(
  {
    name: String,
    expenses: [
      {
        type: mongoose.Schema.Types.ObjectId,
        ref: "Expense",
      },
    ],
  },
  { timestamps: true, toJSON: { getters: true } }
);

const Category = mongoose.model("Category", CategorySchema);

export default Category;

And the other:

import mongoose from "mongoose";
import { loadType } from "mongoose-currency";

const Schema = mongoose.Schema;
loadType(mongoose);

const ExpenseSchema = new Schema(
  {
    name: String,
    price: {
      type: mongoose.Types.Currency,
      currency: "USD",
      get: (v) => v / 100,
    },
    date: {
      type: Date,
    },
    category: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "Category",
    },
  },
  { timestamps: true, toJSON: { getters: true } }
);

const Expense = mongoose.model("Expense", ExpenseSchema);

export default Expense;

Does anyone know how I’d go about seeding them if they both rely on each other?

2

Answers


  1. Chosen as BEST ANSWER

    This is what I landed at thanks to the answer above:

    (async function main() {
      await Promise.all([Category, Expense].map((m) => m.collection.drop()));
      const newCategories = categories.map((v) => new Category(v));
    
      const _ids = newCategories.map((item) => item._id);
    
      const expenses = Array.from({ length: 100 }, () => ({
        description: faker.lorem.sentence({ min: 3, max: 5 }),
        price: faker.finance.amount({ min: 5, max: 50, dec: 2, symbol: "$" }),
        date: faker.date.past({ years: 1 }),
        category: faker.helpers.arrayElement(_ids),
      })).map((v) => new Expense(v));
    
      newCategories.forEach((category) =>
        expenses.map((expense) => {
          if (expense.category === category._id) {
            category.expenses.push(expense._id);
          }
        })
      );
    
      await Promise.all(expenses.map((expense) => expense.save()));
      await Promise.all(newCategories.map((category) => category.save()));
    })();
    

    Not sure if this is most efficient, if anyone knows a better way, please lmk!


  2. Refs to children documentation explains this situation.

    E.g. ("mongoose": "^7.3.1")

    // @ts-nocheck
    import mongoose from 'mongoose';
    import util from 'util';
    import { config } from '../../config';
    
    const CategorySchema = new mongoose.Schema({
        name: String,
        expenses: [
            {
                type: mongoose.Schema.Types.ObjectId,
                ref: 'Expense',
            },
        ],
    });
    const Category = mongoose.model('Category', CategorySchema);
    
    const ExpenseSchema = new mongoose.Schema({
        name: String,
        category: {
            type: mongoose.Schema.Types.ObjectId,
            ref: 'Category',
        },
    });
    const Expense = mongoose.model('Expense', ExpenseSchema);
    
    (async function main() {
        mongoose.set('debug', true);
        try {
            await mongoose.connect(config.MONGODB_URI);
            await Promise.all([Category, Expense].map((m) => m.collection.drop()));
            // seed
            const [c1, c2] = [{ name: 'c1' }, { name: 'c2' }].map((v) => new Category(v));
            const [e1, e2, e3] = [
                { name: 'e1', category: c1._id },
                { name: 'e2', category: c1._id },
                { name: 'e3', category: c2._id },
            ].map((v) => new Expense(v));
    
            c1.expenses.push(e1, e2);
            c2.expenses.push(e3);
    
            await Promise.all([c1, c2].map((v) => v.save()));
            await Promise.all([e1, e2, e3].map((v) => v.save()));
    
            // populate
            const c = await Category.findOne({ name: 'c1' }).populate('expenses').exec();
            console.log(c?.toObject());
            const e = await Expense.findOne({ name: 'e1' })
                .populate({ path: 'category', populate: { path: 'expenses' } })
                .exec();
            console.log(util.inspect(e?.toObject(), false, null));
        } catch (error) {
            console.error(error);
        } finally {
            await mongoose.connection.close();
        }
    })();
    

    Debug logs:

    Mongoose: categories.drop()
    Mongoose: expenses.drop()
    Mongoose: categories.insertOne({ name: 'c1', expenses: [ ObjectId("649d8f7d038538fabd2e1ce3"), ObjectId("649d8f7d038538fabd2e1ce4") ], _id: ObjectId("649d8f7d038538fabd2e1ce1"), __v: 0}, {})
    Mongoose: categories.insertOne({ name: 'c2', expenses: [ ObjectId("649d8f7d038538fabd2e1ce5") ], _id: ObjectId("649d8f7d038538fabd2e1ce2"), __v: 0}, {})
    Mongoose: expenses.insertOne({ name: 'e1', category: ObjectId("649d8f7d038538fabd2e1ce1"), _id: ObjectId("649d8f7d038538fabd2e1ce3"), __v: 0}, {})
    Mongoose: expenses.insertOne({ name: 'e2', category: ObjectId("649d8f7d038538fabd2e1ce1"), _id: ObjectId("649d8f7d038538fabd2e1ce4"), __v: 0}, {})
    Mongoose: expenses.insertOne({ name: 'e3', category: ObjectId("649d8f7d038538fabd2e1ce2"), _id: ObjectId("649d8f7d038538fabd2e1ce5"), __v: 0}, {})
    Mongoose: categories.findOne({ name: 'c1' }, {})
    Mongoose: expenses.find({ _id: { '$in': [ ObjectId("649d8f7d038538fabd2e1ce3"), ObjectId("649d8f7d038538fabd2e1ce4") ], [Symbol(mongoose#trustedSymbol)]: true }}, { skip: undefined, limit: undefined, perDocumentLimit: undefined })
    {
      _id: new ObjectId("649d8f7d038538fabd2e1ce1"),
      name: 'c1',
      expenses: [
        {
          _id: new ObjectId("649d8f7d038538fabd2e1ce3"),
          name: 'e1',
          category: new ObjectId("649d8f7d038538fabd2e1ce1"),
          __v: 0
        },
        {
          _id: new ObjectId("649d8f7d038538fabd2e1ce4"),
          name: 'e2',
          category: new ObjectId("649d8f7d038538fabd2e1ce1"),
          __v: 0
        }
      ],
      __v: 0
    }
    Mongoose: expenses.findOne({ name: 'e1' }, {})
    Mongoose: categories.find({ _id: { '$in': [ ObjectId("649d8f7d038538fabd2e1ce1") ], [Symbol(mongoose#trustedSymbol)]: true }}, { skip: undefined, limit: undefined, perDocumentLimit: undefined })
    Mongoose: expenses.find({ _id: { '$in': [ ObjectId("649d8f7d038538fabd2e1ce3"), ObjectId("649d8f7d038538fabd2e1ce4") ], [Symbol(mongoose#trustedSymbol)]: true }}, { skip: undefined, limit: undefined, perDocumentLimit: undefined })
    {
      _id: new ObjectId("649d8f7d038538fabd2e1ce3"),
      name: 'e1',
      category: {
        _id: new ObjectId("649d8f7d038538fabd2e1ce1"),
        name: 'c1',
        expenses: [
          {
            _id: new ObjectId("649d8f7d038538fabd2e1ce3"),
            name: 'e1',
            category: new ObjectId("649d8f7d038538fabd2e1ce1"),
            __v: 0
          },
          {
            _id: new ObjectId("649d8f7d038538fabd2e1ce4"),
            name: 'e2',
            category: new ObjectId("649d8f7d038538fabd2e1ce1"),
            __v: 0
          }
        ],
        __v: 0
      },
      __v: 0
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search