Forest admin uses snake_case to reference camelCase columns

Hello,

Expected behavior

Display a simple relationship table in forestadmin called EndpointEndpointTag.

The table definition (typeorm):

@Entity('endpoint_endpoint_tags_endpoint_tag')
export class EndpointEndpointTag {
  @PrimaryGeneratedColumn('uuid')
  id?: string;

  @ManyToOne(() => Endpoint)
  endpoint!: Endpoint;

  @Column('uuid')
  endpointId!: string;

  @ManyToOne(() => EndpointTag)
  endpointTag!: EndpointTag;

  @Column('uuid')
  endpointTagId!: string;
}

The table SQL:

   CREATE TABLE "endpoint_endpoint_tags_endpoint_tag" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "endpointId" uuid NOT NULL, "endpointTagId" uuid NOT NULL, CONSTRAINT "PK_578480e08bbbcb2539c619c2f36" PRIMARY KEY ("id"))
        ALTER TABLE "endpoint_endpoint_tags_endpoint_tag" ADD CONSTRAINT "FK_607ba4cc41e8bf50c5aee96cbd6" FOREIGN KEY ("endpointId") REFERENCES "endpoint"("id") ON DELETE NO ACTION ON UPDATE NO ACTION
        ALTER TABLE "endpoint_endpoint_tags_endpoint_tag" ADD CONSTRAINT "FK_e5392616b464b0dccc440170d19" FOREIGN KEY ("endpointTagId") REFERENCES "endpoint_tag"("id") ON DELETE NO ACTION ON UPDATE NO ACTION

I updated FA models with lumber update.

The FA model endpoint-endpoint-tags-endpoint-tag.js :

// This model was generated by Lumber. 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 EndpointEndpointTagsEndpointTag = sequelize.define('endpointEndpointTagsEndpointTag', {
    id: {
      type: DataTypes.UUID,
      primaryKey: true,
      defaultValue: Sequelize.literal('uuid_generate_v4()'),
      allowNull: false,
    },
  }, {
    tableName: 'endpoint_endpoint_tags_endpoint_tag',
    underscored: true,
    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.
  EndpointEndpointTagsEndpointTag.associate = (models) => {
    EndpointEndpointTagsEndpointTag.belongsTo(models.endpoint, {
      foreignKey: {
        name: 'endpointIdKey',
        field: 'endpointId',
      },
      as: 'endpoint',
    });
    EndpointEndpointTagsEndpointTag.belongsTo(models.endpointTag, {
      foreignKey: {
        name: 'endpointTagIdKey',
        field: 'endpointTagId',
      },
      as: 'endpointTag',
    });
  };

  return EndpointEndpointTagsEndpointTag;
};

The forest schema:

+  }, {
+    "name": "endpointEndpointTagsEndpointTag",
+    "nameOld": "endpointEndpointTagsEndpointTag",
+    "icon": null,
+    "integration": null,
+    "isReadOnly": false,
+    "isSearchable": true,
+    "isVirtual": false,
+    "onlyForRelationships": false,
+    "paginationType": "page",
+    "fields": [{
+      "field": "endpoint",
+      "type": "String",
+      "defaultValue": null,
+      "enums": null,
+      "integration": null,
+      "isFilterable": true,
+      "isPrimaryKey": false,
+      "isReadOnly": false,
+      "isRequired": false,
+      "isSortable": true,
+      "isVirtual": false,
+      "reference": "endpoint.endpointIdKey",
+      "inverseOf": null,
+      "relationship": "BelongsTo",
+      "validations": []
+    }, {
+      "field": "endpointTag",
+      "type": "String",
+      "defaultValue": null,
+      "enums": null,
+      "integration": null,
+      "isFilterable": true,
+      "isPrimaryKey": false,
+      "isReadOnly": false,
+      "isRequired": false,
+      "isSortable": true,
+      "isVirtual": false,
+      "reference": "endpointTag.endpointTagIdKey",
+      "inverseOf": null,
+      "relationship": "BelongsTo",
+      "validations": []
+    }, {
+      "field": "id",
+      "type": "String",
+      "defaultValue": null,
+      "enums": null,
+      "integration": null,
+      "isFilterable": true,
+      "isPrimaryKey": true,
+      "isReadOnly": false,
+      "isRequired": false,
+      "isSortable": true,
+      "isVirtual": false,
+      "reference": null,
+      "inverseOf": null,
+      "validations": []
+    }],
+    "segments": [],
+    "actions": []
   }, {

Actual behavior

The table cannot be displayed on ForestAdmin.

It seems that Forest gives snake case fields to sequelize. I did grep the whole project but no reference for endpoint_tag_id to refer to camelCase fields …

Failure Logs

[forest] 🌳🌳🌳  Unexpected error: column endpointEndpointTagsEndpointTag.endpoint_tag_id does not exist
{
  "name": "SequelizeDatabaseError",
  "parent": {
    "length": 146,
    "name": "error",
    "severity": "ERROR",  
    "code": "42703",
    "position": "188",    
    "file": "parse_relation.c",
    "line": "3294",
    "routine": "errorMissingColumn", 
    "sql": "SELECT \"endpointEndpointTagsEndpointTag\".\"id\", \"endpointEndpointTagsEndpointTag\".\"endpointId\" AS \"endpointIdKey\", \"endpointEndpointTagsEndpointTag\".\"endpointTagId\" AS \"endpointTagIdKey\", \"endpointEndpointTagsEndpointTag\".\"endpoint_tag_id\" AS \"endpointTagId\", \"endpointEndpointTagsEndpointTag\".\"endpoint_id\" AS \"endpointId\", \"endpoint\".\"id\" AS \"endpoint.id\", \"endpoint\".\"domain\" AS \"endpoint.domain\", \"endpointTag\".\"id\" AS \"endpointTag.id\", \"endpointTag\".\"name\" AS \"endpointTag.name\" FROM \"public\".\"endpoint_endpoint_tags_endpoint_tag\" AS \"endpointEndpointTagsEndpointTag\" LEFT OUTER JOIN \"public\".\"endpoint\" AS \"endpoint\" ON \"endpointEndpointTagsEndpointTag\".\"endpointId\" = \"endpoint\".\"id\" LEFT OUTER JOIN \"public\".\"endpoint_tag\" AS \"endpointTag\" ON \"endpointEndpointTagsEndpointTag\".\"endpointTagId\" = \"endpointTag\".\"id\" ORDER BY \"endpointEndpointTagsEndpointTag\".\"id\" DESC LIMIT 15 OFFSET 0;"
  },
  "original": {
    "length": 146,
    "name": "error",
    "severity": "ERROR",  
    "code": "42703",
    "position": "188",    
    "file": "parse_relation.c",
    "line": "3294",
    "routine": "errorMissingColumn", 
    "sql": "SELECT \"endpointEndpointTagsEndpointTag\".\"id\", \"endpointEndpointTagsEndpointTag\".\"endpointId\" AS \"endpointIdKey\", \"endpointEndpointTagsEndpointTag\".\"endpointTagId\" AS \"endpointTagIdKey\", \"endpointEndpointTagsEndpointTag\".\"endpoint_tag_id\" AS \"endpointTagId\", \"endpointEndpointTagsEndpointTag\".\"endpoint_id\" AS \"endpointId\", \"endpoint\".\"id\" AS \"endpoint.id\", \"endpoint\".\"domain\" AS \"endpoint.domain\", \"endpointTag\".\"id\" AS \"endpointTag.id\", \"endpointTag\".\"name\" AS \"endpointTag.name\" FROM \"public\".\"endpoint_endpoint_tags_endpoint_tag\" AS \"endpointEndpointTagsEndpointTag\" LEFT OUTER JOIN \"public\".\"endpoint\" AS \"endpoint\" ON \"endpointEndpointTagsEndpointTag\".\"endpointId\" = \"endpoint\".\"id\" LEFT OUTER JOIN \"public\".\"endpoint_tag\" AS \"endpointTag\" ON \"endpointEndpointTagsEndpointTag\".\"endpointTagId\" = \"endpointTag\".\"id\" ORDER BY \"endpointEndpointTagsEndpointTag\".\"id\" DESC LIMIT 15 OFFSET 0;"
  },
  "sql": "SELECT \"endpointEndpointTagsEndpointTag\".\"id\", \"endpointEndpointTagsEndpointTag\".\"endpointId\" AS \"endpointIdKey\", \"endpointEndpointTagsEndpointTag\".\"endpointTagId\" AS \"endpointTagIdKey\", \"endpointEndpointTagsEndpointTag\".\"endpoint_tag_id\" AS \"endpointTagId\", \"endpointEndpointTagsEndpointTag\".\"endpoint_id\" AS \"endpointId\", \"endpoint\".\"id\" AS \"endpoint.id\", \"endpoint\".\"domain\" AS \"endpoint.domain\", \"endpointTag\".\"id\" AS \"endpointTag.id\", \"endpointTag\".\"name\" AS \"endpointTag.name\" FROM \"public\".\"endpoint_endpoint_tags_endpoint_tag\" AS \"endpointEndpointTagsEndpointTag\" LEFT OUTER JOIN \"public\".\"endpoint\" AS \"endpoint\" ON \"endpointEndpointTagsEndpointTag\".\"endpointId\" = \"endpoint\".\"id\" LEFT OUTER JOIN \"public\".\"endpoint_tag\" AS \"endpointTag\" ON \"endpointEndpointTagsEndpointTag\".\"endpointTagId\" = \"endpointTag\".\"id\" ORDER BY \"endpointEndpointTagsEndpointTag\".\"id\" DESC LIMIT 15 OFFSET 0;",
  "stack": "SequelizeDatabaseError: column endpointEndpointTagsEndpointTag.endpoint_tag_id does not exist\n    at Query.formatError (/usr/src/app/node_modules/sequelize/lib/dialects/postgres/query.js:354:16)\n    at query.catch.err (/usr/src/app/node_modules/sequelize/lib/dialects/postgres/query.js:71:18)\n    at tryCatcher (/usr/src/app/node_modules/sequelize/node_modules/bluebird/js/release/util.js:16:23)\n    at Promise._settlePromiseFromHandler (/usr/src/app/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:547:31)\n    at Promise._settlePromise (/usr/src/app/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:604:18)\n    at Promise._settlePromise0 (/usr/src/app/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:649:10)\n    at Promise._settlePromises (/usr/src/app/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:725:18)\n    at _drainQueueStep (/usr/src/app/node_modules/sequelize/node_modules/bluebird/js/release/async.js:93:12)\n    at _drainQueue (/usr/src/app/node_modules/sequelize/node_modules/bluebird/js/release/async.js:86:9)\n    at Async._drainQueues (/usr/src/app/node_modules/sequelize/node_modules/bluebird/js/release/async.js:102:5)\n    at Immediate.Async.drainQueues [as _onImmediate] (/usr/src/app/node_modules/sequelize/node_modules/bluebird/js/release/async.js:15:14)\n    at runCallback (timers.js:705:18)\n    at tryOnImmediate (timers.js:676:5)\n    at processImmediate (timers.js:658:5)"

Thank you a lot for your help.

Okay I got it, just had to put underscored to false in the generated model. I will not delete the post so someone can get help later :slightly_smiling_face: