How to create nested computed fields?

Document structure:

Plan {
  chapter: {
    title: string,
    attachments: string[] // Should be derived (computed)
    // [...]
  }
}

How to compute nested fields?

collection Plan {
  compute chapter.attachments {
    doc => { }
  }
}
1 Like

You can’t inject computed fields into nested arrays or objects. You can create new objects from existing ones, though.

collection Plan {
  history_days 0
  
  // defining schema fields requires early access at the moment (*)
  // a field for "internal use only"
  _chapter: { title: String, /* ... */ }

  compute chapter = doc => Object.assign(doc.chapter, {
    attachments: Attachment.by_chapter(doc.chapter.title).toArray().map(.fileName)
    /*... more fields */
  })
}

Are you trying to handle relationships to a nested object (like my example assumes), or trying to effectively backfill part of your schema, or what are you trying to accomplish otherwise? Why must the attachments be nested under the chapter field?


(*)

To migrate from a schema like this

collection Plan {
  history_days 0
  
  chapter: { title: String }
}

could be update like this

collection Plan {
  history_days 0
  
  // a field for "internal use only"
  _chapter: { title: String }

  compute chapter = doc => Object.assign(doc.chapter, {
    attachments: Attachment.by_chapter(doc.chapter.title).toArray().map(.fileName)
  })

  migration m2024_04_25_new_chapter_format {
    rename chapter _chapter
  }
}

I try to explain the use case simplified.

We have a questionaire that the user answers. So we storing the questions and answers as dedicated collections(Question & Answer). As part of the questionaire the user also gets asked to upload different type of attachments. The Questions and Answers are assigned to different MasterChapters.
Then we generate from the questionaire a document (Collection: Plan) that includes chapters that are based on the MasterChapters. And inside the chapter we want to derive the attachments from the questionaire. So if the attachments will be updated as part of the questionare the attachments as part of the document will be every time in sync thanks to the computed value.

Collections (Simplified):

  • Question
  • Answer
  • MasterChapter
  • Plan (Chapter embedded)

We decided to embedd Chapter in Plan, because:

  1. Every chapter appears only in one plan (Performance improved)
  2. We have some CRUD and reordering mechanisms for the chapters array which was quite simple to implement with Array operations in comparison to Document operations.

Another place where a nested computed field would help @ptpaterson :

Collection Answer

question: Question,
content: {
  select: Boolean | null,
  text: String | null,
  number: Number | null,
  formula: Number | null
}

compute content.formula = (
  doc => (
    if (doc.masterAnswer.formula != null){
        useFormula(doc.masterAnswer.formula, doc)
      }
  )
)

The Use Case is related with the case that we discussed in Privilege elevation in computed fields

I’ll pass on the use case. I wonder if we can include computed fields on Structs, on or after release.

// proposal, not real FSL
struct Content {
  select: Boolean | null,
  text: String | null,
  number: Number | null,

  compute formula = obj =>...
}

collection Answer {
  content: Content
}

But that wouldn’t let you access fields outside of the struct.

In general, if you want to be able to query/read a field with a different shape than what is persisted, then I recommend the pattern where you store an internal field with the actual data, and use a computed field for how you want to query it, e.g. the _chapter field I shared above. Alternatively, you can project an additional field when querying it, potentially wrapping that projection in a UDF.

We need to be able to unambiguously parse your schema in order to provide typechecking as well as safe, deterministic migrations from one schema to another. Having a persisted field that is altered by a computed field works against that.

1 Like