Meteor - How To Extract Key Name From Collection? - meteor

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.

Related

Non-reactive and reactive data in Meteor (same helper)

I've got one view displaying some pictures published by users with some data (let's image Instagram).
I already have these pictures as non-reactive data (otherwise you could see many updates) but these images have one button to like the picture. If I have this as non-reactive data I can't see when I click on "Like" the filled heart (I need to refresh).
This is my subscribe function:
this.subscribe('food', () => [{
limit: parseInt(this.getReactively('perPage')),
//skip: parseInt((this.getReactively('page') - 1) * this.perPage),
sort: this.getReactively('sort')
}, this.getReactively('filters'), this.getReactively('searchText'), this.getReactively('user.following')
]);
And this is my helper:
food() {
const food = Food.find({}, {reactive: true}, {
sort: this.sort
}).fetch().map(food => {
const owner = Meteor.users.findOne(food.owner, {fields: {username: 1, avatarS: 1, following: 1}});
food.avatarS = owner && owner.avatarS;
food.username = owner && owner.username;
if (food.likes.indexOf(Meteor.userId()) == -1) {
// user did not like this plate
food.liked = false;
} else {
// user liked this plate
food.liked = true;
}
return food;
});
}
Is possible to have a non-reactive model but with some reactive properties on it?
I'm using Angular 1.X with TS btw
Thanks in advance!
PS: is it normal that this works as non-reactive when I change reactive to true?
Modification to your code:
//console.log(food.likes);
this.subscribe('reactiveFoodData', {ownerId: food.owner, userId: Meteor.userId()}).subscribe(()=>{
console.log(this.user);
});
// THIS IS THE PUBLISH METHOD LOCATED IN THE SERVER SIDE:
Meteor.publish('reactiveFoodData', function(params: {ownerId:string, userId:string) {
const owner = Meteor.users.findOne(params.ownerId);
if (!owner) {
throw new Meteor.Error('404', 'Owner does not exist');
}
let result = {};
result.avatarS = owner.avatarS;
result.username = owner.username;
const food = Food.find({});
result.liked = !(food.likes.indexOf(params.userId) == -1);
return result;
});
You have few problems:
1. The reactive flag is true by default, you do not need to set it.
2. The function find is accepting only two arguments, not 3.
Should be:
const food = Food.find({}, {reactive: true, sort: this.sort})
If you need some, subset of data to be reactive only (from some collection). You could create a specific Method (which udpates only "likes").
https://guide.meteor.com/methods.html
UPDATE:
Here is how you write a method with return parameter (check two examples, with Future and without):
How to invoke a function in Meteor.methods and return the value
UPDATE2:
You have lost reactivity when you used fetch(). Because you moved from reactive cursor to just simple array over which you map values. Do not expect reactivity after fetch(). If you want fetch or do not want to use Cursors, you could wrap the find inside Tracker.autorun(()=>{}) or utilize publish/subscribe.
Note: But be careful, if you somehow manage to get "empty" cursor in find(), your Tracker.autorun will stop react reactively. Autorun works only if it has something to watch over.
The main point with method, is that if you want to have one time non-reactive action for something. You define the method on server:
Meteor.methods({
myMethod: ()=> {
return "hello";
}
});
And you can call it from client with:
Meteor.call('myMethod', (error, result) => {
console.log(result); // "hello"
});
Instead of working with pure collections. You could start using publish/subscribe. On server you publish 'likes' and on client you just listens to this new reactive view. E.g.,
Meteor.publish('likes', (options: {owner: string, likes: Array<any>}) => {
let result: any = {}
const owner = Meteor.users.findOne(options.owner, username: 1, avatarS: 1, following: 1}});
result.avatarS = options.owner && options.owner.avatarS;
result.username = options.owner && options.owner.username;
result.liked = !(options.likes.indexOf(Meteor.userId()) == -1)
return result;
});
On client side: Meteor.subscibe('likes', {food.owner, food.likes}).subscribe(()=>{});
This is just off the top of my head.
Have you tried looking at Tracker ? https://docs.meteor.com/api/tracker.html
But more specifically the method Tracker.nonreactive
https://docs.meteor.com/api/tracker.html#Tracker-nonreactive

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

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.

In Meteor, how to choose a collection based on a variable?

Let's say you want to dynamically insert into different collections. Right now I am using a switch statement:
switch (i) {
case "dog":
Dog.insert({
name: "Skippy"
});
break;
case "cat":
Cat.insert({
name: "Skippy"
});
break;
}
But this is messy, and if I need to support future collections, it fails. Is there a way to choose the collection based on "i" in the example above?
Correct me if I am wrong but I think this is what you are trying to do:
var Dog = {
insert: function(props) {
console.log(props);
}
}
var insertArbitraryDocument = (function(collectionType, props) {
window[collectionType].insert(props)
}).bind(this);
insertArbitraryDocument('Dog', {name: 'skippy'}); //=> {name: 'skippy'}
In this snippet you are accessing the window object and getting the property of whatever name you are passing in (must be exactly the same as the collection). Then you can call your usual function calls.
I don't think there is a meteor built-in way of doing this, but it's pretty easy to just create a directory of collections manually:
JS in common to client and server:
var collections = {};
function myColl(name) {
var coll = new Meteor.Collection(name);
collections[name] = coll;
return coll;
}
// and now just use myColl instead of new Meteor.Collection
Dog = myColl('dog');
And then, to do what you want to do:
collections[i].insert(data);
Here's a complete working example:
Posts = new Mongo.Collection('posts');
Comments = new Mongo.Collection('comments');
var capitalize = function(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
};
var nameToCollection = function(name) {
// pluralize and capitalize name, then find it on the global object
// 'post' -> global['Posts'] (server)
// 'post' -> window['Posts'] (client)
var root = Meteor.isClient ? window : global;
return root[capitalize(name) + 's'];
};
var insertSomething = function(name, data) {
var collection = nameToCollection(name);
collection.insert(data);
}
Meteor.startup(function() {
// ensure all old documents are removed
Posts.remove({});
Comments.remove({});
// insert some new documents
insertSomething('post', {message: 'this a post'});
insertSomething('comment', {message: 'this a comment'});
// check that it worked
console.log(Posts.findOne());
console.log(Comments.findOne());
});
Note this is nearly identical to this question but I simplified the answer for more generic use.

Most efficient way to ensure user owns document on update?

I'm using Meteor methods to update documents so I can share them easier and have more control. However i've ran into a problem with checking ownership.
How should I check to make sure the user calling the update method is the owner of the document? Currently i'm grabbing the document first then running the update.
Is there a better pattern to accomplish this?
Meteor.methods({
'Listing.update': function(docId, data) {
var doc = db.listings.findOne({_id: docId}) || {};
if (doc.userId !== this.userId) {
throw new Meteor.Error(504, "You don't own post");
}
// ensure data is the type we expect
check(data, {
title: String,
desc: String
});
return db.listings.update(docId, {$set: data});
}
});
You don't need the additional db call to fetch the original doc, just make the userId an additional criteria in the update selector. If no doc exists with the correct _id and userId no update will be done. update returns the number of docs updated so it will return 1 on success and 0 on failure.
like this:
'Listing.update': function(docId, data) {
var self = this;
check(data, {
title: String,
desc: String
});
if ( ! self.userId )
throw new Meteor.Error(500, 'Must be logged in to update listing');
res = db.listings.update({_id: docId, userId: self.userId}, {$set: data});
if ( res === 0 )
throw new Meteor.Error( 504, "You do not own a post with that id" );
return res;
}
Also, if you use findOne to check a document's existence, use the fields option to limit what you return from the db. Usually just {fields: {_id:1}}.

Resources