Embedded Document with String Array fields

Hi,
I was wondering where in the documentation I can find the limitations of the layout we get for mongoose “embedded documents” with different structures.

An embedded doc with simple fields would be rendered as an “indented” UI part on the details page:

const schema = mongoose.Schema(
  {
    moreInfo: {
      name: String,
      lastActive: Date
    }
  }
);

image

And if adding an “array” field to that embedded doc - it will now render as JSON (like it’s falling back on this display).

const schema = mongoose.Schema(
  {
    moreInfo: {
      name: String,
      lastActive: Date,
      classes: [String]
    }
  }
);

image

Is this by design? How do you suggest working around this? Working with flat documents is against mongodb design philosophy and we want to make use of complex structures in our data model when its the right solution.

Thanks!

Expected behavior

Allow at UI to render at least a single level of indentation even with array fields.

Actual behavior

Fallsback to JSON edit widget

Context

Please provide any relevant information about your setup.

  • Package Version: forest-express-mongoose@^6.1.0, forest-express@7.2.0
  • Database Dialect: Mongodb

Hello @Yoad_Snapir :wave:

Your concern is really clear and I understand it.

Sadly this is the best I can propose so far, embedded document with only one level will support editing each field directly, but if you add another level (another deep object or an array) we fallback to the JSON editor.

However, I find your feedback really interesting and supporting nested / deep structure could be a nice feature.

I’m adding your feedback to our productboard.

Sorry for inconvenience, and thanks for your feedback :raised_hands:

Steve.

Thanks Steve,
I thought one way to workaround the problem is to treat those embedded docs as a “hasOne” relationship.
Such that they appear on the left side navigation and have their own edit screen.

Would that be possible? how would you go about doing this?

Hi @Yoad_Snapir,

It’s definitely something we should support natively!

:bulb: A possible workaround would be to create a smart field to extract the information contained in classes.

See this example in our woodshop.

2 Likes

Hello again @Yoad_Snapir :wave:

Yes indeed this is possible, but only for array of embedded. The array must no be deep in an embedded object, it must be at level one of the model, for instance:

const schema = mongoose.Schema({
    ...
    products: [{
         attribute1: String,
         attribute2: String,
    }]
    ...
})

Using this type of declaration, you will be able to see the products records as related data on the left panel you spoke about in your previous message.

Also, as @louis pointed out, retrieving deeper levels of data and relocating them at the top level of the record using a smart field might also fit your need :+1:

Does this help ?

Steve.

Thanks guys, Ill work on a POC and share my results when I have them.

1 Like

Alright, keep us in touch :raised_hands:

Hi Guys,
I believe when not too many fields are involved indeed a SmartField could be a reasonable workaround.
This helper function could be useful: (uses lodash)

function extractEmbeddedFieldToRoot(path, type) {
  return {
    field: path.replace('.', '_'),
    type,
    get: (doc) => {
      return _.get(doc, path);
    },
    set: (doc, value) => {
      doc[path] = value;
      return doc;
    },
  };
}

Which is used like this:

Liana.collection('MyModel', {
  fields: [
    extractEmbeddedFieldToRoot('embDoc.field', 'String'),
    extractEmbeddedFieldToRoot('embDoc.arrField', ['String']),
  ],
});

Few points to note.
Field names are derived from the path here. Periods (".") are replaced with underscores ("_") which are transformed by Forest’s layouts to spaces. works great for us since we dont have underscores in field names anyway and lodash+mongodb use periods to denote hierarchy.
You might want some other field name / path logic.

We use here the full “path” on the output doc within the “set” handler since otherwise (if setting a nested object) the entire embedded doc is overridden due to how “$set” behaves when provided with a full object and not a path. @louis please note the example in the woodshop shows how to do it in a destructive way which may mislead developers.

One last comment, the woodshop example does not return the output doc form “set” handler and the docs say it’s required. Not sure what it affects (maybe other smart fields downstream) so I returned it to be on the safe side.

2 Likes

Quick update for anyone who is interested - this is the version we use today which allows specifying custom names and supports also embedded enum fields.

function extractEmbeddedFieldToRoot({ path, type, enums, name }) {
  const fieldDefinition = {
    field: name || path.replace('.', '_'),
    type,
    get: (doc) => {
      return _.get(doc, path);
    },
    set: (doc, value) => {
      doc[path] = value;
      return doc;
    },
  };

  if (type === 'Enum') {
    if (enums && enums.length) {
      fieldDefinition.enums = enums;
    } else {
      throw new Error('Enum typed fields must provide the enum values');
    }
  }

  return fieldDefinition;
}

hth

5 Likes