Objection.js: Custom Fields

Created on 15 Jul 2016  路  18Comments  路  Source: Vincit/objection.js

If I have a Customer table with the following columns: customerId, firstName and lastName, and my model is defined as:

const Model = require('objection').Model;

class Customer extends Model {
  static get tableName() {
    return 'Customer';
  }
};

module.exports = Customer;

If I do:

  getCustomers (req, res) {
    Customer
    .query()
    .then(function (customers) {
      res(customer);
    })
    .catch(function (err) {
      console.log(err);
    });
  }

I get an object with the data as per the table.

Now, if I want to create a dynamic/calculated field fullName on the model, which is derived as firstName + ' ' + lastName, how would I add it to my model, so that it is included in the .query() results?

enhancement

All 18 comments

Try defining a class method, as so:

class Customer extends Model {
  fullname () {
    return `${this.firstName} ${this.lastName}`
  }
}

When you do a .query() that fetches, objection.js will actually return an instantiated model.

Customer
  .query()
  .first()
  .then(customer => {
    console.log(customer instanceof Customer)
    // --> true, and so it's an instance of class Customer
  })

Edit Related docs (which are excellent btw) at http://vincit.github.io/objection.js/#models

If I define it that way, and call .query(), is it supposed to auto include the fullName with the rest of the fields from the db?

I'm not sure I follow. fullName would be a virtual function which returns a computed result.

If you want to use it as a getter instead, just define it as such, e.g:

class Customer extends Model {
  get fullName () {
    return `${this.firstName} ${this.lastName}`
  }
}

I think I'm missing something related to ES6 classes.

I've declared it now as a getter, and when I console.log(customer[0]) it only includes the firstName and lastName, but console.log(customer[0].fullName) does display the value of fullName.

The getter is a non-enumerable and doesn't get returned like that. One option is to create the property in $parseDatabaseJson hook, but make sure you handle the case that either firstaName or lastName is missing.

I could look into adding a feature that also returns the ES6 getter properties. I think it would be a good addition.

Oh you're right, I'm sorry. Didn't realise you were serializing the model, thought you just wanted to access it. My bad.

@koskimas would it also be possible to consider an option to hide specified fields by default, e.g. passwords.

Thanks, @koskimas. It looks like $parseDatabseJson will do it. Last question: How do I access data from the related models inside this hook?

You can't. Maybe you can move your code to $afterGet? The relations are present there.

Doesn't seem as if the relations can be accessed in $afterGet.

Here's my model definition:

const Model = require('objection').Model;

class EventLink extends Model {
  static get tableName() {
    return 'EventLink';
  }

  $afterGet(queryContext) {
    console.log(this.linkType);
  }

  static get relationMappings() {
    return {
      linkType: {
        relation: Model.BelongsToOneRelation,
        modelClass: __dirname + '/linktype',
        join: {
          from: 'EventLink.linkTypeId',
          to: 'LinkType.linkTypeId'
        }
      }
    }
  }
};

It logs undefined to console.

Can you paste the query here also?

Here's the controller:

class EventController {
  getEvents (req, res) {
    Event
    .query()
    .eager('[eventType,organisation.brand,agent,teamLeader,interactionCategory,eventLinks.[linkType.brand],assessments.[assessmentTimeline.[assessmentStatus,actionedBy,assignedTo],assessmentAnswers.[answerType],user,questionnaire.[organisation.brand,questionnaireScale,questionnaireSections.[questions]]]]')
    .then(events => {
      res(events);
    })
    .catch(err => {
      console.log(err);
    });
  }
}

Wow, impressive eager query :D I don't know what to tell you. That should work. I even have a test case for that.

Thanks! Yeah, eager has given me what NO other ORM could, i.e deep nested models.

For now, I'm moving the logic into the .then() of the controller handler.

It would, however, be super useful if you could add the getters into the query results, as you mentioned earlier.

Thanks again for all the help!

Actually there is a bug and afterGet doesn't have the relations in that case. I'll fix that.

How about this implementation for the computed attributes:

class Person extends Model {
  static computedAttributes = ['foo', 'bar']

  get foo() {
    return `${this.spam} ${this.eggs}`; 
  }

  bar() {
    return this.baz * 42;
  }

  get thisWillNotBeSerialized() {
    return 10 * this.bar();
  }
}

So you just list the methods/getters that should be included in toJSON?

@casalot The $afterGet method now works in 0.5.4.

@koskimas, I have just tested it. Amazing feature! I have also already implemented some virtual attributes (from release 0.5.5). All data transformations now gets done within the model, leaving the controllers lean, with no code repetition.

Great job! Thanks for the very quick turn around time!

Was this page helpful?
0 / 5 - 0 ratings