Display array of nested documents as smart relationship (in related data)

I need to display nested documents which are in a array of my payment record.

My payment model looks like:

{
  schedules: [
    {
      amount: Number,
      status: Number
    }, 
     ...  
]
}

Expected behavior

Being able to create a smart relation ships to display the schedules as related data in the payment record view (UI).

Actual behavior

I don’t find a way to create this smart relation because schedules is not a reference on my payment record.

Failure Logs

| [forest] 🌳🌳🌳  Cannot find the 'schedules' reference field for 'payment' collection.

Context

“database_type”: “MongoDB”,
“liana”: “forest-express-mongoose”,
“liana_version”: “5.7.0”,
“engine”: “nodejs”,
“engine_version”: “12.18.3”,
“framework”: “other”,
“framework_version”: null,
“orm_version”: “5.9.4”

Hi @guillaumejauffret,

This is a really good question. I made an example in the past but I could not find it so let me try to help you :slight_smile:
So first you’ll need to create a Smart collection:

const { collection } = require('forest-express-sequelize');

collection('schedules', {
  onlyForRelationships: true,
  isSearchable: false,
  fields: [{
      field: 'amount',
      type: 'Number',
    }, {
      field: 'status',
      type: 'Number',
    }, {
      field: 'id',
      type: 'String',
  }],
});

Then in your route you just have to do:

router.get('/payment/:paymentId/relationships/schedules', ensureAuthenticated, permissionMiddlewareCreator.details(), async (request, response) => {
  const { paymentId } = request.params;
  const paymentFound = await payment.findByPk(paymentId);
  const recordSerializer = new RecordSerializer({ name: 'schedules' });
  const schedules = paymentFound ? paymentFound.schedules : [];
  const schedulesWithId = schedules.map((schedule, index) => ({ ...schedule, id: `${paymentId}-${index}`));

  response.send(await recordSerializer.serialize(schedulesWithId, { count: schedulesWithId.length }));
});

I hope this works for you :slight_smile:

1 Like

thanks a lot! @vince. I am going to test that right now

@vince can I also create Smart Actions for the record of this Smart Collection?

@vince I still can’t display my smart related records.

I added a smart field schedules in my payment collection like this:

collection('payment', {
  actions: [cancelPaymentSchedule, sendScheduleReminderEmail],
  fields: [
    {
      field: 'schedules',
      type: ['Object'],
      reference: 'payment.schedules'
    }
  ],
  segments: []
}) 

I am not the sure the reference payment.schedules is good nor the type [Object'].

Then, I created a smart collection like this:

collection('schedule', {
  onlyForRelationships: true,
  isSearchable: false,
  fields: [
    {
      field: 'method',
      type: 'Number'
    },
    {
      field: 'amount',
      type: 'Number'
    },
    {
      field: 'currency',
      type: 'Number'
    },
    {
      field: 'dueDate',
      type: 'Date'
    },
    {
      field: 'status',
      type: 'Number'
    },
    {
      field: '_id',
      type: 'String'
    }
  ]
})

Then, I created a route:

router.get('/payment/:paymentId/relationships/schedules', handleGetPaymentSchedules)

Here is the handleGetPaymentSchedules function:

const { RecordSerializer } = require('forest-express-mongoose')

async function handleGetPaymentSchedules(request, response) {
  const { paymentId } = request.params
  const recordSerializer = new RecordSerializer({ modelName: 'schedule' })
  const payment = await Payment.findById(paymentId)
  const schedules = payment.schedules.map(schedule => {
    return {
      _id: schedule._id,
      method: schedule.method,
      amount: schedule.amount,
      status: schedule.status,
      dueDate: schedule.dueDate,
      currency: schedule.currency
    }
  }

  const serializedSchedules = await recordSerializer.serialize(schedules, { count: schedules.length })
  response.send(serializedSchedules)
}

Should RecordSerializer be imported from forest-express-mongoose' or from forest-express-sequelize' ? I am on MongoDB.

I get this result so far:

and my logs after I clicked on the schedules link in the payment recored related data:

api_forest_1  | info:    ┏ GET /forest/payment/5f7d6edc5f2ec6015f124cf2/relationships/schedules?fields%5Bpayment%5D=_id%2CorderId%2CcreatedAt%2CupdatedAt&fields%5BorderId%5D=orderNumber&page%5Bnumber%5D=1&page%5Bsize%5D=15&timezone=Europe%2FLondon&sort=-_id 304 15ms +13ms
api_forest_1  | info:    ┃ [ 1] {
api_forest_1  | info:    ┃ [ 2]   http: {
api_forest_1  | info:    ┃ [ 3]     request: {
api_forest_1  | info:    ┃ [ 4]       method: 'GET',
api_forest_1  | info:    ┃ [ 5]       url: 'http://api-forest.dev.localhost/forest/payment/5f7d6edc5f2ec6015f124cf2/relationships/schedules?fields%5Bpayment%5D=_id%2CorderId%2CcreatedAt%2CupdatedAt&fields%5BorderId%5D=orderNumber&page%5Bnumber%5D=1&page%5Bsize%5D=15&timezone=Europe%2FLondon&sort=-_id',
api_forest_1  | info:    ┃ [ 6]       path: '/forest/payment/5f7d6edc5f2ec6015f124cf2/relationships/schedules',
api_forest_1  | info:    ┃ [ 7]       query: {
api_forest_1  | info:    ┃ [ 8]         fields: { payment: '_id,orderId,createdAt,updatedAt', orderId: 'orderNumber' },
api_forest_1  | info:    ┃ [ 9]         page: { number: '1', size: '15' },
api_forest_1  | info:    ┃ [10]         timezone: 'Europe/London',
api_forest_1  | info:    ┃ [11]         sort: '-_id'
api_forest_1  | info:    ┃ [12]       },
api_forest_1  | info:    ┃ [13]       headers: {},
api_forest_1  | info:    ┃ [14]       source_ip: '172.18.0.1'
api_forest_1  | info:    ┃ [15]     },
api_forest_1  | info:    ┃ [16]     responseTime: 15,
api_forest_1  | info:    ┃ [17]     response: { status: 304, message: 'Not Modified', type: undefined, headers: {} }
api_forest_1  | info:    ┃ [18]   },
api_forest_1  | info:    ┃ [19]   service: 'api_forest',
api_forest_1  | info:    ┃ [20]   env: 'development',
api_forest_1  | info:    ┃ [21]   version: 'v1.4.9-72-g0e7e9371',
api_forest_1  | info:    ┃ [22]   context: {
api_forest_1  | info:    ┃ [23]     message: 'GET /forest/payment/5f7d6edc5f2ec6015f124cf2/relationships/schedules?fields%5Bpayment%5D=_id%2CorderId%2CcreatedAt%2CupdatedAt&fields%5BorderId%5D=orderNumber&page%5Bnumber%5D=1&page%5Bsize%5D=15&timezone=Europe%2FLondon&sort=-_id 304 15ms'
api_forest_1  | info:    ┃ [24]   },
api_forest_1  | info:    ┃ [25]   timestamp: '2020-10-08T10:00:49.351Z'
api_forest_1  | info:    ┗ [26] }

Those requests happen after the schedule are sequalized. I don’t understand why Forest is doing this request:

ET /forest/payment/5f7d6edc5f2ec6015f124cf2/relationships/schedules?fields%5Bpayment%5D=_id%2CorderId%2CcreatedAt%2CupdatedAt&fields%5BorderId%5D=orderNumber&page%5Bnumber%5D=1&page%5Bsize%5D=15&timezone=Europe%2FLondon&sort=-_id

and here is the serialized schedules that are return by the serialize() function:

{
api_forest_1  |     "meta": {
api_forest_1  |         "count": 1
api_forest_1  |     },
api_forest_1  |     "data": [
api_forest_1  |         {
api_forest_1  |             "type": "schedule",
api_forest_1  |             "attributes": {
api_forest_1  |                 "amount": 13720,
api_forest_1  |                 "status": 2,
api_forest_1  |                 "_id": "5f7d6edc5f2ec6015f124a7a"
api_forest_1  |             }
api_forest_1  |         }
api_forest_1  |     ]
api_forest_1  | }

so shouldn’t it be displayed correctly?

Hi @guillaumejauffret,

I am afraid @vince did not notice that you were working on MongoDB, he gave you a solution for Sequelize.
The good news though is that for MongoDB, it should be straight forward, no need to create a smart collection!
You should just define your model properly and the related data should appear automatically.

Is your model defined like something like this?

const schema = mongoose.Schema({
  'schedules': [{
    'amount': Number,
    'status': Number
  }],
  ...
});

@anon34731316 yes it is:

const PaymentScheduleSchema = mongoose.Schema({
  method: {
    type: Number,
    enum: Object.values(EPaymentsMethod)
  },
  amount: Number, // integer - positive or negative - a negative amount correspond to a refund
  currency: {
    type: Number,
    enum: Object.values(EPaymentCurrency),
    default: EPaymentCurrency.EUR
  },
  dueDate: Date,
  status: {
    type: Number,
    enum: Object.values(EPaymentStatus)
  }
})

const PaymentSchema = mongoose.Schema(
  {
    orderId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'order'
    },
    schedules: [PaymentScheduleSchema]
  },
  {
    timestamps: true,
    versionKey: false
  }
)

however it does not work

Hum that’s strange, I’m trying to reproduce and it is working fine :thinking:

Do you have any error in your server console or browser console?

And do you mind upgrading your forest-express-mongoose to v6 (this note will help you to do so)?

ok @anon34731316 i will try this.

should I keep the schedules field in the /forest/payment.js as it is?

collection('payment', {
  actions: [cancelPaymentSchedule, sendScheduleReminderEmail],
  fields: [
    {
      field: 'schedules',
      type: ['Object'],
      reference: 'payment.schedules'
    }
  ],
  segments: []
})

@guillaumejauffret no you should not need any smart field to make it work!
Did you try after removing it?
And do you have any error?

@anon34731316 I did remove it and update forest-express-mongoose to v6.

But I don’t see any link in related data. I see my schedules array as json in the record. That’s it:

I am not sure to understand. A payment record in MongoDB looks like this:
image

so the array of schedules is within the payment record. How could it created related data without smart collection?

@guillaumejauffret,

To better understand, you can have a look to this documentation ressource.
If you define an array of objects (level “3” in the example), it should be displayed as related data.

I still have no idea why it does not work in your case though.
May you try with a model like this?

const PaymentSchema = mongoose.Schema(
  {
    orderId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'order'
    },
    schedules: [{
      method: {
        type: Number,
        enum: Object.values(EPaymentsMethod)
      },
      amount: Number, // integer - positive or negative - a negative amount correspond to a refund
      currency: {
        type: Number,
        enum: Object.values(EPaymentCurrency),
        default: EPaymentCurrency.EUR
      },
      dueDate: Date,
      status: {
        type: Number,
        enum: Object.values(EPaymentStatus)
      }
    }]
  },
  {
    timestamps: true,
    versionKey: false
  }
)

@anon34731316 that way of writting the schedule schema (directly nested in the schedules property) makes it work!

Three questions:

  • How can I rename the properties of a schedule (columns name)?
  • How can I display a label for the status, method and currency property ?
  • How could I now create a single smart action for a record of schedule? :slight_smile:

Hello @guillaumejauffret,

Editing fields from a related data with nested document in mongoose is not possible yet with Forest Admin.
I will let know our product board for addressing this behaviour.

hi @Guillaume_Cisco,

ok but is possible to create smart single action on nested document from related data?

Hi @guillaumejauffret,
This is not yet possible to do so; a nested document is not considered as a dedicated collection where you could define this action in your Admin backend.