TiBianMod / vuex-orm-decorators

Decorator Syntax for Vuex ORM for better type safety.
MIT License
29 stars 11 forks source link

Cannot read property 'localKey' of undefined #6

Closed bestickley closed 2 years ago

bestickley commented 4 years ago

I recently switched to using vuex-orm-decorators from the normal way. Now, I'm getting a Cannot read property 'localKey' of undefined. Any ideas? I debugged the setup and thought this screenshot below might be helpful. It shows some of the classes not being imported correctly. This might not be a vuex-orm-decorators problem but do you have any suggestions for why this might be happening?

Stack Trace:

vuex-orm.esm.js:2592 Uncaught TypeError: Cannot read property 'localKey' of undefined
    at Function.Model.belongsTo (vuex-orm.esm.js:2592)
    at BelongsToField (attributes.js:79)
    at eval (appUser.ts:20)
    at Module../src/store/models/appUser.ts (app.js:1608)
    at __webpack_require__ (app.js:854)
    at fn (app.js:151)
    at eval (role.ts:9)
    at Module../src/store/models/role.ts (app.js:1656)
    at __webpack_require__ (app.js:854)
    at fn (app.js:151)

appUser.ts

import { Model } from "@vuex-orm/core";
import {
  AttrField,
  BelongsToField,
  BelongsToManyField,
  OrmModel,
  PrimaryKey,
} from "vuex-orm-decorators";
import { Role } from "./role";
import { AppUserRole } from "./appUserRole";
import { App } from "./app";
import { User } from "./user";

@OrmModel("appUsers")
export class AppUser extends Model {
  @PrimaryKey()
  @AttrField(null)
  id!: number;
  @BelongsToField(App, "appId")
  app!: App;
  @AttrField(null)
  appId!: number;
  @AttrField(null)
  createdDate!: Date;
  @BelongsToManyField(Role, AppUserRole, "appUserId", "roleId")
  roles!: Role[];
  @AttrField(null)
  updatedDate!: Date;
  @BelongsToField(User, "userId")
  user!: User;
  @AttrField(null)
  userId!: number;
}

app.ts

import { Model } from "@vuex-orm/core";
import {
  AttrField,
  HasManyField,
  OrmModel,
  PrimaryKey,
} from "vuex-orm-decorators";
import { AppRequest } from "./appRequest";
import { AppUser } from "./appUser";
import { Role } from "./role";

@OrmModel("apps")
export class App extends Model {
  @PrimaryKey()
  @AttrField(null)
  id!: number;
  @HasManyField(AppRequest, "appId")
  appRequests!: AppRequest[];
  @HasManyField(AppUser, "appId")
  appUsers!: AppUser[];
  @AttrField("")
  description!: string;
  @AttrField("")
  longName!: string;
  @HasManyField(AppUser, "appId")
  roles!: Role[];
  @AttrField("")
  shortName!: string;
}

LocalKeyError

Scotley commented 4 years ago

Without more of the code its hard to truly say, but I am happy to offer some ideas to look into. I have noticed in some situations that the order that the imports are in can matter. Whether caused by this or not, are you sure that App actually exists at this point?

If you have created your appUser model in the constructor of the App for example, you can end up relying on a reference to an object that is still being constructed, and therefore still undefined.

Keen to know if you find the solution.

bestickley commented 4 years ago

@Scotley, thank you for your response. I reached the conclusion that it was a circular dependency and have switched back to annotating the normal Vuex ORM classes with types as I don't have the extra time right now. I'll try and revisit this in the future.

To answer your question, I'm not sure if App actually exists at this point. I guess that's what I need to figure out.

maartenpaauw commented 4 years ago

I'm facing the same problem. As well on the BelongsTo method.

Cannot read property 'localKey' of undefined at Function.Model.belongsTo

Edit: @Scotley everything else is working perfectly. Only when I try to add a BelongsTo relation via the decorator it will be undefined. When I do the belongsTo relation via the fields method it is working.

maartenpaauw commented 4 years ago

More information, the given model is directly undefined within the BelongsTo decorator. I think it is a problem with the imports as well. I'll be using the non typescript variant for now.

Scotley commented 4 years ago

Odd, there must be a quirk with the BelongsTo decorator. I don't have a current project using the decorators so cannot test unfortunately, when I was using this I didn't run into any issues though which is strange. I will reopen this as there is obviously an issue. I suspect it is to do with the order that objects are being created, something that the decorators won't currently handle as they are just simplistic wrappers.

TiBianMod commented 4 years ago

After too much research and time (Even i unit tested 100% the package), I come to conclusion that, this behavior is related to circular references and due to execution order of the models. You will come across on this behavior every time you using inverse relationships unfortunately :(

You can run this code with node to see the results node filename.js

var User = function (profile) {
    console.log('-> Profile');
    console.log(typeof profile);
};

User(Profile); // "<- Profile is undefined in this point";
// The same goes with the decorators

var Profile = function (user) {
    console.log('-> User');
    console.log(typeof user);
};

Profile(User); // "ALL GOOD";

For example, if you using the hasOne and belongsTo relationships together then you will come across of this behavior (like the above example)

read comments.


// Let's say the order of the Models is like this.. first `Profile` and second `User`
@OrmModel('profiles')
export class Profile extends Model {
@AttrField() id!: string;
@AttrField() user_id!: number;

@AttrField() age!: number;

@AttrField() sex!: string;

// Here we need the User model.
// *** but the problem is that we using 'User' class here before its declaration.
@BelongsToField(User, 'user_id')
user!: User;

}

@OrmModel('users') export class User extends Model { @AttrField() id!: string;

@AttrField() name!: string;

@HasOneField(Profile, 'user_id')
profile!: Profile;

}



I will try to find a solution or better implementation for the relationship decorators, but unfortunately this is a normal behavior.

Any help is welcome
l00k commented 3 years ago

MikroORM deals with it using expressions () => Model

cuebit commented 3 years ago

MikroORM deals with it using expressions () => Model

This is also how vuex-orm-next is now handling model references to avoid circular issues. 👍

Papooch commented 3 years ago

As for now, you can use the string name of the model instead of the class itself, but this behavior can be be pretty much fixed by changing this:

/**
 * Adds the property as a 'Belongs To' relationship field
 * @param parent The class of the parent model
 * @param foreignKey The foreign key of this model
 * @param ownerKey The key on the parent model
 */
export function BelongsToField(parent: typeof Model | string, foreignKey: string, ownerKey?: string) {
    return Field(Model.belongsTo(parent, foreignKey, ownerKey));
}

to this:

export function BelongsToField(parent: string | () => typeof Model, foreignKey: string, ownerKey?: string) {
    return Field(Model.belongsTo(
            typeof parent === 'string' ? parent : parent(),
            foreignKey,
            ownerKey
   ));
}
md-Junaid commented 2 years ago

I want to implement something like this, where my model A should have a field/property of type model B(ignores model A inside it) and model B should have field/property of type model A(but ignores model B property inside it), but I get the error that my model B has undefined property of model B itself. Please how do I avoid this circular dependency issue? I'm using vuex-orm-decorators library @TiBianMod

An example of what I'm trying:

Users.ts


import { Model } from "@vuex-orm/core";
import {
AttrField,
BelongsToManyField,
OrmModel,
PrimaryKey,
} from "vuex-orm-decorators";
import { Roles } from "./roles";
import { RoleUser } from "./roleUser";

@OrmModel("Users") export class Users extends Model {

@AttrField(null) id: number;

@AttrField("") name: string;

@BelongsToManyField(Roles, RoleUser, "user_ids", "role_ids") roles: Roles[]; } export default Users


> Roles.ts

import { Model } from "@vuex-orm/core"; import { AttrField, BelongsToManyField, OrmModel, PrimaryKey, } from "vuex-orm-decorators"; import { Users } from "./users"; import { RoleUser } from "./roleUser";

@OrmModel("Roles") export class Roles extends Model {

@AttrField(null) id: number;

@AttrField("") name: string;

@BelongsToManyField(Users, RoleUser, "role_ids", "user_ids") users: Users[]; } export default Roles


> RoleUser.ts

import { Model } from "@vuex-orm/core"; import { AttrField, OrmModel, PrimaryKey, } from "vuex-orm-decorators";

@OrmModel("RoleUser") export class RoleUser extends Model {

@PrimaryKey() static primaryKey = ["role_ids", "user_ids"]

@AttrField(null) id: number;

@AttrField(new Array()) role_ids: Array;

@AttrField(new Array()) user_ids: Array;

} export default RoleUser


> User.vue

const items = User.query().with('roles').get()


> Roles.vue

const items = Roles.query().with('users').get()



> error

` Cannot read properties of undefined`