How do I use ObjectID from a mongoimport? - meteor

I use a mongoimport to import a bunch of large csv files into a meteor collection, however when they do the insertions the _id values are ObjectID, whereas meteor uses string ids. There's a small blurb on ObjectIDs in the meteor docs here but I don't really understand what I am supposed to do. For example, using Iron Router I have a single route like so
this.route('profileView', {
path: '/profiles/:_id',
notFoundTemplate: 'notFound',
fastRender: true,
waitOn: function() {
return [Meteor.subscribe('singleProfile', this.params._id, Meteor.userId())];
},
data: function() {
Session.set('currentProfileId', this.params._id);
return Profiles.findOne({
_id: this.params._id
}, {
fields: {
submitted: 0
}
});
}
but the url of the route is of type object and looks like http://localhost:3000/profiles/ObjectID(%22530845da3621faf06fcb0802%22). It also doesn't return anything and the page renders blank. Here's the publication.
Meteor.publish('singleProfile', function(id, userId) {
return Profiles.find({
_id: id,
userId: userId,
forDel: {
$ne: true
}
});
});
I guess my question is, how am I supposed to use ObjectIDs so that the routes use just the string portion of the ObjectID, and how do I return the data properly?
Update: I've managed to get the ObjectID out of the url by changing the link to the view from Details to Details so the url is now http://localhost:3000/profiles/530845da3621faf06fcb0802. Unfortunately, the page still renders blank, and I am not sure if that's because of the way I am subscribing, publishing, or finding the collection item.

Summing up the comment thread as an answer:
The string part of the ObjectID can be obtained by simply calling ._str on the id as
id._str
You can also craft an ObjectID from a hex string by using
new Meteor.Colletion.ObjectID(hexstring)
So when you access your route using Details you can craft your find like:
Profiles.findOne({
_id: new Meteor.Collection.ObjectID(this.params._id)
});
Generally speaking, when working with ObjectID's, you will find yourself needing some antipatterns to convert from string to objectId or vice versa, so a utility like the following will come in handy:
IDAsString = this._id._str ? this._id._str : this._id
and
IDAsObjectId = this._id._str ? this._id : new Meteor.Collection.ObjectID(this._id)
Please also take a look at github.com/meteor/meteor/issues/1834 and groups.google.com/forum/#!topic/meteor-talk/f-ljBdZOwPk for pointers and issues around using ObjectID's.

Related

How do I handle values that are dependent on the result of a helper?

So I have an Angular controller with a meteor helper method, as below.
function localeCtrl($scope, $reactive, $stateParams{
$reactive(this).attach($scope);
var self = this;
self.helpers({
locale: function(){ return Locales.findOne($stateParams.id)},
staff: function(){
// Load data from second collection based on current Locale.
// But how?
},
address: function(){
// Take self.location.address and massage it to provide
// google maps link. How?
}
tags: function(){
// Collect all unique instances of a given tag by
// iterating over the available locales.
// E. G. If 10 locales have the 'restaurant' tag, and 5
// more have the 'library' tag, I want an array of
// ['restaurant', 'library'] -- easy enough to do
// by iterating over the locales, but how do I do that
// reactively?
}
});
}
Unfortunately, I need to set additional properties based on the data fetched by locale(). I can't set them up when I initialize the controller because the value in locale() changes as data is fetched from the server. But I need access to the data in locale to, for example, create the google maps address, or fetch associated records. (They aren't imbedded in the locale document for reasons that I'm sure made sense at the time).
Edit:
Additionally, I'm using ground DB to store a local copy of the data for offline access, which makes life even more complicated.
Probably you best bet is to publish your collection using publishComposite which is implemented using the reywood:publish-composite package.
Add the package:
meteor add reywood:publish-composite
Now where you publish the Locales collection you would do something like this:
Meteor.publishComposite('locales', function() {
return {
find() {
//Put whatever you need in the query for locales
const query = {
_userId: this.userId
};
return Locales.find(query);
},
children: [{
find(locale) {
return Staff.find({ localeId: locale._id });
}
}]
};
});
Then in your controller before the helper you add this:
this.subscribe('locales');
Now you should be able to simply call the code like this:
this.helpers({
locale(){
return Locales.findOne(this.$stateParams.id);
}
});
And access it in the template like this:
locale.staff
Give that a try and let me know!

Meteor observeChanges vs manual update

I have a simple todo schema: (just a sample to draw my question)
{
title: {
type: string
},
value: {
type: string
},
author: {
type: object
},
"author._id": {
type: string
},
"author.firstName": {
type: string
},
"author.lastName": {
type: string
},
}
The author entries are from meteor.user. If the meteor user changes the firstName or lastName i have to update the todo. I have two possibilities:
observerChanges (server side) to users collection and update all todos from this user with the new firstname/lastname
if i call the user update method i can call a method to update all todos
when it's better to use cursor.observeChanges and when it's better to call a update method manual? And why?
As the comment says, you should not store the author name / email in the document if it is mutable:
Store the ID of the user only in the document, the UserID is immutable.
When building your ToDo template, look up the User information by ID: you would need to publish a Publication for user by Id, and subscribe to it on the client with the userId as parameter.
Meteor.publish('userById', function(userId) {
return Meteor.users.find({_id: userId}, {limit:1});
});
in your route / template.onCreated depending on your Router, assuming the document is called doc
this.subscribe('userById', this.doc.author._id);
in the template helper
Template.todoTemplate.helpers({
'Author': function() {
return Meteor.users.findOne({_id: this.doc.author._id});
}
});
and call the Author info in the template
<Template name="todoTemplate">
First Name: {{Author.first_name}}
Last Name: {{Author.last_name}}
</Template>
I think you shouldn't rely on the second method, because sometimes you (or your teammate) might forget to update it. Moreover, if you're denormalizing user data in other collections, users knowing Meteor might just call your Meteor.method or manipulate db from the browser console...
You can use this package:
meteor add matb33:collection-hooks
It adds some hooks to your mongo insert/update/remove call
For example:
Meteor.users.after.update(function (userId, doc, fieldNames, modifier, options) {
if (this.previous.firstName === doc.firstName && this.previous.lastName === doc.lastName) {
return;
}
Todos.update({'author._id': doc._id}, {
$set: {
'author.firstName': doc.firstName,
'author.lastName': doc.lastName,
}
})
}, {fetchPrevious: true})
(To update the Todos collection efficiently, make sure to add index to author field)
This is just a handier way than writing your own observeChanges, and better than manually updating Todos collection every time you update the users collection, because you might forgot to call it in some case, or some hacker user just calls Meteor.users.update(Meteor.userId(), {...}) perhaps...
But still, I think you should always add some auto-correct mechanism to avoid wrong data being displayed, because no matter which method you choose, some error will occur (maybe the server watching the db just crashes right after users update). You can check on the client side when displaying content, if author.firstName doesn't match Meteor.users.findOne(author._id) (but you have to publish the user though...), than call a method to tell the server to update it.

Datatype validation error - MeteorJS/Autoform/SimpleSchema/Embedded Simple Schema

I am using Simple Schema,collection hooks and Autoform packages in Meteor and I am trying to update a Embedded object in my schema in the after update collection hook. I feel I might doing something silly, but was just unable to solve this problem. I am getting the exeption while saving: Exception while invoking method '/providers/update' Error: 0 must be an integer
My schema:
Schemas.Providers = new SimpleSchema({
email: {
type: String,
label: 'Email Address',
autoValue: function() {
if (this.isInsert || this.isUpdate) {
return Meteor.user().emails[0].address;
}
},
autoform: {
afFieldInput: {
type: 'email'
}
}
},
officelocation: {
type: String,
label: 'Location of Office',
optional:true
},
location: {
type: [LocationSchema],
optional:true
}});
The collection hook that works:
Providers.after.update(function (userId, doc) {
var oldDoc = this.previous;
Providers.direct.update({_id: doc._id},{$set: {location:{
type:'Point',
coordinates:[0,0]
}}});
});
The collection hook that does not work.. Ideally I should not be updating after the collection update, but wanted to make sure this works:
Providers.after.update(function (userId, doc) {
var oldDoc = this.previous;
var array=[];
var iArray=doc.officelocation.split(",");
array.push(Number(iArray[1]));
array.push(Number(iArray[0]))
Providers.direct.update({_id: doc._id},{$set: {location:[{
type:'Point',
coordinates:array
}]}});
});
Looking at what you are trying to store, use parseInt instead of Number that you are using. It will return an integer that mongo can store.
The issue is not with the method that you are using. It is with the way you are storing data in mongo. Show us how your LocationSchema looks like.
Detail:
Mongo uses a different number format that what javascript uses. In javascript, a Number can be an integer, a float, a decimal or anything that you want. Mongo has very strict demands when it comes to integer or floats.
Overall, what it means is that if you want to store an accurate decimal number in mongo (which I suspect what you are trying to do), you have to either store it as a string (you loose the ability to do direct mongo operation such as $inc $gt etc) or divide it into two parts and store separably. The third option is to store it as a float which isn't accurate an is only useful if you want some kind of approximate value.

How do I use the results of a subscription for additional subscriptions at the route level?

I have a template that displays documents from three different collections Cars, CarPaints, and CarPaintTypes. I know I need all these upfront at the Router level. The template will show a Car document, all the CarPaints that reference that Car, and all the CarPaintTypes that reference the returned CarPaints respectively (think nested list). The route to the template takes an id from the URL that represents Car._id.
Both the Cars collection and CarPaints collection make use of the Car._id as a field (it's the native _id of the Cars collection and a field in the CarPaints collection) so that's easy. However, the CarPaintTypes uses the CarPaint._id as a reference to what CarPaint it belongs to.
So I have three publications:
Meteor.publish('car', function(carId) {
return Cars.find({_id: carId});
});
Meteor.publish('carPaints', function(carId) {
return CarPaints.find({carId: carId});
});
Meteor.publish('carPaintTypes', function(carPaintId) {
return CarPaintTypes.find({carPaintId: carPaintId});
});
My route looks like:
this.route('car', {
path: '/car/:_id',
waitOn: function() {
return [Meteor.subscribe('car', this.params._id),
Meteor.subscribe('carPaints', this.params._id)];
// Can't figure out how to subscribe to or publish
// the carPaintTypes using all the results of what gets
// returned by 'carPaints'
}
});
My question is CarPaintTypes doesn't have the Car._id as a field, just the CarPaint._id to reference to a CarPaint document. Where and how I do take the results of the subscription to carPaints and pass each carPaint document that's returned to a subscription to carPaintTypes? Or is there a way to combine them all in the publication? Is it better to do it later on in my helpers? I figure since I know what I need at the route level, all the subscription calls should be in the route code.
You can grab all 3 cursors inside Meteor.publish method and simply return them:
Meteor.publish('carThings', function(carId){
var carPaint = CarPaints.findOne({carId:carId});
return [
Cars.find({_id: carId}),
CarPaints.find({carId: carId}),
CarPaintTypes.find({carPaintId: carPaint._id});
]
})
On client:
this.route('car', {
path: '/car/:_id',
waitOn: function() {
return [Meteor.subscribe('carThings', this.params._id)]
}
}
With Kuba Wyrobek's help, I figured it out. For what I was trying to achieve, the publish looks like this:
Meteor.publish('carThings', function(carId){
var carPaints = CarPaints.find({carId: carId}).fetch();
return [
Cars.find({_id: carId}),
CarPaints.find({carId: carId}),
CarPaintTypes.find({carPaintId: {$in: _.pluck(carPaints, "_id")}})
];
});
I didn't get that you could do manipulations inside your publication blocks. This is super cool and flexible. Thanks for your help.

How to define routes that include the current user with Iron Router?

I'm fairly new to Meteor and Iron Router and I'm trying to create a route that looks like this '/users/hippomoe/bookmarks/Kj6ecNk7DeW7PmmGA' where hippomoe would be the current logged in user and Kj6ecNk7DeW7PmmGA would be the id of a bookmark. Some of the things that I've tried were to add a handlebars helper that would provide the username
bookmark: function() {
return {
_id: this._id,
user: Meteor.user().username
};
}
and I also tried defining user: Meteor.user().username in the data context within the RouteController definition. In both instances I get the following error: "You called Route.prototype.resolve with a missing parameter. "user" not found in params"
I've tried finding an example that illustrates this type of route (which I assume would be common). The other questions that I have seen on StackOverflow related to this were about getting the user associated with a particular document and not the current user that is logged in.
It seems that I missing something simple. Can someone provide/point me to a simple example of how to achieve this?
I'm having difficulty understanding why you would need such a route. If you already know the name of the logged in user, why would you need to have it once more in the route? Or is this meant to be server-side? In that case, please show your routes.
Otherwise, does this not work:
Router.map(function () {
this.route('postShow', {
path: '/users/:ignoreme/bookmarks/_id',
data: function () {
var params = this.params;
var user = Meteor.user().username;
if (user == params.ignoreme) {
...
} else {
return "something went wrong";
}
}
});
});
This is just to allow the url pattern you want, but again, I don't see why you wouldn't just use a URL /user/bookmarks or similar and then change the logic to depend on Meteor.user().
The handlebars helper that I had written in my question
bookmark: function() {
return {
_id: this._id,
user: Meteor.user().username
};
}
would have worked if I would have specified the pathFor syntax correctly. My pathFor should have looked like this
{{pathFor 'saveBookmark' bookmark}}
I had inadvertently left off bookmark!
I think you are missing a ':' before _id, try this:
path: '/users/:ignoreme/bookmarks/:_id',

Resources