Error copying req.body properties into Mongoose Model - javascript

First of all I have to say that I'm new in Angular and node technologies. So sorry for my ignorance.
I get this error when I try to save an Entity from edition view: 'Cast to ObjectId failed for value "[object Object]" at path "category"'.
Well, I've got these code:
HTML:
<form class="form-horizontal" data-ng-submit="update()" novalidate>
<fieldset>
<div class="form-group">
<label for="listaCat">Categoría:</label>
<select id="listaCat" class="form-control" data-ng-Fmodel="notification.category" data-ng-options="c.name for c in listaCategorias track by c._id">
</select>
</div>
<div class="form-group">
<label class="control-label" for="name">Descripción</label>
<div class="controls">
<input type="text" data-ng-model="notification.name" id="name" class="form-control" placeholder="Descripción" required>
</div>
</div>
<div class="form-group">
<input type="submit" value="Guardar" class="btn btn-default">
</div>
<div data-ng-show="error" class="text-danger">
<strong data-ng-bind="error"></strong>
</div>
</fieldset>
</form>`
Angular controller:
$scope.update = function() {
var notification = $scope.notification;
notification.$update(function() {
$location.path('notifications/' + notification._id);
}, function(errorResponse) {
$scope.error = errorResponse.data.message;
});
};
Server side controller:
var mongoose = require('mongoose'),
errorHandler = require('./errors.server.controller'),
Notification = mongoose.model('Notification'),
_ = require('lodash');
exports.update = function(req, res) {
var notification = req.notification;
notification = _.extend(notification , req.body);
notification.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(notification);
}
});
};
Mongoose Model:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var NotificationSchema = new Schema({
name: {
type: String,
default: '',
required: 'Rellena la notificación',
trim: true
},
created: {
type: Date,
default: Date.now
},
user: {
type: Schema.ObjectId,
ref: 'User'
},
category: {
type: Schema.ObjectId,
ref: 'Category'
}
});
mongoose.model('Notification', NotificationSchema);
var CategorySchema = new Schema({
name: {
type: String,
default: '',
required: 'Rellena la categoría',
trim: true
},
created: {
type: Date,
default: Date.now
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
mongoose.model('Category', CategorySchema);
So, if I debug inside Server controller at update method with WebStorm, I can see that req.body comes with each attribute well formed, but after convert req.body into Notification Mongoose Model with:
notification = _.extend(notification , req.body);
the category attribute is not a Model but an ObjectId. It seems as lodash.extend is not working properly for complex attributes. I've tried many other ways of cloning the object but without success.
Finally I solved it, with this line inside the angular controller:
notification.category = $scope.notification.category._id;
notification.$update(function() {
Anyway, I think that this is not the right way. I guess there must be a way of copying the req.body properties into a mongoose model without doing it manually for the complex properties.
Thanks a lot in advance!

Since you are working on AngularJS and ExpressJS, i would suggest you to use $resource service which is exactly meant for interacting with the rest API.
**$resource** contains these default set of actions:
{ 'get': {method:'GET'},
'save': {method:'POST'},
'query': {method:'GET', isArray:true},
'remove': {method:'DELETE'},
'delete': {method:'DELETE'} };
There is nice documentation available in the link that i shared above.
In your case:
i assume, http://localhost:300/notifications/:id, this might be your rest url where you want to perform update action.
You can create your custom services like:
var module = angular.module('myapp.services',['ngResource']);
module.factory('MyAppUpdateService',function($resource){
return $resource('notifications/:id',
{
id: '#id'
},
{
'update': { method:'PUT' }
}
);
});
Now inside your angular app controller you can inject this service as dependency and hence it will be available to perform update in that REST url.
angular.module('myapp',['ngResource','myapp.services']);
angular.module('myapp').controller('MeetupsController',['$scope','$resource','$state','$location','MeetupUpdateService','socket',
function($scope,$resource,$state,$location, MyAppUpdateService){
$scope.updateMeetup = function(){
$scope.updateService = new MyAppUpdateService();
$scope.updateService.name = $scope.notification.name;
.
.
.
$scope.updateService.$update({id:$scope.notification.category._id},function(result){
$location.path("/meetup/")
});
}
})]);
So this was just an example, if you want more comprehensive implementation. Look here, i am creating a MEAN seed of my own, and i am doing the same.
Any doubt please do ask.

Related

MethodOverride PUT not working

I am using Node.js, Express and MethodOverride to try and have a form update only 1 part of a model (my user model).
User model:
var userSchema = new mongoose.Schema({
email: { type: String, unique: true, lowercase: true },
password: String,
profile: {
name: { type: String, default: 'Company Name' },
location: { type: String, default: 'Location' },
website: { type: String, default: 'Your Website' },
picture: { type: String, default: '' }
},
assetNumPre: { type: String, default: 'test' }, // this is the one I want to change
});
module.exports = mongoose.model('User', userSchema);
HTML form:
<form role="form-inline"action="/dashboard/settings/assetNumber?_method=PUT" method="POST">
<div class="col-md-3">
<div class="form-group">
<label for="prefix" class="control-label">Prefix for Asset Number</label>
<br>
<small>Any alphanumeric characters to a limit of 6</small>
<input type="text" class="form-control" id="prefix" name="prefix" placeholder="Prefix max 6 characters" maxlength="6" value="{{ prefix }}">
</div><!-- Prefix for Asset Number-->
<br>
<div class="box-footer">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
Then route:
app.put('/dashboard/settings/assetNumber',
setRender('dashboard/settings/assetNumbers'),
setRedirect({auth: '/login'}),
isAuthenticated,
dashboard.getDefault,
(req, res) => {
var prefix = req.body.prefix;
console.log(req.params);
User.findByIdAndUpdate({_id: req.params.user_id}, prefix, function(err, UpdatedUser) {
if (err) {
res.send(err);
}
console.log(UpdatedUser);
});
res.locals.prefix = req.user.assetNumPre;
});
One thing my route is missing is req.user.assetNumPre which is where I need to save it but I have no clue how to do this PUT request. Docs are not helping much either.
I got the route from a Stack Overflow example a few days ago and can't find the link to it. My app.js had method override working because I have done DELETE requests already. The model has the correct field and has a default test value that shows up in my show page.
You're calling this:
User.findByIdAndUpdate({_id: req.params.user_id}, prefix...
But prefix is only the value:
var prefix = req.body.prefix;
findByIdAndUpdate takes an Object, not a value, to update a specific field.
So try:
User.findByIdAndUpdate({_id: req.params.user_id}, { assetNumPre: prefix }...
Here is the fixed route:
app.put('/dashboard/settings/assetNumber',
setRedirect({auth: '/login', success: '/dashboard/settings/assetNumber', failure: '/dashboard/settings/assetNumber'}),
isAuthenticated,
(req, res) => {
User.findById(req.user.id, function(err, user) {
if (err) return (err);
user.assetNumPre = req.body.prefix || 'pre';
user.save(function(err) {
if (err) return (err);
req.flash('success', { msg: 'Asset Number Prefix updated.' });
res.redirect(req.redirect.success);
});
});
res.locals.prefix = req.user.assetNumPre;
});
So a few things changed that were not part of the issue. I figured out I need to just set the data inside the callback function. Then do a user.save.

How to add an array to a javascript server side model in MEAN.JS

I am using MEAN.JS and I have created a module for projects. I would like to add tasks to these projects and I would like to do it with a multi-dimensional array. I would like the array to include a task.description and a task.status which would both be strings. I think I understand the client-side part of my project and I know there are still other files. However, I believe this will make the question as simple as possible, as I am struggling to get my profile developed on this site. I will however include controller.js, so I can get this working and hopefully give credit for a correct answer.
project.server.model.js
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
/**
* Project Schema
*/
var ProjectSchema = new Schema({
created: {
type: Date,
default: Date.now
},
title: {
type: String,
default: '',
trim: true,
required: 'Title cannot be blank'
},
description: {
type: String,
default: '',
trim: true
},
/* MODEL for TASK ARRAY*/
task: {
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
mongoose.model('Project', ProjectSchema);
projects.server.controller.js
'use strict';
/**
* Module dependencies.
*/
var path = require('path'),
mongoose = require('mongoose'),
Project = mongoose.model('Project'),
errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller'));
/**
* Create a project
*/
exports.create = function (req, res) {
var project = new Project(req.body);
project.user = req.user;
project.save(function (err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(project);
}
});
};
/**
* Show the current project
*/
exports.read = function (req, res) {
res.json(req.project);
};
/**
* Update a project
*/
exports.update = function (req, res) {
var project = req.project;
project.title = req.body.title;
project.description = req.body.description;
project.save(function (err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(project);
}
});
};
edit-project.client.view.html
<section ng-controller="ProjectsController" ng-init="findOne()">
<div class="page-header">
<h1>Edit Project</h1>
</div>
<div class="col-md-12">
<form name="projectForm" class="form-horizontal" ng-submit="update(projectForm.$valid)" novalidate>
<fieldset>
<div class="form-group" show-errors>
<label for="title">Title</label>
<input name="title" type="text" ng-model="project.title" id="title" class="form-control" placeholder="Title" required>
<div ng-messages="projectForm.title.$error" role="alert">
<p class="help-block error-text" ng-message="required">Project title is required.</p>
</div>
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea name="description" ng-model="project.description" id="description" class="form-control" cols="30" rows="4" placeholder="Description"></textarea>
</div>
<div class="form-group">
Task Description
<textarea name="description" ng.model="project.task.description" class="form-control" cols="30" rows="3" placeholder="Description"></textarea>
<div>
Task Status
<input name="status" ng.model="project.task.status" class="form-control" placeholder="Status">
</div>
</div>
<div class="form-group">
<input type="submit" value="Update" class="btn btn-default">
</div>
<div ng-show="error" class="text-danger">
<strong ng-bind="error"></strong>
</div>
</fieldset>
</form>
</div>
</section>
First, create a model for tasks (task.server.model.js), which references a Project
var TaskSchema = new mongoose.Schema({
description: String,
status: String,
// referencing Project model
project: { type: mongoose.Schema.Types.ObjectId, ref: 'Project' }
});
And then in Project model reference Task
// Add this to Project Schema definition
tasks: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Task' }]
It's should
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
/**
* Task Schema
*/
var TaskSchema = new Schema({
description: String,
status: String,
project: {
type: Schema.ObjectId,
ref: 'Project'
}
});
Hope it's help you!

Meteor using namedContext to addInvalidKeys to an AutoForm form returning an error

I have the following SimpleSchema where I am trying to add custom validation to validate against entering duplicate customer name, yet whenever I try to save a new customer I get error:
Exception in delivering result of invoking
'adminCheckNewCustomerName': TypeError: Cannot read property
'namedContext' of null
can someone please tell me what I am doing wrong / missing here to validate the customer name against duplicate records? Thanks
schema.js:
AdminSection.schemas.customer = new SimpleSchema({
CustomerName: {
type: String,
label: "Customer Name",
unique: true,
custom: function() {
if (Meteor.isClient && this.isSet) {
Meteor.call("adminCheckNewCustomerName", this.value, function(error, result) {
if (result) {
Customer.simpleSchema().namedContext("newCustomerForm").addInvalidKeys([{
name: "CustomerName",
type: "notUnique"
}]);
}
});
}
}
}
});
UI.registerHelper('AdminSchemas', function() {
return AdminSection.schemas;
});
form.html:
{{#autoForm id="newCustomerForm" schema=AdminSchemas.customer validation="submit" type="method" meteormethod="adminNewCustomer"}}
{{>afQuickField name="CustomerName"}}
<button type="submit" class="btn btn-primary">Save Customer</button>
{{/autoForm}}
collections.js:
this.Customer = new Mongo.Collection("customers");
Check collection2 code for fetching the schema attached to a collection:
_.each([Mongo.Collection, LocalCollection], function (obj) {
obj.prototype.simpleSchema = function () {
var self = this;
return self._c2 ? self._c2._simpleSchema : null;
};
});
This cryptic homonym _c2 (one of two hard things in programming...) comes from attachSchema:
self._c2 = self._c2 || {};
//After having merged the schema with the previous one if necessary
self._c2._simpleSchema = ss;
Which means that you have forgotten to attachSchema or fiddled with the property of your collection.
To solve:
Customer.attachSchema(AdminSchemas.customer);
//Also unless this collection stores only one customer its variable name should be plural

extend mean module findOne() to handle other attributes that strings

When start a new meanjs project (mongoose, angular etc.) using the generator and add a CRUD-module i get methods like this:
$scope.findOne = function() {
$scope.income = Incomes.get({
incomeId: $stateParams.incomeId
});
In my income server model is shown below, it has some different attributes and some different object types on these attributes, for example, number, date and string.
When i get data in my $scope.income after the promise "$scope.findOne" has succeded all my data are strings. Do i need to cast each and every one of them to their proper type?
In my front end i want to present the different types in the input elements of my "update" view. For example:
<label class="control-label" for="date">Date of transaction</label>
<div class="list-group-item">
<div class="controls">
<input type="date" data-ng-model="income.date" id="date" class="form-control" placeholder="Date" required>
</div>
This does not work since the object $scope.income.date consists of a string. Changing the input type to string makes it show up. But I want to use a date picker here.
Should I write something like:
$scope.findOne = function() {
Incomes.get({
incomeId: $stateParams.incomeId
}).then(function(data){
var dateVar=new Date(data.date);
var amountVar =Number(data.amount)
$scope.income ={date: dateVar, name: data.name, amount:amountVar}()
);
What is best practise here?
Model I am using:
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
/**
* Income Schema
*/
var IncomeSchema = new Schema({
name: {
type: String,
default: '',
required: 'Please fill Income name',
trim: true
},
amount: {
type: String,
default: '',
required: 'Please fill Income amount',
trim: true
},
date: {
type: Date,
default: '',
required: 'Please fill Income date',
trim: true
},
monthly: {
type: Boolean,
default: '',
required: 'Please fill whether income is recurring monthly',
trim: true
},
yearly: {
type: Boolean,
default: '',
required: 'Please fill whether income is recurring yearly',
trim: true
},
account: {
type: Schema.ObjectId,
ref: 'Account',
required: 'Please select account'
},
created: {
type: Date,
default: Date.now
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
mongoose.model('Income', IncomeSchema);
First I had to get a hold of the promise, than I easily could build more complex objects from the Incomes.get response. The data is sent over the network as JSON and there fore it is just text, so I needed to instantiate it to the proper types using f ex. Date and Number:
Incomes.get({
incomeId: $stateParams.incomeId
}).$promise.then(function(data){
var dateVar=new Date(data.date);
var amountVar =Number(data.amount)
$scope.income ={date: dateVar, name: data.name, amount:amountVar}()
);
To make the resources function for remove work properly "this" needed to be used, dont forget that the promise is in another namespace so "that=this" is needed.
var that=this;
Incomes.get({
incomeId: $stateParams.incomeId
}).$promise.then(function(income){
income.date= new Date(income.date);
var recurringVar;
if(income.monthly===true){
recurringVar = 'monthly';
} else if( income.yearly===true){
recurringVar = 'yearly';
}
income.recurring=recurringVar;
that.income=income;
});

Comparing value from input with backbone collection data

I'm trying to create very simple login with backbonejs. Collection stores usernames and passwords. Login view has two inputs and on click it should perform check function and compare input value with data from collection.
Html part looks like this:
<div class="login-block">
<script type="text/template" id="start">
<form id="login">
<div class="input-wrapper"><input type="text" placeholder="Username" id="username" required></div>
<div class="input-wrapper"><input type="password" placeholder="Password" id="password" required></div>
<div class="input-wrapper"><button class="btn">Sign in!</button></div>
</form>
</script>
<div class="error" class="block">
Error
</div>
<div class="success">
Success
</div>
</div>
Here is my Js code:
var User = Backbone.Model.extend({
defaults: {
login: 'root',
mail: 'root#mail.com',
password: ''
}
});
var user = new User();
//variable to store username
var loginData = {
username: "",
password: ""
}
// userbase
var UserCollection = Backbone.Collection.extend({
model: User
});
var userCollection = new UserCollection([
{
username: 'Ivan',
mail: 'ivan#mail.com',
password: '1234'
},
{
username: 'test',
mail: 'test#mail.com',
password: 'test'
}
]);
// login page
var LoginView = Backbone.View.extend({
el: $(".login-block"),
events: {
"click .btn": "check"
},
check: function(){
loginData.username = this.$el.find("#username").val(); // store username
loginData.password = this.$el.find("#password").val();// store password
if (loginData.username === userCollection.each.get("username") && loginData.password === userCollection.each.get("password"))
{appRouter.navigate("success", {trigger: true});
}else{
appRouter.navigate("error", {trigger: true});
}
},
render: function () {
//$(this.el).html(this.template());
var template = _.template($('#start').html())
$(this.el).html(template());
//template: template('start');
return this;
}
});
var loginView = new LoginView({collection: userCollection});
var AppRouter = Backbone.Router.extend({
routes: {
'': 'index', // start page
'/error': 'error',
'/success': 'success'
},
index: function() {
loginView.render();
console.log("index loaded");
},
error: function(){
alert ('error');
},
success: function(){
console.log('success');
}
});
var appRouter = new AppRouter();
Backbone.history.start();
It works fine to the check function, and it stores username and password, but something is clearly wrong either with router or check function when it starts comparison. Instead of routing to success or error page, it rerenders index page.
P.S I didn't use namespacing and code in general is not of a greatest quality, but it was made for educational purpose only.
You have to add the attribute type="button" to your button, otherwise it will submit the form when clicked (See this question):
<script type="text/template" id="start">
<form id="login">
<div class="input-wrapper"><input type="text" placeholder="Username" id="username" required></div>
<div class="input-wrapper"><input type="password" placeholder="Password" id="password" required></div>
<div class="input-wrapper"><button class="btn" type="button">Sign in!</button></div>
</form>
</script>
You can also return false in the click event handler, which would cancel the default action. (submitting the form, if you don't add type="button").
For comparing the values with the hardcoded collection, you can't call each as you where doing (which is an iteration function provided by Underscore) because you would receive an error. You could use Underscore's findWhere method which is also available in Backbone collections. So the click event handler (Your check function) could look like this:
check: function(){
loginData.username = this.$el.find("#username").val(); // store username
loginData.password = this.$el.find("#password").val();// store password
if(userCollection.findWhere({username: loginData.username, password: loginData.password})){
appRouter.navigate("success", {trigger: true});
}else{
appRouter.navigate("error", {trigger: true});
}
return false;
},
You can try it on this fiddle
The logic check you're doing doesn't look like it would work to me. I would expect the following to generate an error:
userCollection.each.get('username')
the function you're calling on your collection, each, is a wrapped underscore method which takes a function callback as a parameter. If you want to check your username and password, I'd do something like this:
var user = userCollection.findWhere({ username: loginData.userName });
This will return you the model where the username matches. Then you can check the password of that model:
if (user.get('password') === loginData.password) {
// do something
} else {
// do something else
}
EDIT Heck, you can do both checks at once:
var user = userCollection.findWhere({ username: loginData.userName, password: loginData.password });
I'll leave the previous code up just to demonstrate.

Categories

Resources