Problem with composite keys - Expected id to be a string or number, received null

Hey there, I stubbling against an annoying problem I cannot manage to solve… Thank you for your help :slight_smile:

Expected behavior

I have 2 models, one is a Proposal that represents a contract, the other a Proposal Member representing a member assigned to the proposal/contract.

A proposal can have multiple proposal member, but a proposal member belong to only 1 proposal (Proposal Member uses a composite key based on the memberId and the proposalId).

Below are the models generated by Forest admin at setup:

Proposal model

// This model was generated by Forest CLI. However, you remain in control of your models.
// Learn how here: https://docs.forestadmin.com/documentation/v/v6/reference-guide/models/enrich-your-models
module.exports = (sequelize, DataTypes) => {
  const { Sequelize } = sequelize;
  // This section contains the fields of your model, mapped to your table's columns.
  // Learn more here: https://docs.forestadmin.com/documentation/v/v6/reference-guide/models/enrich-your-models#declaring-a-new-field-in-a-model
  const Proposal = sequelize.define(
    'proposal',
    {
      text: {
        type: DataTypes.STRING,
        allowNull: false,
      },
      createdAt: {
        type: DataTypes.DATE,
        defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
      },
      updatedAt: {
        type: DataTypes.DATE,
      },
      paymentOption: {
        type: DataTypes.ENUM('MILESTONES', 'PAY_AS_YOU_GO'),
        allowNull: false,
      },
      platformPercentage: {
        type: DataTypes.INTEGER,
        defaultValue: 5,
        allowNull: false,
      },
      vat: {
        type: DataTypes.INTEGER,
        defaultValue: 20,
        allowNull: false,
      },
    },
    {
      tableName: 'Proposal',
      schema: process.env.DATABASE_SCHEMA,
    }
  );

  // This section contains the relationships for this model. See: https://docs.forestadmin.com/documentation/v/v6/reference-guide/relationships#adding-relationships.
  Proposal.associate = (models) => {
    Proposal.belongsTo(models.opportunity, {
      foreignKey: {
        name: 'opportunityIdKey',
        field: 'opportunityId',
      },
      as: 'opportunity',
    });
    Proposal.hasMany(models.proposalMember, {
      foreignKey: {
        name: 'proposalIdKey',
        field: 'proposalId',
      },
      as: 'proposalProposalMembers',
    });
    Proposal.hasMany(models.proposalMilestone, {
      foreignKey: {
        name: 'proposalIdKey',
        field: 'proposalId',
      },
      as: 'proposalProposalMilestones',
    });
  };

  return Proposal;
};

Proposal member model

// This model was generated by Forest CLI. However, you remain in control of your models.
// Learn how here: https://docs.forestadmin.com/documentation/v/v6/reference-guide/models/enrich-your-models
module.exports = (sequelize, DataTypes) => {
  const { Sequelize } = sequelize;
  // This section contains the fields of your model, mapped to your table's columns.
  // Learn more here: https://docs.forestadmin.com/documentation/v/v6/reference-guide/models/enrich-your-models#declaring-a-new-field-in-a-model
  const ProposalMember = sequelize.define(
    'proposalMember',
    {
      proposalId: {
        type: DataTypes.INTEGER,
        primaryKey: true,
        allowNull: false,
      },
      memberId: {
        type: DataTypes.INTEGER,
        primaryKey: true,
        allowNull: false,
      },
      title: {
        type: DataTypes.STRING,
        allowNull: false,
      },
      description: {
        type: DataTypes.STRING,
        allowNull: false,
      },
      isLead: {
        type: DataTypes.BOOLEAN,
        defaultValue: false,
        allowNull: false,
      },
      rate: {
        type: DataTypes.INTEGER,
        allowNull: false,
      },
      unit: {
        type: DataTypes.ENUM('DAYS', 'HOURS'),
      },
      quantity: {
        type: DataTypes.INTEGER,
      },
      allocation: {
        type: DataTypes.INTEGER,
      },
    },
    {
      tableName: 'ProposalMember',
      timestamps: false,
      schema: process.env.DATABASE_SCHEMA,
    }
  );

  // This section contains the relationships for this model. See: https://docs.forestadmin.com/documentation/v/v6/reference-guide/relationships#adding-relationships.
  ProposalMember.associate = (models) => {
    ProposalMember.belongsTo(models.member, {
      foreignKey: {
        name: 'memberIdKey',
        field: 'memberId',
      },
      as: 'member',
    });
    ProposalMember.belongsTo(models.proposal, {
      foreignKey: {
        name: 'proposalIdKey',
        field: 'proposalId',
      },
      as: 'proposal',
    });
  };

  return ProposalMember;
};

Actual behavior

What I see is:

  • I can visualise both collections idependently
  • From a proposal member, I can see the link to a proposal

BUT

When I click on Details for a Proposal and click on the Proposal proposal member relation (basically when I look for the members for a proposal using relationship on a field),

I get a

Cannot reach your data

Your server may be down or your database connection broken.

What I see is that the request has returned form the server successfully

{
"data":[
{"type":"proposalMember",
"attributes":{"proposalId":1,"memberId":2,"title":"Paul the engineer","description":"description","isLead":false,"rate":20,"unit":"DAYS","quantity":20,"allocation":30},
"relationships":{
"member":{"data":{"type":"member","id":"2"},"links":{"related":{"href":"/forest/proposalMember/undefined/relationships/member"}}},
"proposal":{"data":{"type":"proposal","id":"1"},"links":{"related":{"href":"/forest/proposalMember/undefined/relationships/proposal"}}}}
},
],
"included":[
{"type":"member","id":"2","attributes":{"id":2}},
{"type":"proposal","id":"1","attributes":{"id":1},"relationships":{}}
]}

And note I can export the 2 proposals in CSV by clikcing on Action and all works. I also see the 2 records are available for search (look at the Search within 2 proposal proposal ... at the top right)

But I get on the console

[forest] 🌳🌳🌳 Unexpected error in the list view:  Error: Expected id to be a string or number, received null
    at i (vendor-5274ce00880a892114c98442cc88aca6.js:14353)
    at l.h._load (vendor-5274ce00880a892114c98442cc88aca6.js:14776)
    at l.h._pushInternalModel (vendor-5274ce00880a892114c98442cc88aca6.js:14786)
    at vendor-5274ce00880a892114c98442cc88aca6.js:14784
    at e.n._run (vendor-5274ce00880a892114c98442cc88aca6.js:5357)
    at e.n._join (vendor-5274ce00880a892114c98442cc88aca6.js:5356)
    at e.n.join (vendor-5274ce00880a892114c98442cc88aca6.js:5322)
    at l.h._push (vendor-5274ce00880a892114c98442cc88aca6.js:14780)
    at vendor-5274ce00880a892114c98442cc88aca6.js:14732
    at b (vendor-5274ce00880a892114c98442cc88aca6.js:5765)
    at v (vendor-5274ce00880a892114c98442cc88aca6.js:5763)
    at t.invoke (vendor-5274ce00880a892114c98442cc88aca6.js:5286)
    at e.t.flush (vendor-5274ce00880a892114c98442cc88aca6.js:5278)
    at e.t.flush (vendor-5274ce00880a892114c98442cc88aca6.js:5292)
    at e.n._end (vendor-5274ce00880a892114c98442cc88aca6.js:5355)
    at _boundAutorunEnd (vendor-5274ce00880a892114c98442cc88aca6.js:5306)

My intuition is I am missing an id field on what is returned by the requests, but I don’t understand why one is required or how to solve this.

Thank you :bowing_man:

Context

Please provide any relevant information about your setup.

  • Package Version:
  • Express Version: ~4.17.1
  • Sequelize Version: ^8.0.0
  • Database Dialect: Postgresql
  • Database Version: PostgreSQL 13.3
  • Project Name: Collective.work

Hi @Paul_Vidal, and welcome to our community :wave:

I tried on my end with a similar setup and I’m totally able to reproduce.
I’ll open a ticket on our end for this issue.

The easiest workaround here would be to add an id column, or to override the route to add a precomputed id field. It is a far from perfect solution though.

Sorry for the inconvenience

Sorry for the inconvenience.

Thanks Jeff for the responsiveness :slight_smile:

I tried adding a smart field id by joining memberId and proposalId but it failed, maybe because as you said I did not override the route…

Would you mind giving me more context on that?

Thanks for taking care of that :bowing_man:

(Fun fact - I also tried to add an id smart field on my setup, hoping that the serialization process would consider adding it as a JSONAPI serialized id - which does not work because smart fields cannot be used as a primary key :slight_smile: ).

In fact, I think I completely missed the issue here. It is a regression on our end, introduced in 7.7.0. I’ll update the previous ticket to add all the informations I discovered while searching.

Again, sorry for the inconvenience.

@Paul_Vidal,

We’ve just released a new version of forest-express-sequelize@8.0.10 that should fix your issue (You should be able to display proposal members as a related data).

Let me know if you are still having the issue or if it is fixed :pray:

problem solved indeed :heart: Thank you for the awesome support