Optional sub-documents in Ottoman Model


#1

Hi,

I’m currently modelling some documents for our system and would like to have some “optional” fields for the model, for example. A user may want to customise aspects of something they are ordering, or may just want to use a template. Is it possible to allow an entire sub-document to be “null” or missing (in Couchbase terms) in an Ottoman Model? If so, what’s the best way to achieve this?

Here’s an example:

const OrderItem = ottoman.model('OrderItem', {
  orderId: Order,
  templateId: FrameTemplate,
  customisation: {
    width: 'number',
    height: 'number'
  }
}, {
  index: {
    findByOrder: {
      by: 'orderId',
      type: 'n1ql'

    },
    findByTemplate: {
      by: 'templateId',
      type: 'n1ql'

    },
    findByID: {
      by: '_id',
      type: 'refdoc'
    }
  }
});

I would like the “customisation” to be optional, so rather than writing:

{type: 'number', default: 0.0}

for width & height, I could somehow signal that customisation can be empty / null.

The other question, leading on from this, is how do I have “variable” sub-objects in a model. For example, supposing some customisations have a “depth” field, but not everything does. I don’t want to fill in values for every possible opportunity in my model, but do I have to do this to achieve my goal?

Thanks

Glen


#2

Hey @glenswoop,

To solve your first problem, you can avoid specifying any fields in the submodel, and that should remove it from being included in the final object stored to couchbase.

In terms of the second idea (the notion of a field that does not exist on every instance in an array). There are two possible solutions to this, the first is to add the depth field to the model, but don’t fill it out when its not used. The alternative is to build an array of type ‘Object’, which will allow you to place any type of object or model. Some models with depth field, and some without.

Cheers, Brett


#3

Okay, so first answer. I need to store the sub-object (customisation) data in the DB - it’s not a separate model, so that doesn’t sound right, e.g. I need something like:

const OrderItem = ottoman.model('OrderItem', {
  orderId: Order,
  templateId: CarTemplate,
  customisation: { type: 'Object', default: null}
});

That also satisfies the criteria for the 2nd question, where I want to have variable structures:

customisation1 = {
  type: 'finish',
  paint: 'Electric Blue'
}

customisation2 = {
  type: 'wheels',
  style: '5-spoke',
  size: '18in'
}

I’m guessing that “Object” is not a supported type for Ottoman, but having the ability to have variable structures allows us to leverage the power of Couchbase / NoSQL ‘document’ type storage.


#4

Any answer on this? Is it possible to support sub-object definitions on Ottoman Models without defining another model?


#5

Hi @glenswoop,

You essentially need to use the Mixed type and then you can store any arbitrary structure in that position.

customisation: { type: 'Mixed', default: null}

However, given that you probably always want to know the type and allow multiple customizations I would probably define it as

customisation: [
    {
        type: string,
        attributes: 'Mixed'
    }
]

You could then use it like:

OrderItem.create({
    orderId: orderModel,
    templateId: templateModel,
    customisation: [
        {
            type: 'paint',
            attributes: {
                color: 'Electric Blue'
            }
        },
        {
            type: 'wheel',
            attributes: {
                style: '5-spoke',
                size: '18in',
            }
        }
    ]
})

If you’re wanting to guarantee what the structure of the customisation is, you’ll need to create them as Ottoman models and assign the model to the property. Brett’s blog post here: https://blog.couchbase.com/nodejs-odm-couchbase-ottoman/ touches on what approach with the GPS Position model.

const PaintCustomization = ottoman.model('PaintCustomization', {
  type: {type: 'string', default: 'paint'},
  color:'string'
})

const WheelCustomization = ottoman.model('WheelCustomization', {
  type:{type:  'string', default: 'wheel'},
  size:'string',
  style: 'string'
})

const OrderItem = ottoman.model('OrderItem', {
  orderId: Order,
  templateId: CarTemplate,
  customisation: 'Mixed'
})

OrderItem.create({
    orderId: orderModel,
    templateId: templateModel,
    customisation: WheelCustomization.fromData({
       size: '18in',
       style: '5-spoke'
    })
})

-TJ


#6

Hi,

Okay, thanks ‘Mixed’ is working. Doc’s said ‘mixed’.

Thanks

Glen