Meteor: publish intersection of find - meteor

I have a collection as below:
Schema.Place = new SimpleSchema({
type: {
type: String,
autoValue: function(){ return 'Point'; }
},
coordinates: {
type: [Number],
decimal:true,
},
});
Schema.Direction = new SimpleSchema({
_id: {
type: String,
optional: true,
},
from: {
type: Schema.Place,
},
to: {
type: Schema.Place,
}
});
I then want to query the same directions according to the from and to points. The first problem is that I can't query two geo index in the very same query so I do as follow:
Meteor.publish('Directions', function(direction){
var ids = Ride.find({
active: true,
from: {
$near: {
$geometry: {
type: 'Point',
coordinates: direction.from.coordinates,
},
$maxDistance: 5000,
}
}
}).map(function(item){return item._id});
return Ride.find({
_id: {$in: ids},
to :{
$near: {
$geometry: {
type: 'Point',
coordinates: direction.to.coordinates,
},
$maxDistance: 5000,
}
}
});
});
Problem is that publish loses its reactivity due to the double query filtering...
I have some ideas how to do the job but it seems pretty weird to me there is no better way to do so:
Publish the first query and do the filtering on client (but not sure
$near is supported client side though)
Use observeChanges in the publish function
Use a meteor method in publish function (I've seen it in an old discussion but not sure whether it is possible or relevant to my problem).

Related

Meteor collections: upsert w/ array

Forms of this question have been asked a few times, but I've been unable to find a solution:
I have a schema like this (simplified):
StatusObject = new SimpleSchema({
statusArray: [statusSchema]
});
where statusSchema is
{
topicId:{
type: String,
optional: true
},
someInfo:{
type: Number,
optional: true,
decimal: true
},
otherInfo:{
type: Number,
optional: true
}
}
I am trying to upsert - with the following meteor method code:
var upsertResult = BasicInfo.update({
userId: this.userId,
statusArray: {
$elemMatch: { topicId : newStatus.topicId }
}
}, {
$set: {
"statusArray.$.topicId": newStatus.topicId,
"statusArray.$.someInfo": newStatus.someInfo,
"statusArray.$.otherInfo": newStatus.otherInfo
}
}, {
multi: true,
upsert: true
});
But I keep getting an error: statusArray must be an array
I thought by adding the $, I was making sure it is recognized as an array? What am I missing?
It seems (after your clarification comments), that you want to find a document with particular userId and modify its statusArray array using one of these scenarios:
Update existing object with particular topicId value;
Add a new object if the array doens't have one with particular topicId value.
Unfortunately, you can't make it work using just one DB query, so it should be like this:
// try to update record
const updateResult = BasicInfo.update({
userId: this.userId,
'statusArray.topicId': newStatus.topicId
}, {
$set: {
"statusArray.$": newStatus
}
});
if (!updateResult) {
// insert new record to array or create new document
BasicInfo.update({
userId: this.userId
}, {
$push: {
statusArray: newStatus
},
$setOnInsert: {
// other needed fields
}
}, {
upsert: true
});
}
Your code is treating StatusArray as an object,
Before you do the upsert, build the status array first, assuming that your current value is currentRecord
newStatusArray = currentRecord.statusArray
newStatusArray.push({
topicId: newStatus.topicId,
someInfo : newStatus.someInfo,
otherInfo: newStatus.otherInfo
})
and in the upsert, simply refer to it like this
$set: { statusArray: newStatusArray}

Difference between defaultValue and autoValue in Autoform?

I'm working on a project which I start with using autoValue as
Programs.attachSchema(new SimpleSchema({
createdBy: {
type: String,
autoValue: function() {
return this.userId
},
optional: true,
autoform: {
type: 'hidden'
}
},
createdAt: {
type: Date,
label: "Created At",
defaultValue: new Date(),
optional: true,
autoform: {
type: 'hidden'
}
}
}));
everything works find until I need to update the information by other users, let's say admin, Programs.update or Programs.insert methods will change the email field.
I tried to use defaultValue for createdBy field but
defaultValue: this.userId
return me null
and i'm not allowed to use
defaultValue: Meteor.userId()
Can anyone explain the difference? I tried use function() {return this.userId} for defaultValue which still got no luck
defaultValue is used by simple-schema for defining default value. There are some quirks so read the docs: https://github.com/aldeed/meteor-simple-schema#defaultvalue
Think of when the code is ran and you will understand why you can't use Meteor.userId() or this.userId for defaultValue. The schema is ran once at startup.
What allows autoValue to work is that it returns a function. The function is ran during db updates/inserts. Read over the docs to fully understand it: https://github.com/aldeed/meteor-simple-schema#autovalue
Now, if I understand your question properly, you have issues with autoValue when an admin comes along and modifies the document? Causing the createdBy to be set to the admin's id? To solve something like that, you just need to be more specific with your autoValue function.
See if this code helps guide you in the proper direction:
Programs.attachSchema(new SimpleSchema({
createdBy: {
type: String,
autoValue: function() {
if (this.isInsert) {
return this.userId;
} else if (this.isUpsert) {
return { $setOnInsert: this.userId };
}
this.unset(); // Prevent user from supplying their own value
return undefined;
},
optional: true,
autoform: {
type: 'hidden'
}
},
createdAt: {
type: Date,
label: 'Created At',
defaultValue: new Date(),
optional: true,
autoform: {
type: 'hidden'
},
autoValue: function() {
if (this.isInsert) {
return new Date();
} else if (this.isUpsert) {
return { $setOnInsert: new Date() };
}
this.unset(); // Prevent user from supplying their own value
return undefined;
},
}
}));
You should try this snippet,
new SimpleSchema({
// ...
createdBy: {
autoValue() {
return Meteor.userdId();
}
}
// ...
})
Now the explanation, Your problem is more likely related with the this binding, this.userId, was called from SimpleSchema context in this way this does not have any userId() method, you should use the full namespace in this case Meteor.userId();
A very cool explanation on this binding I recommend you to read
This binding

selectize does not work in meteor

Can any one tell me why this work in meteor:
"landTenancyType" : {
type: String,
optional: true,
autoform: {
type: "selectize",
options: function(){
return [
{label: "Joint", value: "Joint"},
{label: "Tenancy In Common", value: "Tenancy In Common"}
]
}
}
}
but this does not work:
"landTenancyType" : {
type: String,
optional: true,
autoform: {
type: "selectize",
options: function(){
return Categories.find().map(function(obj) {
console.log(obj);
return { label: obj.name, value: obj.name };
});
}
}
}
All the necessary publish and subscribe are working. Console does also show that values are coming from the collection. However a blank selectize ui is killing me. If i change type: "selectize", to type: "select", the select list is populated but i do not have the selectize goodness i need. Any ideas what I am doing wrong?
By the way I am using meteor with autoform 5.0 and comerc:autoform-selectize.
I think the problem is in this line:
Categories.find().map(function(obj)
find returns a cursor, you could do find().fetch() to get an array. Then map would work on that array.
Can you return object wittin another objects from a function ? A function will always return a single entity (either single value or single object). Please re-check your code's following section:
options: function(){
return Categories.find().map(function(obj) {
console.log(obj);
return { label: obj.name, value: obj.name };
});
}

meteor - How to add a subdocument as reference with SimpleSchema

I have the following SimpleSchema
Schema.Team = new SimpleSchema({
name:{
type:String
},
members: {
type: [Schema.User],
optional:true
}
});
I would like to insert (on the server) a new team document with the current user, as a reference (not as an embedded document).
I have tried:
Teams.insert({name:"theName",members:[Meteor.user()]}) // works but insert the user as an embedded doc.
Teams.insert({name:"theName",members:[Meteor.user()._id]}) // Error: 0 must be an object
I have also tried in two steps:
var id = Teams.insert({name:teamName});
Teams.update({ _id: id },{ $push: { 'users': Meteor.user()._id } });
Then I have another error I don't understand: Error: When the modifier option is true, validation object must have at least one operator
So how can I insert a document with a reference to another schema?
If you just want to store an array of userIds in your Team collection try:
Schema.Team = new SimpleSchema({
name:{
type:String
},
members: {
type: [String],
optional:true
}
});
Then
Teams.insert({ name: "theName", members: [Meteor.userId()] });
Should work. Later when you want to add an additional id you can just:
Teams.update({ _id: teamId },{ $addToSet: { members: Meteor.userId() }});
The following is probably the syntax you are after, assuming you are also using AutoForm.
If you are using collection2, you can also add an autovalue for when a team is created to automatically add the creator to that team for more convenience.
Schema.Team = new SimpleSchema({
name: {
type:String
},
members: {
type: [String],
defaultValue: [],
allowedValues: function () {
// only allow references to the user collection.
return Meteor.users.find().map(function (doc) {
return doc._id
});
},
autoform: {
// if using autoform, this will display their username as the option instead of their id.
options: function () {
return Meteor.users.find().map(function (doc) {
return {
value: doc._id,
label: doc.username // or something
}
})
}
},
autoValue: function () {
if (this.isInsert && !this.isFromTrustedCode) {
return [this.userId];
}
}
}
});

Any way to have Simple Schema in Meteor validate a specific array index?

From what I understand in the docs, you can define your schema like this:
MySchema = new SimpleSchema({
//This says that the addresses key is going to contain an array
addresses: {
type: [Object],
},
// To indicate the presence of an array, use a $:
"addresses.$.street": {
type: String,
},
"addresses.$.city": {
type: String,
}
});
Ok, I get this part. But what if I wanted to validate the contents in a specific array index? I want something like this:
MySchema = new SimpleSchema({
//This says that the itemsOrdered key is going to contain an array
itemsOrdered: {
type: [Object],
},
// Here I want to validate certain indexes in the array.
"itemsOrdered.0.sku": {
type: String
},
"itemsOrdered.0.price": {
type: Number
},
"itemsOrdered.1.sku": {
type: String
},
"itemsOrdered.1.price": {
type: Number
},
"itemsOrdered.1.quantity": {
type: Number
},
"itemsOrdered.2.sku": {
type: String
},
"itemsOrdered.2.price": {
type: Number
},
"itemsOrdered.2.customerNotes": {
type: String
optional: true
}
});
So here I'm trying to validate the values inside array index 0, 1, and 2. Each array index has a different item that has been ordered.
Normally I would use a hash table data structure, but for this purpose I need to preserve order which is why I'm using an array.
When I try to run this code I get an error Cannot read property 'blackbox' of undefined
Have you considered custom validation?
https://github.com/aldeed/meteor-simple-schema/blob/master/README.md#custom-validation
According to the doc within the function the key property of this will provide the information you want. So you could have something like:
MySchema = new SimpleSchema({
//This says that the itemsOrdered key is going to contain an array
itemsOrdered: {
type: [Object],
},
// Here I want to validate certain indexes in the array.
"itemsOrdered.$.sku": {
type: String,
custom: function () {
var key = this.key,
re = /\d+/;
var index = Number(key.match(re)[0]);
// Do some custom validation
}
},
"itemsOrdered.$.price": {
type: Number
},
"itemsOrdered.$.quantity": {
type: Number,
optional: true
},
"itemsOrdered.$.customerNotes": {
type: String,
optional: true
}
});
Here I put the validation logic in the sku field since it's required.

Resources