Sails JS: Pass parameter to toJSON - node.js

The sailsjs model attribute method toJSON is very handy for processing model attributes before sending back to client. However, this method does not take any parameters and I cannot pass additional information to the method that can be used for processing attributes.
In my particular case, I need to check if I should send an attribute by comparing logged in user id (req.user.id). E.g.
toJSON: function () {
var obj = this.toObject();
var result = {};
if (req.user.id === 123) { // How can I access req from inside toJSON?
// do something here ...
}
}
I could not find a way to access the req parameter from inside toJSON. Any suggestions?

Sails does purposely not expose the req attribute as a global so there is no way to access it from within toJSON:
How to access session variables outside controller
Depending on the functionality you are trying to achieve you might be able to use policies instead: How to access request object in sails js lifecycle callbacks
In a similar case, I found myself using a workaround instead. You can add the id of the logged in user in your controller to the model:
YourModel.findOne().exec(function (err, yourModelObject) {
yourModelObject.loggedInUserId = req.user.id;
res.send(yourModelObject);
});
It is then accessible in your toJSON method and you could use it like this:
toJSON: function () {
var obj = this.toObject();
var result = {};
if (obj.loggedInUserId === 123) {
// do something here ...
}
delete obj.loggedInUserId;
return obj;
}
It is a little bit of a hack, but it worked for me. Be careful that a user can't somehow set the same value on the model (for example by using schema: true in your model) in question, to prevent security holes in your code.

Related

Is there a way to send function context to model in nodejs mongoose?

I need to implement pre hooks in mongoose that are based on the id of the user that does the request.
I can't access the initial function context (userId, permissions, etc) and use it in mongoose pre hooks.
let writePermissionCondition = async function(next) {
let ctx = getCurrentContext();
next();
}
As I can't send the current context in the mongoose model, I can't access the userId that does the actual request.
Edit: a little more info on the architecture and what I'm trying to achieve
1) The user makes a request to the service (listening to events. ex: user/create, user/delete, user/find, etc)
2) The event calls a CRUD function
3) The Model used in the CRUD has ReadPermission and WritePermission middlewares (hooks)
4) The ReadPermission and WritePermission verifies the user that makes the request if, in fact, has permissions to read or write. This is the step where I need to know what user tried to access that Model.
I might be wrong, and the permissions should be implemented in the controller and not in the model.
Set it to global var and use it on mongoose operation
let writePermissionCondition = async function(next) {
let ctx = getCurrentContext();
global.ctx = ctx
next();
}
Now you can access global.ctx anywhere in the application.
Mongoose provides many useful tools for solving complex situations like your question.
In your case you can user mongoose virtuals:
Virtuals are document properties that you can get and set but that do not get persisted to MongoDB.
You can start trying with a simple implementation like this:
yourSchema.virtual('context').
get(function() { return this.__context; }).
set(function(ctx) {
this.__context = ctx;
});
In this way you can call getCurrentContext(); where the context is available (e.g. during express req/res flow) and save it the model you've retrieved.
Example flow (pseudocode):
const user = User.get(req.id)
const context = getCurrentContext();
user.context = context;
//... save/update...
Now, when the user will be saved/updated, and your pre hooks are called, your validation code can access the context variable attached to retrieve your data.

how to save multiple data with object params request, at once in sails js

Currently, I'm developing an API with sails js + mongoDB for an mobile app. I want to save multiple data that send from mobile app and I catch it via req parameters (as object) in my controller's function.
Some parts of my controller code look like this :
var contactData = req.allParams().Contact;
looping.. {
Contact.create(contactData[index]).exec(function addContact(err,createdContact){ });
}
Could you tell me how the best way to implement looping for save that multiple contact data to the database?
Regards, argi danu.
Okay, So if you are aware of the 'name' attributes for the request parameters, then you can simply use the create method as -
var contactData = req.allParams();
var name = contactData.name // attribute name
var number = contactData.number // attribute name
And after that simply call the create method
Contact.create({name : name, number :number}).exec(functionaddContact(err,createdContact){
// DO Something
});
But if you strictly want to use for loops to get all the data from request parameters, then Consider the following scenario -
Convert the combination of req parameters into a JSON object by appending the contactData into [ ] as
var json = '[' + contactData +']';
and then using the following code get the parameter values from the created JSON object as follows
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
if(typeof obj[property] === 'string') {
console.log(property); // provides the property name
console.log(obj[property]); // provides the value of the property
}
}
May be this helps. It worked with SQL databases

iron-router template data in rendered callback

I'm passing a data object to a template with iron-router but would like to access the data within the Template.name.rendered = function() { ... } callback.
From Meteor data-context with iron-router I've tried UI.getData() but receive an error There is no current view. Using this.data returns null.
How can I get access to the data object passed to the template from the rendered callback?
You were on the right track with looking for the data context, but this is actually how you get access to it:
var ctx = Template.currentData();
Also, I believe Template.x.rendered is about to be deprecated, so if that doesn't work, try using
Template.x.onRendered(function() {
var ctx = Template.currentData();
console.log(ctx);
});

Backbone.js - get view, model or collection property in ajax 'beforeSend'

I am using Backbone.js in my node app. I am calling various ajax call from views, models and colllection. I have created my custom property e.g. "myName" in every view, model and collection and assigned with unique name for each. Now I want this "myName" property in ajax "beforeSend", So I should know from which view or model, this ajax is called. Is there any option to do this?
beforeSend callback of $.ajax() receives 2 arguments:
beforeSend
Type: Function( jqXHR jqXHR, PlainObject settings )
As you can see 2nd argument is settings which receives all options passed to fetch method of 'Backbone.Collection' or Backbone.Model:
Example:
Your ajax setup:
$.ajaxSetup({
beforeSend: function (xhr, options) {
console.log(options.testVar); // Will be "Hello" when collection fetched
}
});
Place when you doing fetch() or somehow interacting with server:
yourCustomCollectionOrModel.fetch({testVar: "Hello"}).done(function(){
// bla bla
})
So whenever yourCustomCollectionOrModel has fetched testVar will be passed to the beforeSend's options argument.
Note: Avoid globals if you can solve the issue in more preferred way.
You can go event better if you don't want to repeat the same any time you fetching collection or model.
Just rewrite the fetch() method, and add collection/model specific flag to the options.
Example
var TestCollection = Backbone.Collection.extend({
url: 'your/api/path',
fetch: function (options) {
options = options || {};
options.testVar = 'Hello';
return this.constructor.__super__.fetch.call(this, options);
}
});
Update:
Another and maybe the shortest way to achieve the same behavior, is to wrap Backbone.sync like this:
var oldSync = Backbone.sync;
Backbone.sync = function(method, model, options) {
options = options || {};
options.modelContext = model;
return oldSync.call(Backbone, method, model, options);
}
In this way you don't need to rewrite fetch, or manually pass options to fetch() method.
And the in beforeSend callback of $.ajax:
$.ajaxSetup({
beforeSend: function (xhr, options) {
console.log(options.modelContext); // this will be the model's or collection's instance
}
});
Hope this helps!
// for example
$.ajaxSetup({
beforeSend: function (xhr) {
xhr.setRequestHeader('viewName', yourGlobalVariable /* or session storage */);
}
});
before each ajax call (or model/collection fetch, save etc) store in yourGlobalVariable name of your view.

Backbone.js/express.js parameters for model.save()

I'm using Backbone.js on the client and node.js on the backend, and I'm having a bit of trouble doing a 'limited' model save, as explained here : http://backbonejs.org/#Model-save
As in the example, if I do
book.save({author: "Teddy"});
how do I access the parameters of the save using express.js, i.e. the fact that I only want to save to the 'author' field? I've tried the following
req.body -> gives ALL parameters of the model being saved, I want only the 'author' field
req.query -> empty
Any help much appreciated!
As stated in Model.save documentation:
save model.save([attributes], [options])
[...] The attributes hash (as in set) should contain the attributes you'd like to change —
keys that aren't mentioned won't be altered — but, a complete
representation of the resource will be sent to the server.
You can however override the save method and provide a data attribute via the options which will be sent to the server instead of the full model representation. For example, this will only save the attributes passed to the method :
var M = Backbone.Model.extend({
save: function (attrs, options) {
options || (options = {});
options.contentType = 'application/json';
options.data = JSON.stringify(attrs);
Backbone.Model.prototype.save.call(this, attrs, options);
}
});
And a Fiddle http://jsfiddle.net/dLFgD/
As #mikebridge noted in the comments, this behavior can now be obtained by passing an attrs option. So either use
book.save(null, {
attrs: {author: "Teddy"}
});
or keep the override
var M = Backbone.Model.extend({
url: '/echo/json/',
save: function(attrs, options) {
options || (options = {});
options.attrs = attrs;
Backbone.Model.prototype.save.call(this, attrs, options);
}
});
http://jsfiddle.net/nikoshr/dLFgD/7/
You could also send a PATCH request if you're using a Backbone version that supports it (>=0.9.9) and your server understands that verb, as explained in #pkyeck's answer
with the current version of Backbone (1.1.0) you could also do a PATCH which only sends the changed attributes to the server.
If instead, you'd only like the changed attributes to be sent to the server, call model.save(attrs, {patch: true}). You'll get an HTTP PATCH request to the server with just the passed-in attributes.
taken from here: http://backbonejs.org/#Model-save
in your case this would be:
book.save("author", "Teddy", {patch: true});
The best way I have found to access POST parameters is to use the bodyParser middleware. This is referenced at this question: How to retrieve POST query parameters?
/* On startup, register the bodyParser middleware */
app.use(express.bodyParser());
/* Then, during route execution, each parameter can be accessed with req.param */
app.post('/author/:id', function(req, res) {
var author = req.param('author', null); // Second parameter is default.
...
});

Resources