SierraSoftworks / Iridium

A high performance MongoDB ORM for Node.js
http://sierrasoftworks.github.io/Iridium/
Other
569 stars 25 forks source link

How to map properly different property name in the code and in the db #108

Open fyn-dev opened 6 years ago

fyn-dev commented 6 years ago

Let's say in my document I have this structure:

{
    "_id" : ObjectId("5a4212d22457b44fec8b06d3"),
    "house_name" : "good one"
}

but in my code I want to map like this:

@Collection('houses')
export class House extends Instance<HouseDocument, House> implements HouseDocument {
    @ObjectID
    // tslint:disable-next-line:variable-name
    public _id: string;
    @Property('house_name')
    public houseName: string;
}

problem is when I try for example insert new document is saving as houseName property but not as house_name. How to insert data using house_name but keep in the code houseName property?

TypeScript version 2.6.2 Iridium version 8.0.0-alpha.5

notheotherben commented 6 years ago

First thing's first, the @Property decorator's first argument is the type of the property, so in your case it should be @Property(String) to ensure that things are validated correctly.

As for renaming properties, you're going to want to use the @Transform decorator for that. Since you're renaming a top-level property, you'll need to transform the object one level above it (the document). Let's quickly build a decorator which leverages the @Transform decorator to accomplish this for any given field on your document.

export function RenameProperties(map: { [codeField: string]: string }) {
  return Iridium.Transform(db => {
    for (const codeField in map) {
      const dbField = map[codeField]
      db[codeField] = db[dbField]
      delete db[dbField]
    }
    return db
  }, code => {
    for (const codeField in map) {
      const dbField = map[codeField]
      code[dbField] = code[codeField]
      delete code[codeField]
    }
    return code
 })
}

This would allow you to rename your property like this:

@Collection("houses")
@RenameProperties({
  houseName: "house_name"
})
export class House extends Instance<HouseDocument, House> implements HouseDocument {
  @ObjectID
  public _id: string;

  @Property(String)
  public houseName: string;
}

Please let me know if you run into any problems with that approach.

Regards, Benjamin

fyn-dev commented 6 years ago

@SPARTAN563 thanks! This may work, but it looks like no standard decorator like @RenameProperty/@MapProperty in the library itself.

fyn-dev commented 6 years ago

@SPARTAN563 after some tested this code doesn't work properly it throw this error

Expected houseName to be a defined non-null value but got undefined instead

If I set @Property(String, false) then no issue, but how to apply @Property decorator to field which will be renamed in db to another one?

notheotherben commented 6 years ago

I'm sorry about that @fyn-dev, I had completely forgotten about that little quirk.

The issue is caused by the way that the schema is used to build the proxy instance (which forwards requests for houseName to the underlying document.houseName while doing transforms on the field). As a result, you end up needing both fields to be present in the schema (with the code field set to not-required) for this to work. All round, not a great experience.

I've instead opted to expand the underlying Iridium Instance type to support renaming natively through a new Instance.renames field (and a corresponding @Iridium.Rename() decorator).

You should now be able to simply do the following (without any extra code) using v8.0.0-alpha.10.

@Collection("houses")
export class House extends Instance<HouseDocument, House> implements HouseDocument {
  @ObjectID
  public _id: string;

  @Property(String)
  @Rename("house_name")
  public houseName: string;
}

Please let me know if you run into any issues with it, and sorry about the slow turnaround time on this (holidays got in the way).

fyn-dev commented 6 years ago

@SPARTAN563 Thanks a lot really nice feature to have!