9

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.

Travis Webb
  • 14,688
  • 7
  • 55
  • 109
Lu Roman
  • 2,220
  • 3
  • 25
  • 40

2 Answers2

9

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

slebetman
  • 109,858
  • 19
  • 140
  • 171
  • 1
    I know that setting enumerable to false hides the property from the for in, but hide it from the console.log?, wow that's new to me, so i just checked just to be sure, and it does hides it from the console in node, it stills shows up in chrome console which is weird, but since i was asking about node.js this answer is correct. – Lu Roman Aug 16 '15 at 19:11
  • @Zagen: console.log is non-standard. It may be standardized in ES7. Different implementations may implement it differently so it's no surprise that console.log in different environments behave differently. For most implementations though, the most obvious way to implement it is using for..in. – slebetman Aug 17 '15 at 02:11
4

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

Robbie
  • 18,750
  • 4
  • 41
  • 45
  • Indeed they seem to override the inspect property which would show up when inspecting it, and i'm guessing that self.toObject() sets the associated collections properties to non enumerables, as @slebetman stated to be how the properties are hidden to the node console. Thanks for your answer :) – Lu Roman Aug 16 '15 at 19:14
  • did you try passing `showJoins:true`? Did it make them show in the `console.log`? – Robbie Aug 16 '15 at 19:18
  • Nopes, let me try that later and i'll report you back :) – Lu Roman Aug 16 '15 at 19:58
  • 1
    Sry that i reply this late, I've been busy. It seems that `._model` method is an object now(in the new waterline), so i can't test passing `showJoins:true`. I'll might try something else later, but still the main question is what was the mechanism to hide something from the `console.log`. And just as a side note, i wasn't getting associations even after running .populate on the find call, and it was due to not having tableNames on model definition, since i'm using a custom made waterline loader instead of loading sails for test (Since it's up to 5 times slower). Thanks and have a good day. – Lu Roman Aug 26 '15 at 04:14