Something should be wrong with applying of "getter" methods in model-builder.js
Asked Answered
H

2

-1

I am trying to set-up some basic scenarios and use LoopBack to find out its actual level of flexibility and usability.

One of them is the necessity to modify the value of some attributes in resulted JSON objetcs during processing the source data from a db (e.g. MySQL).

I am using the following versions:

strong-cli v2.5.5 (node v0.10.29) node-inspector v0.7.4 strong-build v0.1.0 strong-cluster-control v0.4.0 strong-registry v1.1.0 strong-supervisor v0.2.3 (strong-agent v0.4.9, strong-cluster-control v0.4.0) loopback-datasource-juggler v1.6.2 loopback-connector-mysql v1.4.1

I tried everything but it looks that "getter" method is applied by the way I don't understand or there is a bug.

To describe the problem I used a simple table "city" (in MySQL db):

CREATE TABLE `city` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `countryId` bigint(20) NOT NULL,
  `name` varchar(100) NOT NULL,
  `comment` varchar(255) DEFAULT NULL,
  `enabled` char(1) NOT NULL DEFAULT 'Y',
  PRIMARY KEY (`id`),
  UNIQUE KEY `id_UNIQUE` (`id`)
)

and filled some simple data.

To define the model in LoopBack I used the file "city.js" located in "models" directory:

"use strict";

var loopback = require('loopback');
var app = require('../app.js');

var properties = {
    id: {type: "number", id: true},
    countryId: {type: "number"},
    name: {type: "string"},
    comment: {type: "string"},
    enabled: {type: "string"}
};

var options = {
    acls: [
        {
            accessType: "*",
            permission: "ALLOW",
            principalType: "ROLE",
            principalId: "$everyone"
        }
    ]
};

var City = app.model("city", {
    properties: properties, 
    options: options,
    public: true,
    dataSource: "mysql",
    plural: "cities"
});

City.getter["enabled"] = function(v) {
    console.log("Getter is called: ", v);
    return 'Q'
};

As you can see the "getter" method is defined on "city" model using "City" object.

When I try to run LoopBack and then send a request through StrongLoop API Explorer:

http://localhost:3000/api/cities/1

the console looks like (icluding info for DEBUG=loopback:connector:*):

supervisor running without clustering (unsupervised)
loopback:connector:mysql Settings: {"connector":"loopback-connector-mysql","host":...
connect.multipart() will be removed in connect 3.0
visit https://github.com/senchalabs/connect/wiki/Connect-3.0 for alternatives
connect.limit() will be removed in connect 3.0
Getter is called:  undefined
Getter is called:  undefined
2014-07-12T12:26:41.978Z pid:12180 worker:supervisor INFO strong-agent not profiling, ...
2014-07-12T12:26:41.981Z pid:12180 worker:supervisor Generate configuration with:
2014-07-12T12:26:41.983Z pid:12180 worker:supervisor     npm install -g strong-cli
2014-07-12T12:26:41.983Z pid:12180 worker:supervisor     slc strongops
2014-07-12T12:26:41.983Z pid:12180 worker:supervisor See http://docs.strongloop.com/...
Browse your REST API at ...
LoopBack server listening @ ...
Getter is called:  undefined
  loopback:connector:mysql SQL: SELECT * FROM `city` WHERE `id` = 1 LIMIT 1 +8s
  loopback:connector:mysql Data:  +9ms [ { id: 1,
    countryId: 1,
    name: 'Brno',
    comment: 'The second largest city of its country',
    enabled: 'Y' } ]
Getter is called:  undefined
GET /api/cities/1 304 44ms

and the result looks like:

{
  "id": 1,
  "countryId": 1,
  "name": "Brno",
  "comment": "The second largest city of its country",
  "enabled": "Q"
}

As you can see the "enabled" attribute is finally changed by "getter" method but:

  • 'getter' method is also called 2x during initialization of LoopBack
  • 'getter' method is also called 1x immediately before MySQL db is queried
  • console.log(v) inside 'getter' method always returns 'undefined'

The source code of 'model-builder.js' in loopback-datasource-juggler (line 364)

Object.defineProperty(ModelClass.prototype, propertyName, {
  get: function () {
    if (ModelClass.getter[propertyName]) {
      return ModelClass.getter[propertyName].call(this); // Try getter first
    } else {
      return this.__data && this.__data[propertyName]; // Try __data
    }
  },

is an explanation why. The 'call' method has only one parameter (this) which represent 'this' object in the called function and there is no second parameter which would represent the parameter 'v' in the called function (related to the answer of Raymond Feng on How do I create getter and setter overrides?).

The problem is also that 'this' inside 'getter' method always represents an object that include all properties of the model:

City.getter["enabled"] = function(v) {
    console.log("Getter is called: ", this);
    return 'Q'
};

and the message from console then:

Getter is called: { id: 1, countryId: 1, name: 'Brno', comment: 'The second largest city of its country', enabled: 'Y' }

Can you explain me the idea of the current implementation of "getter" methods please?

Thanks a lot.

Milos Lapis MLC

UPDATE

Thanks a lot Raymond for your answer.

At the very first I supposed that but when I used something like:

City.getter["enabled"] = function() {
    return this.enabled + "x"
};

the Node crashed immediately when I requested:

localhost:3000/api/cities/1

with an error: RangeError: Maximum call stack size exceeded

That is why I thought that your implementation is a bit different. Is there something wrong?

What exactly should I use to add 'x' to the actual value retrieved from db?

Thanks.

Milos Lapis MLC

Helmut answered 12/7, 2014 at 13:32 Comment(0)
F
1

In your example code, you are recursively calling the getter you have defined.

City.getter["enabled"] = function() {
  // this.enabled calls City.getter["enabled"]
  return this.enabled + "x"
};

The value retrieved from DB is stored in __data property. Here is the corrected getter function:

City.getter["enabled"] = function() {
  return this.__data.enabled + "x"
};
Frisky answered 22/8, 2014 at 7:28 Comment(0)
C
1

JavaScript getter functions are in the following format:

function() {
  // this is the object instance owning the property
}

Please note getter function doesn't take any argument and the receiver is the object instance owning the property. For example:

myModelInstance.myProperty will call the getter function with this set to myModelInstance.See more at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects.

Clarineclarinet answered 13/7, 2014 at 4:54 Comment(1)
Thanks a lot Raymond for your answer. At the very first I supposed that but when I used something like: City.getter["enabled"] = function() { return this.enabled + "x" }; the Node crashed immediately when I requested localhost:3000/api/cities/1 with an error: RangeError: Maximum call stack size exceeded That is why I thought that your implementation is a bit different. Is there something wrong? What exactly should I use to add 'x' to the actual value retrieved from db? Thanks. Milos Lapis MLCCaster
F
1

In your example code, you are recursively calling the getter you have defined.

City.getter["enabled"] = function() {
  // this.enabled calls City.getter["enabled"]
  return this.enabled + "x"
};

The value retrieved from DB is stored in __data property. Here is the corrected getter function:

City.getter["enabled"] = function() {
  return this.__data.enabled + "x"
};
Frisky answered 22/8, 2014 at 7:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.