SimpleSchema update error - meteor

I'm trying to create a favorite button by saving the usersId to the logged in users account. The concept is, if there is a userId (user is a favorite), else user is not a favorite. The problem is I'm getting an error update failed: Error: Favorites must be an array and I'm not sure what this means.
Path: schema.js
Schema.UserProfile = new SimpleSchema({
"favorites.$.favorite": {
type: Object
}
});
Path: studentlist.js
Template.student.events({
'click .favourite':function(event,template) {
console.log('click');
var candidateId = this._id;
Meteor.users.update({_id: Meteor.userId() }, { $set: { "profile.favorites": candidateId }});
}
});

You have basically two errors.
as of the error, you must have defined Favorites property to be an array. and in the code you're trying to update with $set command.
when you're inserting an item into an array in MongoDB, you've to use $push operator.
and the second problem you'll face after fixing this one would be the improper data type insertion. because you've defined favorite to be an object, but trying to insert a mere id.

Related

autoValue not working when you validate against mongodb collection fields

I am trying to set a value using autoValue based on already stored values
I am using meteor 1.3.4.1
This used to work in meteor 1.1.0.2
here is my code:
id: {
type: String,
label: "ID",
autoValue: function() {
var isFirstTime = this.field("profile.isFirstTime").value;
var isApproved = this.field("profile.changesApproved").value;
var value = this.field("profile.unapproved_id").value;
var userId = this.userId;
var user = Meteor.users.findOne({_id: userId});
if (user && user.profile && user.profile.id)
{
return user.profile.id;
}
}
}
I expect value of user.profile.id to be returned since user.profile.id has a value in the users collection but I get a value that is passed from input field. How do I get simple-schema to notice collection values as it used to on meteor 1.1.0.2
I think the confusing part here is the 'this' keyword. in the context of the autoValue function(), this may be the object itself, unless some other context is bound to it by SimpleSchema.
So would suggest you trace the code with debugger and check what is the value of this. if it is not what you think it is, check where it is defined

Meteor - Validating new user document serverside

I'm having trouble with this seemingly trivial stuff, harrrr!
I have this user document:
userData = {
account: {
type: 'free'
},
profile: {
name: 'Artem',
},
username: 'aaa#gmail.com',
password: '123'
};
Which I'm sending client-side: Accounts.createUser(userData);
Then server side I want to check if account type equals 'free'. If it doesn't - I want to abort new user creation (and hopefully throw error client side)
There are 2 functions which I've found in the docs that presumably can help me do it:
Accounts.validateNewUser
Problem: it receives 'trimmed-down' user object which doesn't contain properties other than profile, username, password, email. Thus I cannot validate account.type as it doesn't exist on user object being validated.
Accounts.onCreateUser
Problem: it is called after a generic user object is created and there is no way I can cancel inserting new document in Users collection. It absolutely requires to return a user document. If I return undefined it throws errors on server:
Exception while invoking method 'createUser' Error: insert requires an argument
It also doesn't allow to throw method errors (as it's not a method) -> thus I cannot log error client side.
You can use Accounts.validateNewUser with little change to your data structure:
userData = {
profile: {
name: 'Artem',
account : {
type : 'free'
}
},
username: 'aaa#gmail.com',
password: '123'
};
Then you should be able to access data you need.
As far as I remember there were some discussion on meteor forum about removing profile field, that's why I'm solving this kind of problems in different way. For me Meteor.users is collection which should not be changed for sake of peace in mind - it could be changed by future version of meteor. My approach require to write more code in the beginning, but later it pays off, because you have place to store data about user and Meteor.users collection has docs with minimal amount of data.
I would use jagi:astronomy#0.12.1 to create schema and custom methods. In general I would create new collection UserAccounts with schema:
UserAccount = new Astro.Class( {
name: 'UserAccount',
collection: 'UserAccounts',
fields: {
'userId' : {type: 'string'},
'type' : {type: 'string', default:'free'}
},
} )
and add schema to Meteor.users :
User = new Astro.Class( {
name: 'User',
collection: Meteor.users,
fields: {
'services' : {type: 'object'},
'emails' : {type: 'array'}
},
methods:{
account : function(){
return UserAccounts.findOne({userId:this._id})
}
}
} )
The usage looks like this:
var user = Meteor.users.findOne();
user.account().type
In summary:
Accounts.onCreateUser : always allow to create user account and always create UserAccount which corresponds to it ( with field userId)

MDG ValidatedMethod with Aldeed Autoform: "_id is not allowed by the schema" error

I'm getting the error "_id is not allowed by the schema" when trying to use an autoform to update a collection via a ValidatedMethod.
As far as I can see from this example and the official docs there is no expectation for my schema to include the _id field, and I wouldn't expect to be updating the id from an update statement, so I have no idea why this error is happening.
If I switch from using the validated method to writing directly to the collection (with a schema attached to the collection that doesn't have the id in) everything works as expected, so I'm assuming the issue is with my the validate in my ValidatedMethod.
Any idea what I'm doing wrong?
Template: customer-edit.html
<template name="updateCustomerEdit">
{{> quickForm
collection="CustomerCompaniesGlobal"
doc=someDoc
id="updateCustomerEdit"
type="method-update"
meteormethod="CustomerCompanies.methods.update"
singleMethodArgument=true
}}
</template>
Template 'code behind': customer-edit.js
Template.updateCustomerEdit.helpers({
someDoc() {
const customerId = () => FlowRouter.getParam('_id');
const instance = Template.instance();
instance.subscribe('CustomerCompany.get', customerId());
const company = CustomerCompanies.findOne({_id: customerId()});
return company;
}
});
Update Validated Method:
// The update method
update = new ValidatedMethod({
// register the name
name: 'CustomerCompanies.methods.update',
// register a method for validation, what's going on here?
validate: new SimpleSchema({}).validator(),
// the actual database updating part validate has already been run at this point
run( newCustomer) {
console.log("method: update");
return CustomerCompanies.update(newCustomer);
}
});
Schema:
Schemas = {};
Schemas.CustomerCompaniesSchema = new SimpleSchema({
name: {
type: String,
max: 100,
optional: false
},
email: {
type: String,
max: 100,
regEx: SimpleSchema.RegEx.Email,
optional: true
},
postcode: {
type: String,
max: 10,
optional: true
},
createdAt: {
type: Date,
optional: false
}
});
Collection:
class customerCompanyCollection extends Mongo.Collection {};
// Make it available to the rest of the app
CustomerCompanies = new customerCompanyCollection("Companies");
CustomerCompaniesGlobal = CustomerCompanies;
// Deny all client-side updates since we will be using methods to manage this collection
CustomerCompanies.deny({
insert() { return true; },
update() { return true; },
remove() { return true; }
});
// Define the expected Schema for data going into and coming out of the database
//CustomerCompanies.schema = Schemas.CustomerCompaniesSchema
// Bolt that schema onto the collection
CustomerCompanies.attachSchema(Schemas.CustomerCompaniesSchema);
I finally got to the bottom of this. The issue is that autoform passes in a composite object that represents the id of the record to be changed and also a modifier ($set) of the data, rather than just the data itself. So the structure of that object is along the lines of:
_id: '5TTbSkfzawwuHGLhy',
modifier:
{
'$set':
{ name: 'Smiths Fabrication Ltd',
email: 'info#smithsfab.com',
postcode: 'OX10 4RT',
createdAt: Wed Jan 27 2016 00:00:00 GMT+0000 (GMT Standard Time)
}
}
Once I figured that out, I changed my update method to this and everything then worked as expected:
// Autoform specific update method that knows how to unpack the single
// object we get from autoform.
update = new ValidatedMethod({
// register the name
name: 'CustomerCompanies.methods.updateAutoForm',
// register a method for validation.
validate(autoformArgs) {
console.log(autoformArgs);
// Need to tell the schema that we are passing in a mongo modifier rather than just the data.
Schemas.CustomerCompaniesSchema.validate(autoformArgs.modifier , {modifier: true});
},
// the actual database updating part
// validate has already been run at this point
run(autoformArgs)
{
return CustomerCompanies.update(autoformArgs._id, autoformArgs.modifier);
}
});
Excellent. Your post helped me out when I was struggling to find any other information on the topic.
To build on your answer, if for some reason you want to get the form data as a single block you can use the following in AutoForm.
type="method" meteormethod="myValidatedMethodName"
Your validated method then might look something like this:
export const myValidatedMethodName = new ValidatedMethod({
name: 'Users.methods.create',
validate(insertDoc) {
Schemas.NewUser.validate(insertDoc);
},
run(insertDoc) {
return Collections.Users.createUser(insertDoc);
}
});
NB: The Schema.validate() method then requires an Object, not the modifier as before.
I'm unclear if there are any clear advantages to either method in general.
The type="method-update" is obviously the way you want to go for updating documents because you get the modifier. The type="method" seems to be the best way to go for creating a new document. It would likely also be the best option in most cases where you're not intending to create a document from the form data.

meteorjs meteor-user-status mizzao package: cannot get other users

Trying to implement mizzao/meteor-user-status user status package, but only getting the status for the currently logged in user (me). Others show undefined.
Code in /server folder:
Meteor.publish('userStatus', function() {
return Meteor.users.find({
"status.online": true
});
});
Code in /client folder:
Meteor.subscribe('userStatus');
Template.member.helpers({
isOnline: function(username) {
console.log(username); //gets each username, not just logged me logged in
var currentUser = Meteor.users.findOne({
username: username
});
console.log(currentUser.status.online); //gets undefined for other users other than me
return currentUser.status.online; //returns undefined for other users
}
});
Maybe this needs to reference "userStatus" somehow instead of Meteor.users.findOne(...), but since global object was not created, e.g. like UsersOnline = new Mongdb.Collection - I don't know how to implement this.
Getting error in Chrome debugging tool: TypeError: Cannot read property 'status' of undefined, obviously, it only gets currently logged in user.
Edit: To clarify: I get other users from a custom "profiles" collection in Mongo that I created specifically to overcome the issue of not being able to read "users" collection, due to security, I guess. So I get the list of profiles and supply the user name to this function that supposed to use mizzao package to see if they are online. Since the package reads from "users" collection, it only returns current user (me).
I would be totally OK if I could modify the package for it to write to my "profiles" collection, if someone suggests how to do that.
Another update:
I ran the count on the server, and it returns only one (me, I guess).
var cursor = Meteor.users.find({
"status.online": true
}, {
fields:{
_id: 1,
status: 1,
username: 1
// Any other fields you may need
}
});
console.log(cursor.count());
If you have a publication like this:
Meteor.publish('userStatus', function() {
return Meteor.users.find({
"status.online": true
});
});
it will return users that are online only. So you get undefined for users other than you, because they are not logged-in.
Try logging in as a different user at the same time (i.e. using different browser), and see whether you can now see the status of that user.
You can always change your publication to:
Meteor.publish('userStatus', function() {
return Meteor.users.find({}, {fields: {username : 1, status : 1}});
});
and you will get statuses for all users. But if you only need to check the "online" flag (i.e. you don't need other features of the package), it is better to only send data about the online users and consider not published user as offline.
I guess you trying to read users before subscription is ready.
Server:
Meteor.publish('userStatus', function() {
return Meteor.users.find({
"status.online": true
}, {
fields:{
_id: 1,
status: 1,
profile: 1,
username: 1
// Any other fields you may need
}
});
});
Client (you should return cursor, which will re-run once subscription is ready):
Meteor.subscribe('userStatus');
Template.member.helpers({
isOnline: function(username) {
console.log(username); //gets each username, not just logged me logged in
var cursor = Meteor.users.find({username: username});
if(cursor.count()){
var currentUser = cursor.fetch()[0];
if(currentUser.status && currentUser.status.online){
console.log(currentUser.status.online); //gets undefined for other users other than me
return currentUser.status.online; //returns undefined for other users
}
}
return {};
}
});

Meteor - How To Extract Key Name From Collection?

I have the following in my initialize file to get the values loaded in the database on startup:
Meteor.startup(function() {
if(typeof Person.findOne() === 'undefined') {
Person.insert({
name: "",
gender: ["male", "female", "prefer not to say"],
age: 0
});
}
});
And then in the server/abc.js I have:
Meteor.methods({
checkPerson: function (input) {
for (var key in Person) {
if (input === key) {
...
}
}
}
});
This meteor method checkPerson is called in the client side with a string value being passed as its only argument(input).
I want to check this 'input' string value against the name of the key in the Person Collection.
Person has a key called 'gender'. So for instance, if the 'input' holds the string value 'gender' then the if statement should be true but in my case it comes as false and hence the code inside the if statement is never executed.
Any help/guidance with this will be appreciated.
UPDATE
I searched on mongodb documentation and found here: http://docs.mongodb.org/manual/reference/operator/query/exists/ and also using some help from this thread: (using $exists in Mongo with dynamic key names and the native driver for node)
that I could do something like this:
var checkThis = {};
checkThis[input] = { $exists : true };
var p = Person.findOne(checkThis);
So if it finds one then 'p' holds the record or else it will be undefined. But still the above code does not work.
If I were to put directly:
var p = Person.find({gender: {$exists: true} });
then it works.
So I need assistance in getting the code to work with the variable 'input'.
Mongo is a schemaless database - you can insert any document structure you like into a collection and the data store won't complain. Therefore Person won't be able to indicate which fields conform to the pattern.
The most common way people deal with this problem is to use a package which provides a schema layer on top of mongo. With meteor, a popular choice is SimpleSchema, and its related package AutoForm. SimpleSchema allows you to define which fields should be allowed into a collection, and AutoForm gives you a set of helpers to enforce them in your UI.
If, instead, you prefer not to use a package you could do something like the following:
person.js
var REQUIRED_FIELDS = {
name: String,
gender: ['male', 'female', 'prefer not to say'],
age: Number
};
Person = new Meteor.Collection('person');
Person.isValid = function(person) {
try {
check(person, REQUIRED_FIELDS);
return true;
} catch (_error) {
return false;
}
};
Meteor.methods({
'person.insert': function(person) {
check(person, REQUIRED_FIELDS);
return Person.insert(person);
}
});
my-template.js
Template.myTemplate.events({
submit: function() {
var person = {
name: $('#name').val(),
gender: $('#gender').val(),
age: parseInt($('#age').val(), 10)
};
if (Person.isValid(person))
Meteor.call('person.insert', person);
else
alert('invalid person');
}
});
Here we are using meteor's check package to do some basic field validation. By adding an isValid helper to the Person collection, we can validate the schema without the need for a method call. Best of all we can reuse the same check when inserting a new document.

Resources