Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hidden properties to console.log or utils.inspect

I'm working with sails.js waterline orm. Now this is not particularly a sails question, but i have to place some context, so when you create a record you get back an object with the data created. If the record has other records (collections) associated, it has keys related to those in the returned object, but those keys are getters/setters, even if no data is present for those related objects.

I've simplified a few things just to expose the main point.

This is my user model:

var User = {
 attributes: 
   status: {type:'string'}
   images: {
     collection: 'Image'
   }
}

Lets assumme, i performed a create query on a User model, that has a images collection associated. The userRecord is what the query returned. if i console.log this out it shows the properties related to the model itself but not the associated records, even though the key is actually there, you can access it but is not visible to console.log or utils.inspec even when setting show hidden to true.

console.log(userRecord)

This is what gets returned

{ name: 'zaggen'}

This is what should get returned

{ name: 'zaggen',
  images: [{ 
    path: 'some-path/img.png'
  }] 
 }

And i can access the hidden property like this:

console.log(userRecord.images[0].path) 
// outputs some-path/img.png

How is this even possible?, as far as i know there is no way to hide info to the console.log in node, except maybe when the properties are defined in the __proto__ object, but in this case they are not.

After searching for this i haven't found anything and its pretty weird so i thought it could be a good question for SO. It'll help on my work process if i could console.log this info and get all the data, right now i can use lodash and call clone or defaults and i get the object as it should.

like image 348
Lu Roman Avatar asked Aug 15 '15 22:08

Lu Roman


2 Answers

as far as i know there is no way to hide info to the console.log in node, except maybe when the properties are defined in the proto object

That's no longer true in ES5. It was true in ES3.

Notice that even in the original javascript, objects and functions have hidden properties like .__proto__ or .constructor or .prototype? It was like some native javascript objects have these magic features (like how setting innerHTML can call the HTML compiler). ES5 exposes all that magic by way of Object.defineproperty.

The specific feature that hides a property from console.log() is enumerable. Setting it to false on a property makes it hidden from for..in (no need for .hasOwnProperty() anymore):

var foo = {a:1}
Object.defineProperty(foo,'b',{
    enumerable: false, // hide it from for..in
    value: 2
})

console.log(foo); // prints out {a:1}
console.log(foo.b); // prints out 2

There are other useful features such as getters and setters (allowing you to emulate properties like .innerHTML that calls a function when you write to it) and writable (allowing you to make a property read-only). See the full documentation for details: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

like image 158
slebetman Avatar answered Oct 02 '22 17:10

slebetman


Sails uses waterline which is where the model is defined. If you take a look at the source code for it, you see this:

https://github.com/balderdashy/waterline/blob/77fe3a9b9a9b12110a6ba079a84e5cd43a4369db/lib/waterline/model/lib/model.js#L57-L75

/**
 * Log output
 * @return {String} output when this model is util.inspect()ed
 * (usually with console.log())
 */

Object.defineProperty(this, 'inspect', {
  enumerable: false,
  configurable: false,
  writable: false,
  value: function() {
    var output;
    try {
      output = self.toObject();
    } catch (e) {}

    return output ? util.inspect(output) : self;
  }
});

So they override the console.log output to self.toObject(). This is one of their internal methods that does all kinds of stuff that could be responsible for the output your seeing. For example:

// Don't run toJSON on records that were not populated
if (!self.proto._properties || !self.proto._properties.joins) return;

Or:

if (!this.proto._properties.showJoins) return;

I noticed in their integration tests, they pass { showJoins: true } as the second argument when creating the model. I couldn't find anything about it in the docs, but maybe you could try that?

https://github.com/balderdashy/waterline/blob/48dc007b69a133169651aeb422fa3a61c3c6802c/test/integration/model/save.js#L150

like image 37
Robbie Avatar answered Oct 02 '22 17:10

Robbie