loopbackio / loopback-next

LoopBack makes it easy to build modern API applications that require complex integrations.
https://loopback.io
Other
4.95k stars 1.07k forks source link

Navigational properties are not allowed in model data #4354

Closed dexdinesh closed 3 years ago

dexdinesh commented 4 years ago

As I had already used relations in loop back4 project now when I tried to perform any crud operation it returns 500 Error: Navigational properties are not allowed in model data (model "UserDevice" property "user_id")

My models are as follows:

User Model:

import { Entity, model, property, hasMany } from '@loopback/repository';

@model({
  settings: {
    strictObjectIDCoercion: true,
    strict: false,
  }
})
export class Users extends Entity {
  @property({
    type: 'string',
    id: true,
    generated: true,
    mongodb: { dataType: 'ObjectID' },
  })
  id?: string;

  @property({
    type: 'string',
    required: true,
  })
  full_name: string;

  @property({
    type: 'string',
    required: true,
  })
  email: string;

  @property({
    type: 'string',
    required: false,
    default: null,
  })
  password: string;

  @property({
    type: 'string',
    required: false,
    default: null,
  })
  company: string;

  @property({
    type: 'string',
    required: false,
    default: null,
  })
  biography: string;

  @property({
    type: 'string',
    required: false,
    default: null,
  })
  job_position: string;

  @property({
    type: 'string',
    required: true,
  })
  uid: string;

  @property({
    type: 'string',
    required: true,
  })
  year: string;

  @property({
    type: 'string',
    required: false,
    default: null,
  })
  profile_pic: string;

  @property({
    type: 'string',
    required: false,
    default: null,
  })
  profile_thumbnail: string;

  @property({
    type: 'string',
    required: false,
    default: null,
  })
  otp: string;

  @property({
    type: 'boolean',
    default: false,
  })
  login_status: boolean;

  @property({
    type: 'string',
    required: false,
    default: null,
  })
  tmp_password: string;

  @property({
    type: 'boolean',
    required: false,
    default: false,
  })
  receive_post_notification: boolean;

  @property({
    type: 'boolean',
    required: false,
    default: false,
  })
  receive_new_follower_notification: boolean;

  @property({
    type: 'string',
    default: null,
  })
  created_at: string;

  @property({
    type: 'string',
    default: null,
  })
  updated_at?: string;

  constructor(data?: Partial<Users>) {
    super(data);
  }
}

export interface UsersRelations {
  // describe navigational properties here
}

export type UsersWithRelations = Users & UsersRelations;

User Device Model:

import { Entity, model, property, belongsTo } from '@loopback/repository';
import { timeZone } from '../Helpers/TimeZone';
import { Users } from './users.model';

@model({
  settings: {
    strictObjectIDCoercion: true,
    strict: false,
  }
})
export class UserDevice extends Entity {
  @property({
    type: 'string',
    id: true,
    generated: true,
  })
  id?: string;
  @property({
    type: 'string',
    required: true,
  })
  device_id: string;

  @property({
    type: 'string',
  })
  device_name?: string;

  @property({
    type: 'number',
  })
  device_type: number;

  @property({
    type: 'string',
  })
  app_version: string;

  @property({
    type: 'string',
  })
  fcm_token: string;

  @property({
    type: 'number',
  })
  is_active: number;

  @property({
    type: 'string',
    default: timeZone(),
  })
  created_at?: string;

  @property({
    type: 'string',
    default: null
  })
  updated_at?: string;

  @belongsTo(() => Users)
  user_id: string;

  constructor(data?: Partial<UserDevice>) {
    super(data);
  }
}

export interface UserDeviceRelations {
  // describe navigational properties here
}

export type UserDeviceWithRelations = UserDevice & UserDeviceRelations;

Error I always receive on Create and Update operation:

Unhandled error in POST /users/login: 500 Error: Navigational properties are not allowed in model data (model "UserDevice" property "user_id") at UserDeviceRepository.ensurePersistable (/home/abc/Downloads/mohit/node-apps/rosenberg/alumniapp-api2/node_modules/@loopback/repository/src/repositories/legacy-juggler-bridge.ts:585:15) at UserDeviceRepository.entityToData (/home/abc/Downloads/mohit/node-apps/rosenberg/alumniapp-api2/node_modules/@loopback/repository/src/repositories/legacy-juggler-bridge.ts:554:17) at UserDeviceRepository.updateAll (/home/abc/Downloads/mohit/node-apps/rosenberg/alumniapp-api2/node_modules/@loopback/repository/src/repositories/legacy-juggler-bridge.ts:434:38) at UsersController.login (/home/abc/Downloads/mohit/node-apps/rosenberg/alumniapp-api2/src/controllers/users.controller.ts:169:41) at processTicksAndRejections (internal/process/task_queues.js:93:5) at MySequence.handle (/home/abc/Downloads/mohit/node-apps/rosenberg/alumniapp-api2/src/sequence.ts:44:22) at HttpHandler._handleRequest (/home/abc/Downloads/mohit/node-apps/rosenberg/alumniapp-api2/node_modules/@loopback/rest/src/http-handler.ts:78:5)

LonguCodes commented 4 years ago

This is desired behaviour, discussed in #3439. I have no idea why they decided on that and I have another question. How should we update relation properties? @bajtos

achrinza commented 4 years ago

This is an intended feature to prevent errors such as invalid foreign keys from being keyed into the database. AFAIK, updating navigational properties requires sending another HTTP request directly to the controller that you'd like to modify the navigational property of.

Hence,

  1. Send a request to the "parent" model controller with the registered inclusionResolver.
  2. Send a request to the "child" model controller that you'd like to modify the navigational property by filtering by the child model's ID (and potentially the parent model's ID if the child model is a weak entity).

See:

ludohenin commented 4 years ago

@dexdinesh this is because your not using the buildin default property format for your FK in

export class UserDevice extends Entity {
  // ...
  @belongsTo(() => Users)
  user_id: string; // <-- expected to be userId
}

otherwise you have to configure your relation

export class UserDevice extends Entity {
  // ...
  @belongsTo(() => Users, {keyTo: 'id', keyFrom: 'user_id'})
  user_id: string;
}

But I can't get this working either. @bajtos Has the relation to configured in the repository (accessor) everytime a relation is declared in a model ? I'm getting the same error since I updated LB dependencies "@loopback/repository": "^1.14.0" to "@loopback/repository": "^1.19.1" also updated to lastest release with any more success

ludohenin commented 4 years ago

when I think I'm solving stuffs, I'm getting this error

"statusCode":500,
"name":"TypeError",
"message":"Cannot read property 'type' of undefined","stack":"TypeError: Cannot read property 'type' of undefined\n    at Object.resolveHasManyMetadata (/home/ludohen/github/ta-api-core-2/node_modules/@loopback/repository/dist/relations/has-many/has-many.helpers.js:22:22) [...]

EDIT:

found the reason for that, when it's not following the naming convention (using the id), all options have to be set (keyTo, KeyFrom and name)

fabianorodrigo commented 4 years ago

This is an intended feature to prevent errors such as invalid foreign keys from being keyed into the database. AFAIK, updating navigational properties requires sending another HTTP request directly to the controller that you'd like to modify the navigational property of.

Hence,

  1. Send a request to the "parent" model controller with the registered inclusionResolver.
  2. Send a request to the "child" model controller that you'd like to modify the navigational property by filtering by the child model's ID (and potentially the parent model's ID if the child model is a weak entity).

See:

@achrinza, what is the recommendation if I need a atomic transaciton? How is the community handling this use case?

achrinza commented 4 years ago

@achrinza, what is the recommendation if I need a atomic transaciton? How is the community handling this use case?

@fabianorodrigo Common LoopBack 4 SQL connectors support database-level transaction isolation. Unfortunately I don’t have a concrete example to share right now. Hope this helps!

fabianorodrigo commented 4 years ago

@achrinza, what is the recommendation if I need a atomic transaciton? How is the community handling this use case?

@fabianorodrigo Common LoopBack 4 SQL connectors support database-level transaction isolation. Hope this helps!

Sorry @achrinza, but when you suggest two requests as a workaround to the problem "Navigational properties are not allowed in model", one to the parent model and another to the child, this requests are made by the client side, right? The transactions supported by connectors are just applicable if I received a request with all the data (parent and child) and then open transaction, insert in the parent, insert in the child and commit transaction. My doubt is exactly that: How can I preserv atomicity with different requests? How is the community dealing with this chalenge?

achrinza commented 4 years ago

Sorry for the late reply; this issue slipped through my radar.

The atomicity can be handled at the server side by making 2 repository calls within a single request. This does mean a bit of extra logic in the controller, but this ensures atomicity.

The issue of implementing navigational properties is that LoopBack 4 does not have first-class support for database-level foreign key constraints. This enables cross-database relations, though requires LoopBack 4 to re-implement certain database logic. Some connectors such as PostgreSQL support foreign key definitions in @model, but not all.