mswjs / data

Data modeling and relation library for testing JavaScript applications.
https://npm.im/@mswjs/data
MIT License
827 stars 52 forks source link

Auto populating related fields #232

Open Meemaw opened 2 years ago

Meemaw commented 2 years ago

Hey!

Have a question about auto populating oneOf fields.

Imagine the following factory:

const db = factory({
  item: {
    it: primaryKey(uuid),
    metadata: oneOf("chain"),
  },
  metadata: {
    id: primaryKey(uuid)
    name: () => "Test",
  }
})

Its possible to create item like so:

const item = db.item.create({
  metadata: db.metadata.create()
})

☝️ this is fine, but imagine we now have 5 relations here, and every time we create the item we need to pass in all of those related field creations.

Is there an option (and if not why not), that the following code would automatically also create all related fields defined on the factory:

const item = db.item.create() // automatically created metadata (and other related fields)
kettanaito commented 2 years ago

Hi, @Meemaw.

I believe relational properties ("metadata") do not have default values by design. So, whenever you create a parent entity, you must provide a reference to the child entity of the relationship to create a reference.

I like the explicit action here. Note that you can always abstract repetitiveness on your end, closer to the domain knowledge and model restrictions of your application.

function createItem(db) {
  return db.item.create({
    metadata: db.metadata.create(),
    one: db.one.create(),
    two: db.two.create(),
  })
}

const db = factory({ item: { ... }, metadata: { ... } })
const firstItem = createItem(db)
const secondItem = createItem(db)

Why not add this?

Relational properties have undefined as a value by default, and no relational entities are created unless you explicitly say so. I like that explicitness, it makes it easier to predict what the library is doing.

If we created undefined relationships when calling parental .create(), how would you then force some of them to stay undefined? There's a concept of a nullable relationship but it's there to describe an explicit absence of value rather than undefined value (semantic difference between null and undefined).

Referenced models may have their own relationships, creating a cascade of entities the library needs to create for you implicitly. You may not need to have all the deep relationship trees to test a certain scenario, for example. In that case, populating all relationships is wasting computing power.

Meemaw commented 2 years ago

Hi @kettanaito thanks for the answer.

I agree that this could be solved with the createItem helper. We are using@mswjs/data to auto generate graph for a GraphQL API for frontend unit tests & storybooks where by default we would like to have all related entities automatically created so things just work.

I'm trying to write a generic helper for a entity we have which could take in simple objects instead of msw model definitions and would work well with Typescript.

function createItem({metadata, one, two} = {}) {
  return db.item.create({
    metadata: db.metadata.create(metadata),
    one: db.one.create(one),
    two: db.two.create(two),
  })
}

And could be used like this:

createItem({
  metadata: {
    name: "Test"
  },
  one: {
    another: {
      age: 16
     }
  }
})

Or just

createItem()

This is a very basic & fixed example, but would like to have a generic solution for a very easy factory data creation, without having to think about msw that is powering it.

We basically never query or modify msw database, so wondering if it's a wrong tool for the job, and we should just use plain objects all around.