I have the following code:
Meteor.methods({
inPlay: function (id) {
alert(id);
playersCollection.update({_id: id},{
$set: {
age : 30,
username : "Jack"
}
});
}
});
However the above method won't work. The document id is currently in the form of ObjectId("3454....."), and if I change the selector from _id to other property like name then it will work, but I need to use ObjectID.
I have tried many things but nothing works! Any idea?
Related
I've got a question on how to combine data from different Firebase database nodes before displaying it on the frontend. I've got a Firebase database with the following structure. (I'm new to a nosql setup, so this looks more relational):
{
"agents" : {
"-KPCmnwzjd8CeSdrU3As" : {
"contactNumber" : "12345",
"name" : "aaa"
},
"-KPCmw6dKuopDlsMVOlU" : {
"contactNumber" : "123",
"name" : "bbb"
},
"-KPCoWcLecpchcFV-vh_" : {
"contactNumber" : "123",
"name" : "ccc"
},
"-KPROMhPatLjVxMdvfLf" : {
"contactNumber" : "256342",
"name" : "blah"
},
"-KPWIFl5qp5FvAeC3YhG" : {
"contactNumber" : "123",
"name" : "eee"
}
},
"listings" : {
"-KPWKTvW3GzFEIT2hUNU" : {
"agent" : "-KPCoWcLecpchcFV-vh_",
"description" : "third",
"reference" : "REF1"
}
}
}
I'm using Firebase SDK 3.2.0 and AngularFire 2.0.1. In my Angular app I am able to get the list of listings, and for each one look up the agent information. The reason I'm not storing the agent info with the listing is I want the ability to update the agent and the change should reflect on all listings. I don't want to have to go and update all listings if the agent telephone number changes (as an example).
In my controller I have the following:
// get the listings
var listingsRef = firebase.database().ref().child('listings');
vm.listings = $firebaseArray(listingsRef);
// this will move to my ui-router as a resolve but for simplicity's sake
// I added it here...
vm.listings.$loaded().then(function(data){
// loop through the listings...
data.forEach(function(listing) {
if (listing.agent) {
// get the agent for the listing
listing.agent = AgentFactory.getAgent(listing.agent);
}
});
});
Right now the data is displaying correctly on the frontend. There is a slight delay with the agent data showing because of the need of the getAgent promise to resolve.
My questions are:
Is this the correct way of getting the agent data? Should I be looping through the listings and for each query the agent data? How do I wait / keep track of all of the getAgents to resolve?
Any help would be appreciated.
Thanks.
I've structured my data similarly. If you want to wait for all the getAgents to resolve you can use $q.all. I'm not entirely sure what your AgentFactory.getAgent is returning, but let's assume it's a $firebaseObject. If that's the case inject $q and then do the following:
vm.listings.$loaded().then(function (data) {
// loop through the listings...
var promises = [];
data.forEach(function (listing) {
if (listing.agent) {
// get the agent for the listing
listing.agent = AgentFactory.getAgent(listing.agent);
promises.push(listing.agent.$loaded());
}
});
return $q.all(promises);
}).then(function (agents) {
//all agents are loaded
});
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.
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.
Right now I have a working messaging system developed in Meteor where users can send private messages to each other.
The server looks like this:
// .. lot of code
Meteor.publish("privateMessages", function () {
return PMs.find({ to: this.userId });
});
PMs.allow({
insert: function(user, obj) {
obj.from = user;
obj.to = Meteor.users.findOne({ username: obj.to })._id;
obj.read = false;
obj.date = new Date();
return true;
}
});
// .. other code
When the user subscribes to privateMessages, he gets a mongo object that looks like this:
{ "to" : "LStjrAzn8rzWp9kbr", "subject" : "test", "message" : "This is a test", "read" : false, "date" : ISODate("2014-07-05T13:37:20.559Z"), "from" : "sXEre4w2y55SH8Rtv", "_id" : "XBmu6DWk4q9srdCC2" }
How can I change the object to return the username instead of the user id?
You need to do so in a way similar to how you changed username to _id. You can create a utility function:
var usernameById = function(_id) {
var user = Meteor.users.findOne(_id);
return user && user.username;
};
Edit:
If you don't want to poll minimongo for each message, just include username instead of _id inside your message object. Since username is unique, they will be enough.
If in your app you allow users to change username, it might be a good idea to also keep the _id for the record.
In one of larger apps I've been working with we kept user's _id in the model (to create links to profile etc.), as well as cached his profile.name (for display purposes).
I suggest adding the collection helpers package from atmosphere. Then create a helper for PMs called toUser that returns the appropriate user.
Then you can get the name using message.user.name.
Env: Titanium 3.1.3, Alloy 1.2.2.
I'm using the following adapter for persistence on the models/collections: https://github.com/viezel/napp.alloy.adapter.restapi
I have an API that has a different URL structure for a collection than it does a single model. Consider the following:
To get a single record: [GET] /files/:id
To get all the files for a user: [GET] /users/:id/files
I have the following schema for files.js:
exports.definition = {
config: {
"URL": "https://my.api.here/files",
//"debug": 1,
"adapter": {
"type": "restapi",
"collection_name": "files",
"idAttribute": "id"
}
},
extendModel: function(Model) {
_.extend(Model.prototype, {});
return Model;
},
extendCollection: function(Collection) {
_.extend(Collection.prototype, {
initialize: function(){
this.url = "http://my.api.here/users/"+this.user_id+"/files";
}
});
return Collection;
}
}
What I'm trying to do in the above is override the collection initialize method to change the URL structure for the collection. I then call this accordingly:
var currentUserFiles = Alloy.createCollection("files", {user_id:"12345"});
currentUserFiles.fetch({
success: function(files){
console.log("Woo! Got the user's files!");
console.log(JSON.stringify(files.models));
},
error: function(){
console.log("Nope");
}
});
This doesn't work. The fetch() method just continues to try to call /files. I've tried setting url as a property on the collection after it's created, that also don't work.
Ideally, I'd like to do this for both local instances as well as the singleton version of the collection.
So - the question is: can I utilize a different URL for a collection than I do for a model? Obviously, I don't want to just call /files and sort/filter client-side - that'd be a nightmare with a lot of records. What am I missing here?
It's a bit late but for anyone else that comes across this. I problem is where/how the url is specified for model and collection. The model needs a specific id (eg: primary key) passed into it because the model can only be one object. If you need more than one object, then use the collection. Hope this helps :)
extendModel : function(Model) {
_.extend(Model.prototype, {
url : function() {
return "http://my.api.here/users/"+this.user_id+"/files/"+ FILE_ID
},
});
return Model;
},
extendCollection : function(Collection) {
_.extend(Collection.prototype, {
url : function() {
return "http://my.api.here/users/"+this.user_id+"/files"
},
});
},