Backbone.js collection wont fetch models that arent valid - javascript

I added a validation to a Model and a Collection wont fetch the models who arent valid. (Btw I use coffeescript so the examples are in coffeescript)
Somebody knows a solution for this? The following isnt working
collection = new UserCollection
collection.fetch({
silent: true
success: ->
console.log('collection.models:', collection.models)
})
UPDATE 1
I have a lot of users without a mobile number.
User Collection:
class UserCollection extends Backbone.Collection
url: ->
app.routes.users_url
User Model:
class User extends Backbone.Model
idAttribute: '_id'
defaults: {
"email": null
"mobile": null
"loc": null
}
url: ->
app.routes.users_url + '/' + (#id || '')
validate: (attrs) ->
if !attrs.email
return "Email address must be provided"
if !attrs.name
return "Name must be provided"
if !attrs.mobile
return "Mobile number must be provided"
if #isNew() and attrs.password != undefined
if !attrs.password
return "Password must be provided"
if attrs.password != attrs.password_confirmation
return "Passwords do not match"
model: User
UPDATE 2
ok i temporary fixed it by hacking the backbone.js.
It is happening in function _prepareModel
I changed this line:
if (model.validate && !model._performValidation(attrs, options)) model = false;
into this line:
if (!options.silent && model.validate && !model._performValidation(attrs, options)) model = false;
It is not a solution so i keep this question open

"I added a validation to a Model and a Collection wont fetch the models who arent valid.
(Btw I use coffeescript so the examples are in coffeescript)"
If your models don't validate you have a problem with your models or your validation.
"I have a lot of users without a mobile number."
In your validation you have:
if !attrs.mobile
return "Mobile number must be provided"
you could define a parse function in your collection to log what models are coming from the server (parse() gets passed the raw response from fetch())
parse: function(response) {
console.log(response.results);
return response.results;
}
or you can take the line that validates the existence of a mobile number out of your validation since you don't know if the user has a mobile number.
and just to cover all the bases, defining an error function for fetch() should help you:
collection.fetch({
silent: true
success: ->
console.log('collection.models:', collection.models)
error: (collection, response) ->
console.log('response')
})

When you validate your model, check for model.silent and only validate if that doesn't exist.
So you do the following when you want to fetch a model:
var test = new MyModel({ id: '123', silent: true });
// in your Model validate function
validate: function(attrs) {
if (!attrs.silent) {
// validate logic here
}
}
Then you can fetch the model. After you get your model you can unset silent.

Related

Exception when checking if an issue field becomes "In Progress" with workflow

My issue fields have a State and an option called In Progress
So I wrote a Youtrack Workflow that launches a http post to my discord channel when an issue becomes "In Progress".
Here is the JavaScript code for that:
var entities = require('#jetbrains/youtrack-scripting-api/entities');
var http = require('#jetbrains/youtrack-scripting-api/http');
exports.rule = entities.Issue.onChange({
// TODO: give the rule a human-readable title
title: 'Open-discord-channel',
guard: function(ctx) {
return ctx.issue.fields.becomes(ctx.State, ctx.State.InProgress);
},
action: function(ctx) {
var issue = ctx.issue;
var connection = new http.Connection('https://discordapp.com');
connection.addHeader('Content-Type', 'application/json');
var response = connection.postSync('/api/webhooks/123/1DJucC8-vdZR-xxx', [], issue.description);
if (response && response.code === 200) {
issue.addComment(response.response);
}
// TODO: specify what to do when a change is applied to an issue
},
requirements: {
// TODO: add requirements
}
});
When activating this workflow this exception gets thrown:
TypeError: Cannot read property "InProgress" from undefined (open-discord-channel/open-discord-channel#16)
org.mozilla.javascript.ScriptRuntime.constructError(ScriptRuntime.java:4198)
org.mozilla.javascript.gen.open_discord_channel_open_discord_channel_2052._c_anonymous_1(open-discord-channel/open-discord-channel:16)
It tells me Cannot read property "InProgress" but in fact return ctx.issue.fields.becomes(ctx.State, ctx.State.InProgress); the value InProgress was suggested by the embedded Youtrack Workflow editor.
Can anybody tell me how I can access the real "In Progress" value to make this code running?
EDIT
tried this
return ctx.issue.fields.becomes(ctx.State.name, "In Progress");
Still gave me an exception
Processing issue COOPR-85:
TypeError: Cannot read property "name" from undefined (open-discord-channel/open-discord-channel#16)
org.mozilla.javascript.ScriptRuntime.constructError(ScriptRuntime.java:4198)
org.mozilla.javascript.gen.open_discord_channel_open_discord_channel_2076._c_anonymous_1(open-discord-channel/open-discord-channel:16)
If you want to use the ctx.issue.fields.becomes(ctx.State, ctx.State.InProgress) syntax, add a definition for the 'In Progress' state to the requirements section:
requirements: {
State: {
type: entities.State.fieldType,
InProgress: {
name: 'In Progress'
}
}
}
Alternatively, to avoid the Cannot read property "name" from undefined error, check the State field for null values:
return ctx.issue.fields.State && ctx.issue.fields.becomes(ctx.State.name, "In Progress");
I hope it will be helpful.

should missing fields in a POST request throw an error?

I am writing a route for registering users. There are three required fields, name, email and password.
How should I handle a missing field ?
Like this ?
function(req, res) {
if(!req.body.name || !req.body.email || !req.body.password) {
res.status(400).json({
"message": "All fields required"
});
return;
}
}
Or should I throw an error and pass it to my error handler like this :
function(req, res, next) {
if(!req.body.name || !req.body.email || !req.body.password) {
return next(new Error('All fields required'));
}
}
You can use a middleware to make sure your endpoints get what they are supposed to. Try Express Validator
Include:
var expressValidator = require('express-validator')
Then
app.use(express.bodyParser());
app.use(expressValidator([])); // place after bodyParser
At your endpoint you can either check fields in body, params, or query separately like
req.checkBody('age', 'Invalid Age').notEmpty().isInt(); //required integer
req.checkBody('name', 'Invalid Name').notEmpty().isAlpha(); //required string
req.checkBody('name', 'Invalid Name').isAlpha(); // not required but should be string if exists
//for params use req.checkParams and for query req.checkQuery
var errors = req.validationErrors();
if (errors) {
res.send(errors).status(400);
return;
}
Or you can define and use a schema in a separate file. Let's say userSignUp.js inside validationSchemas directory
module.exports = {
'name': {
optional: true,
isLength: {
options: [{ min: 3, max: 15 }],
errorMessage: 'Must be between 3 and 15 chars long'
},
errorMessage: 'Invalid Name'
},
'email': {
notEmpty: true,
isEmail: {
errorMessage: 'Invalid Email'
}
},
'password': {
notEmpty: true,
errorMessage: 'Invalid Password' // Error message for the parameter
}
}
And at point of validation:
var userSignUpSchema = require('./validationSchemas/userSignUp.js);
req.checkBody(userSignUpSchema);
if (req.validationErrors()) {
res.send(errors).status(400);
return;
}
For every use case you can add another schema file and validate the fields
For only validate if fields are empty, the best way is validate in the front. If you use HTML 5 it's possible to use require in the input, this way the form is not submmited. But, in javascript this is not problem too.
Definitely the first option. The second one may in fact crash your server if you don't have any error middleware (not sure about that though). Even if you had error middleware it would probably default to returning 500 - 400 is the proper response.
For required fields, instead of checking with the server and loading it more, you can simply do it in your front end. There are multiple ways of doing it. In HTML, there is a required attribute which you can set to true and it does not submit the form until all these required fields are filled.
To get more control over how the error is handled, you can verify the form using a form verification method on submitting it which checks for empty and/or invalid fields (Form validation). This can be done using jQuery, plain old JS, angular or whatever you'd like to use at the client side.
In case you want to keep it completely server side, the first option wherein you're checking for empty object attributes and returning an error JSON is how I'd do it.

Alerts/Notifications on Meteor application do not appear

I created a new "Alerts" collection. Its to show number of unread messages. Messages get submitted and appear, and theres no other error on console or server.
2nd issue is, when i click on the specific room, it is supposed to mark all new messages as "read". Somehow the number stays. Error shows Exception in queued task: .added#http://localhost:3000/app/lib/collections/messages.js
File structure:
roomList.js - shows a list of all rooms, shows number of unread messages
roomDetail.js - when click specific room in list, will mark message as
"read", unread number dissapears.
alerts.js (Alerts collection)
messages.js (Messages collection)
rooms.js (Rooms collection)
publications and sub js
Meteor.publish('alerts', function() {
return Alerts.find({ userId: this.userId, read: false });
});
Meteor.subscribe('alerts')
Alerts collection js
Alerts = new Mongo.Collection('alerts');
Alerts.allow({
update: ownsDocument,
//if removed, shows error:
// insert failed: Access denied. No allow validators set on restricted collection for method 'insert'.
insert: function(){
return true;
}
});
createMessageAlert = function(message) {
if ( message.user !== Meteor.userId() ){
Alerts.insert({
userId : message.user,
roomId : Router.current().params._id, //params id of current room
messageId : message._id,
read : false
});
}
};
roomDetail.js
Messages.insert({
roomId : Router.current().params._id,
msg : message,
user : Meteor.user()._id
});
template.find('input').value = '';
createMessageAlert(message);
roomsList.js
Template.list.helpers({
alerts: function (){
return Alerts.find({ userId: Meteor.userId(), read: false });
},
alertCount: function(){
return Alerts.find({ userId: Meteor.userId(), read: false }).count();
}
});
Template.allRooms.events({
'click a': function() { //click the a href to go into roomDetail
Alerts.update(this._id, {$set: {read: true}});
}
});
Ultimate solution :
You should call the createMessageAlert from a trigger when a new Message is added in Messages collection.
Pre-requisites:
create a trigger(MSG_OBSERVER) for Messages collection, where whenever anything is added to the collection, a createMessageAlert method is invoked provided with the added document object, so you can play inside the method and do desired operations.
When you are updating Alerts collection. The collection should be published in such a way(named as "null") that it should be reactive and should be available from all the instances accessing the same account from different browser instances.
Implementation
Just add below code in your collections.js
Meteor.method(
'createMessageAlert': function(id, fields) {
if ( fields.user !== Meteor.userId() ){
Alerts.insert({
userId : fields.user,
roomId : Router.current().params._id, //params id of current room
messageId : id,
read : false
});
}
}
);
var MSG_OBSERVER = Messages.find();
MSG_OBSERVER.observe({
added: function(id, fields){
Meteor.call('createMessageAlert', id, fields);
}
});
Meteor.publish(null ,function() { // null name means send to all clients
//use Messages.insert() to generate a fake message and add data in below params
Meteor.call('createMessageAlert', id, fields);
return Alerts.find();
});
Explaination
If you again read the pre-requisites, you will understand the code. Ensure you are subscribed with desired collection on client side. This code makes every collection involved and triggers very reactive and responsive.
Whatever you will add as messages will be added to Alerts as well.
Publishing "null" will simply publish data to all clients making UI behavior more robust and asynchronous.(I am using this feature in displaying real-time graphs, you don't even have to refresh UI and your data gets reflected.)
While publishing "null", you can create a fake Message OBJ and add it to call createMessageAlert function. You have to do this because you have to initiate publish on server restarts. choose Message Obj wisely so that it won't impact the work flow.

Sails.js Set model value with value from Callback

i need to provide something like an association in my Model. So I have a Model called Posts with an userid and want to get the username from this username and display it.
So my ForumPosts.js Model looks like the following:
module.exports = {
schema: true,
attributes: {
content: {
type: 'text',
required: true
},
forumTopicId: {
type: 'text',
required: true
},
userId: {
type: 'integer',
required: true
},
getUsername: function(){
User.findOne(this.userId, function foundUser(err, user) {
var username = user.username;
});
console.log(username);
return username;
}
}
};
I know that this return will not work because it is asynchronus... But how can i display it in my view? At the Moment i retrive the value with:
<%= forumPost.getUsername() %>
And for sure get an undefined return...
So the question is: How can I return this value - or is there a better solution than an instanced Model?
Thanks in advance!
Off the top of my head, you can just load associated user asynchronously before rendering:
loadUser: function(done){
var that = this;
User.findOne(this.userId, function foundUser(err, user) {
if ((err)||(!user))
return done(err);
that.user = user;
done(null);
});
}
then in your controller action:
module.exports = {
index: function(req, res) {
// Something yours…
forumPost.loadUser(function(err) {
if (err)
return res.send(err, 500);
return res.view({forumPost: forumPost});
});
}
}
and in your view:
<%= forumPost.user.username %>
This is kind of a quick and dirty way. For a more solid and long-term solution (which is still in development so far) you can check out the alpha of Sails v0.10.0 with the Associations API.
So this particularly case of associations between your models. So here you have a User model and ForumPost model and you need the user object in place of your user_id as user_id works as a relationship mapping field to your User model.
So if your are using sails V0.9.8 or below you need to handle this logic in your controller where ever you want to access User model attributes in your view.
In your controller write your logic as:
model.export = {
//your getForumPosts method
getForumPosts : function(req,res){
var filters = {};
forumPost.find(filters).done(function(err,posts){
if(err) return res.send(500,err);
// Considering only one post obj
posts = posts[0];
postByUser(posts.user_id,function(obj){
if(obj.status)
{
posts.user = obj.msg;
delete posts.user_id;
res.view({post:posts});
}
else
{
res.send(500,obj.msg);
}
});
}
}
}
function postByUser(user_id,cb){
User.findOne(user_id).done(function(err,user){
if(err) return cb({status:false, msg:err});
if(user){
cb({status:true, msg:user});
}
}
}
and then you can access your post object in your view.
Or else you can keep watch (at GitHub) on next version of sails as they have announced associations in V0.10 n it is in beta testing phase as if now.

Backbone.js Parse Method

I am trying to unit test my first backbone.js application using sinon.js and jasmine.js.
In this particular test case, I used the sinon.js fakeServer method to return a dummy response with the following structure.
beforeEach( function(){
this.fixtures = {
Tasks:{
valid:{
"tasks":[
{
id: 4,
name:'Need to complete tests',
status: 0
},
{
id: 2,
name:'Need to complete tests',
status: 1
},
{
id: 3,
name:'Need to complete tests',
status: 2,
}
]
}
}
};
});
So when I actually call the fetch call in the below test case, it returns the 3 models correctly. In the parse method of the collection, I tried to remove the root 'tasks' key and just return the array of objects alone, which was mentioned in the backbone.js documentation. But when I do this, no models are getting added to the collection and the collection.length returns 0.
describe("it should make the correct request", function(){
beforeEach( function(){
this.server = sinon.fakeServer.create();
this.tasks = new T.Tasks();
this.server.respondWith('GET','/tasks', this.validResponse( this.fixtures.Tasks.valid) );
});
it("should add the models to the tasks collections", function(){
this.tasks.fetch();
this.server.respond();
expect( this.tasks.length ).toEqual( this.fixtures.Tasks.valid.tasks.length );
});
afterEach(function() {
this.server.restore();
});
});
Task Collection
T.Tasks = Backbone.Collection.extend({
model: T.Task,
url:"/tasks",
parse: function( resp, xhr ){
return resp["tasks"];
}
});
Can you please tell me what am I doing wrong here?
The problem with my code was in the validate method of the model and not the parse method of the collection. I was testing for the attributes even when they dont exist. The object that is sent to validate will not have all the attributes every time. For example, in a task model with id,title and status, where status is set as 0 by default, if I create a model like
var t = new Task({'title':'task title'});
t.save();
here, the validate method will only get {'title':'task title'} as a parameter to the validate method.
So it is important to add those conditions too in the validate method and when I added conditions to check for the presence of the particular attribute and also when it is not null or undefined, my tests started passing.

Categories

Resources