How to retrieve model data for use inside of another model? - node.js

I'm working with an Ember app that ties into a Rails backend through a Sails.js API, and ran into an issue I can't find any similar examples for.
My Messages model has a sender_id column, which corresponds with the id column in my Users model. There is also a Profile model (belongs to User through user_id) that contains the username column, which is what I'd like to be able to access through the Messages model as 'senderName' (i.e., message.senderName). My models and routes are all hooked up, so I have access to all the data I need and everything displays correctly in the browser aside from my senderName function.
The plan was to look up the Profile object through its user_id (using sender_id) inside of Messages, then pull the username field from there. I've been able to receive Promises back from my Profile query, but from there I'm not sure how/if I can access the actual username field. Is there a way to do this, or am I trying to fit a square peg into a round hole?
I also tried accessing username by looking up the User object first (this.store.find('user'), etc.) and using my Rails associations for user.profile.username, but that didn't work either.
Models:
App.Message = DS.Model.extend({
sender_id : DS.attr('number'),
content : DS.attr('string'),
senderName: function() {
var id = this.get('sender_id');
var profile = this.store.find('profile', {user_id: id}).then(function() {
console.log(profile);
// What next? profile.username doesn't work
})
}.property('sender_id')
});
App.Profile = DS.Model.extend({
user: DS.belongsTo('user', { async: true }),
username : DS.attr('string'),
user_id : DS.attr('number')
});
App.User = DS.Model.extend({
profile: DS.belongsTo('profile', { async: true }),
email: DS.attr('string'),
name: DS.attr('string')
});
Template:
<script type="text/x-handlebars" data-template-name="messages/index">
<div class="small-12 column">
{{#each message in model}}
<p>From: {{message.senderName}}</p>
<p>Content: {{message.content}}</p>
{{/each}}
</div>
</script>
Here's an example of the promises I'm receiving back in the browser console:
Promise {_id: 93, _label: undefined, _state: undefined, _result: undefined, _subscribers: Array[0]…}
Thanks for your help!

If I'm getting the question correct, you are having trouble accessing the fields in the model? If so, try:
profile.get('username')

You're really overcomplicating this :-)
Message also belongs to sender, why did you define sender_id as a number?
If you delete senderName and change the sender_id property to:
sender: DS.belongsTo('user', { async: true }),
You can use message.get('sender.profile.username'). I think your main issue here is that you're trying to do an async operation in the computed property, which is a no-no. You can alias the senderName if you like, via senderName: Ember.computed.alias('sender.profile.username') if you end up using it a lot.

Related

Is it possible to refer to "this" document in Mongoose?

I'm using Mongoose in Node.js, and I am wondering if it is possible to refer to the currently selected document using "this" or a similar mechanism. Here is the use case I'm looking for :
Mongoose Schema :
const mySchema = mongoose.Schema({
position: Number,
date: Number,
lastEventDate: Number
});
Let's say that, at some point in time, an event occurs.
For a document selected through its position, I want to update "lastEventDate" to the document's date.
Here is my dream code :
myModel.findOneAndUpdate(
{position: myPosition},
{$set: {
'lastEventDate': THISDOCUMENT.date
}}
);
Note : I'm using $set here because the actual code updates subdocuments...
Is there a built-in "THISDOCUMENT" reference such as the one I'm dreaming of, to do it all in a single query ?
Or do I have to first query the value before updating the document (two queries).
Couldn't find anything on the web, and I'm quite the newbie when it comes to using "this".
Thanks for any kind of help !
[EDIT :] Precisions about the objective :
I am in a situation where I only have the position "myPosition" to identify the correct document, and I want to set "lastEventDate" to the same value as "date" for that document.
My question is about efficiency : is it possible to perform the update in a single upload query ? Or do I have to first download the "date" value before uploading it back to the "lastEventDate" key ?
Gathering all the information provided, I will venture on a possible answer!
You could try something like:
Your schema JS file
const mySchema = mongoose.Schema({
position: Number,
date: Number,
lastEventDate: Number
});
mySchema.methods.doYourThing(){
this.lastEventDate=this.date; //it will set the lastEventDate
}
mongoose.model("myModel", MySchema, "mycollection")
Now, whenever you call doYourThing(), the action wanted will take place, you call it after you have a instance of the mode.
This is from my own code
const token = user.generateJwt(expirationDate); //send a token, it will be stored locally in the browser
it is inside a function that return an instance of user, and in the model User I have done a function called generateJwt like I have showed, and we have something like this:
return jwt.sign(
{
_id: this._id, //this is created automatically by Mongo
email: this.email,
name: this.name,
exp: parseInt(expiry.getTime() / 1000, 10), //Includes exp as UNIX time in seconds
level: this.level,
lastLogin: this.lastLogin,
failedLogin: this.failedLogin
},
process.env.JWT_SECRET
); // DO NOT KEEP YOUR SECRET IN THE CODE!
It returns all the information of the user!
Please, do not hesitate to add comments and feebacks, I am not sure it is what you want, but that is why I have understood your request.
Anothe option is using Virtuals, they also have access to this.

Custom User Registration: How to write gender to customer record

My user registration form has a custom field for Gender. How can I make SCA write the customer's gender to the NS database?
Should I use Commerce API or SuiteScript API?
I guess I should edit the model Account/SuiteScript/Account.Model.js but should I use the Commerce API (Customer object or the Profile/User object) or directly write to the database using record.setFieldValue()?
In Account/SuiteScript/Account.Model::register() function:
//#method register
//#param {UserData} user_data
//#param {Account.Model.Attributes} user_data
register: function (user_data)
{
var customer = session.getCustomer();
....default user registration code
var user = Profile.get();
// Should I do it like this?
customer.updateProfile({
"customfields": {
"custentity_dev_gender": user_data.gender
}
})
// Should I edit the user object??
// Should I interact directly with the customer record. Does Commerce API allow this??
var record = nlapiLoadRecord('customer', user.id);
record.setFieldValue('custentity_dev_gender', user_data.gender);
var internalId = nlapiSubmitRecord(record);
return {
touchpoints: session.getSiteSettings(['touchpoints']).touchpoints
, user: user
, cart: LiveOrder.get()
, address: Address.list()
, creditcard: CreditCard.list()
};
}
Its Very Simple actually no need to write record.setFieldValue()
Very first step is to create custom entity field from Customization--> Entity Field-->New
Apply the entity field to customer give edit access to field and Save.
Copy the entity id from there, in this case say 'custentity_gender_field'
The you need to override below two files, if you don't know ask separate question I'll reply there.
i.Modules/suitecommerce/LoginRegister#2.2.0/Templates/login_register_register.tpl
ii.Modules/suitecommerce/Profile#2.3.0/SuiteScript/Profile.Model.js
Go to first file write below code somewhere where you need to put gender
<div class="login-register-register-form-controls-group" data validation="control-group">
<label class="login-register-register-form-label for="custentity_gender_field">{{translate 'Gender: '}}
<small class="login-register-register-form-optional">{{translate '(optional)'}}</small>
</label>
<div class="login-register-register-form-controls" data-validation="control">
<input type="text" name="custentity_gender_field" id="custentity_gender_field" class="login-register-register-form-input">
</div>
</div>
Then Go to Second File i.e. Profile.Model.js
You can see in get function various filed values, put your custom field value there
this.fields = this.fields || ['isperson', 'email', 'internalid', 'name', 'overduebalance', 'phoneinfo', 'companyname', 'firstname', 'lastname', 'middlename', 'custentity_gender_field', 'emailsubscribe', 'campaignsubscriptions', 'paymentterms', 'creditlimit', 'balance', 'creditholdoverride'];
Deploy your code and test under user roles--> Manage User
I missed one more thing here, you need to make changes in
Account#2.2.0/SuiteScript/Account.Model.js as well in this model find
register: function (user_data)
{
}
and your custom field in
ModelsInit.session.registerCustomer(
{
custentity_gender_field:user_data.custentity_gender_field
});
That's it...

Return the value from a mongoose find

I'm trying to render a template when i check the link for view a profile (for example going to http://localhost:3000/user/testuser`), all ok going to the routes but the values don't being showed on the page. This is my code:
Account.find({ username: req.params.username }, function(err, userWatch){
res.render('account/profile',{
title : userWatch.username,
user : req.user,
watchUser : userWatch,
});
You are using Account.find() that returns the array of objects. You should use Account.findOne() to fetch data, if you want either one or none objects.

Password confirmation and external Model validation in Sails.js

I've been playing around with Sails for maybe one day. I'm trying to wrap my head around what would be the best way to do extensive validation in Sails.js.
Here is the scenario:
Registration Form:
Username: _______________
E-Mail: _______________
Password: _______________
Confirm: _______________
User inputs:
a correct e-mail
a username that already exists
two passwords that don't match
Desired outcome:
Username: _______________ x Already taken
E-Mail: _______________ ✓
Password: _______________ ✓
Confirm: _______________ x Does not match
Requirements, a few key points:
The user receives all error messages (not just the first one) for every aspect of his input. They are not vague ("username already taken" or "username must be at least 4 letters long" is better than "invalid username")
The built-in model validation can obviously not be responsible for checking a matched password confirmation (SRP)
What I think I need to do:
UserController:
create: function(req, res) {
try {
// use a UserManager-Service to keep the controller nice and thin
UserManager.create(req.params.all(), function(user) {
res.send(user.toJSON());
});
}
catch (e) {
res.send(e);
}
}
UserManager:
create: function(input, cb) {
UserValidator.validate(input); // this can throw a ValidationException which will then be handled by the controller
User.create(input, cb); // this line should only be reached if the UserValidator did not throw an exception
}
User: (model)
attributes: {
username: {
type: 'string',
required: true,
minLength: 3,
unique: true
},
email: {
type: 'email',
required: true,
unique: true
},
password: {
type: 'string',
required: true
}
}
UserValidator:
This is the tricky part. I need to combine input-specific validation (does the password confirmation match?) with the Model validation (is the username taken and is the e-mail address valid?).
If there was a way to instantiate a User-model and perform validation without saving to the database in Sails/Waterline I think this would be quite straight-forward, but there doesn't seem to be that option.
How would you go about solving this problem? Thank you very much for your help!
You can do this in your model:
module.exports = {
types: {
mycustomtype: function (password) {
return password === this.confirm;
}
},
attributes: {,
password:{
type: 'STRING',
required: true,
mycustomtype: true
}
}
}
There are going to be some validations that you can perform immediately on the client-side without needing to round-trip to the server. Things like comparing the password with the confirmation password, as well as verifying a string matches an email regex can be done with client-side javascript.
For other things like checking whether a username exists or not, you could use an ajax call to sails to directly ask it 'does this username exist' and provide real-time validation on the client-side based on the result, or you can wait until the user submits the form and parse the form submission to display those validations. Since checking ahead of time for things like this aren't 100% reliable (i.e. someone could create a user with that name after the check but prior to the form being posted back), some people choose to forgo the pre-check and only handle the error after post.
Waterline has its own built-in validation mechanism called Anchor, which is built on validator.js (previously called node-validator). For a full list of validations available, see here. I would recommend that instead of defining a separate validation layer, you define a method that parses the sails validation messages and formats them in a way that is user-friendly and consistent.
If you want to perform your own validations outside of what Waterline would do for you, you could do those validations inside a lifecycle callback, for instance the beforeCreate(values, callback) lifecycle callback. If you detect errors, you could pass them into the callback as the first parameter, and they would be passed back as an error to the caller of the create collection method.
An alternative to using a lifecycle callback, would be to create your own collection method that handles the create. Something like this:
Users.validateAndCreate(req.params.all(), function (err, user) {
...
});
More information about how to create a collection method like this can be found in my answer to this question: How can I write sails function on to use in Controller?

Mongoose key/val set on instance not show in JSON or Console.. why?

I have some information on my mongoose models which is transient. For performance reasons I dont wish to store it against the model.. But I do want to be able to provide this information to clients that connect to my server and ask for it.
Here's a simple example:
var mongoose = require('mongoose'),
db = require('./dbconn').dbconn;
var PersonSchema = new mongoose.Schema({
name : String,
age : Number,
});
var Person = db.model('Person', PersonSchema);
var fred = new Person({ name: 'fred', age: 100 });
The Person schema has two attributes that I want to store (name, and age).. This works.. and we see in the console:
console.log(fred);
{ name: 'fred', age: 100, _id: 509edc9d8aafee8672000001 }
I do however have one attribute ("status") that rapidly changes and I dont want to store this in the database.. but I do want to track it dynamically and provide it to clients so I add it onto the instance as a key/val pair.
fred.status = "alive";
If we look at fred in the console again after adding the "alive" key/val pair we again see fred, but his status isnt shown:
{ name: 'fred', age: 100, _id: 509edc9d8aafee8672000001 }
Yet the key/val pair is definitely there.. we see that:
console.log(fred.status);
renders:
alive
The same is true of the JSON representation of the object that I'm sending to clients.. the "status" isnt included..
I dont understand why.. can anyone help?
Or, alternatively, is there a better approach for adding attributes to mongoose schemas that aren't persisted to the database?
Adding the following to your schema should do what you want:
PersonSchema.virtual('status').get(function() {
return this._status;
});
PersonSchema.virtual('status').set(function(status) {
return this._status = status;
});
PersonSchema.set('toObject', {
getters: true
});
This adds the virtual attribute status - it will not be persisted because it's a virtual. The last part is needed to make your console log output correctly. From the docs:
To have all virtuals show up in your console.log output, set the
toObject option to { getters: true }
Also note that you need to use an internal property name other than status (here I used _status). If you use the same name, you will enter an infinite recursive loop when executing a get.
Simply call .toObject() on the data object.
For you code will be like:
fred.toObject()
This has been very helpful. I had to struggle with this myself.
In my case, I was getting a document from mongoose. When I added a new key, the key was not visible to the object if I console.log it. When I searched for the key (console.log(data.status), I could see it in the log but not visible if I logged the entire object.
After reading this response thread, it worked.
For example, I got an object like this one from my MongoDB call:
`Model.find({}).then(result=> {
//console.log(result);// [{ name:'John Doe', email:'john#john.com'}];
//To add another key to the result, I had to change that result like this:
var d = result[0];
var newData = d.toJSON();
newData["status"] = "alive";
console.log(newData);// { name:'John Doe', email:'john#john.com', status:'alive'};
}).catch(err=>console.log(err))`
Hope this helps someone else.
HappyCoding

Resources