In Meteor, how can I publish a single object from an array field? - meteor

I have the following Mongo collection:
{
id: '123456',
name: 'GameXYZ',
reviews: [
{createdBy: 'Bob', score: 5}, {createdBy: 'John', score: 8}
]
}
I would like to create a publish function that returns only the review created by Bob:
{
reviews: [
{createdBy: 'Bob', score: 5}
]
}
I've tried this:
return myCollection.find({'reviews.createdBy': 'Bob'}, {'reviews.$': 1});
The problem is Meteor returns the entire document. According to their documents, "Field operators such as $ and $elemMatch are not available on the client side yet."
My function is running on the server, so I don't know why it's not working. It does work on the Mongo Shell.
My question is: could anyone recommend a way to publish only that single object of the array, in Meteor?

This is best done like the comments example from discover meteor. Create new collection reviews. You can put what ever you need in it but it has to have the id of what ever it's a review of. That way you can publish and find it with reviews.find({reviewsId: the id of the collection; in this case 123456})

Related

NoSQL Database Structure

For a small app prototype I'm building, I would like to use firestore (firebase) to store some data. I'm wondering if the following is a good way of going about a nosql database.
I have Paths, that belong to Categories. A Path can have courses and comments. I would like users to see the likes a path has, and get categories sorted by the amount of paths inside.
That's why I'm adding the paths_count on the categories table, I will use Cloud Functions to update the counts for the likes and paths on each database update.
categories: [
1: {name: "productivity", paths_count: 10},
2: {name: "cooking", paths_count: 5},
]
paths: [
1: {
name: "Productivity 101",
category_id: 1,
likes_count: 5,
likes: [],
courses: [],
comments: []
}
]
Is this a good start?

Firebase Firestore comment tree architecture

I'm trying to implement a Reddit/HackerNews style tree of comments as part of a project, and am trying out Firestore as a database solution. However, I'm unsure as to the correct design reading through the docs. In a SQL database I would use numeric keys like:
0
1.0
1.1
1.1.1
0.0
to represent my tree. However, numeric keys like that seem to be a Firebase antipattern. The other route is using an actual tree in the json where a post is represented like:
{
uid: 'A0000',
content: 'foo',
children: [
{uid:..., content:..., children: []}]
}
but supposedly deep trees are bad in Firestore. As I understand it the reason deep trees are bad is that you have to fetch the whole thing, but in my case I'm not sure if that's a problem. A client fetching a post would fetch the root content node and the first 20 or so child trees. That could be a pretty big fetch, but not insanely so.
Does anyone know of a good standard way to implement this kind of structure?
Extra: Here is the more verbose expression of what the structure should look like once the client processes it.
{
uid: 0,
title: 'Check out this cat!',
body: 'It\'s pretty cute! This **text** is [markdown](link), so it can have ' +
'links and *stuff*. Yay!',
poster: {
uid: 0,
name: 'VivaLaPanda',
aviUrl: 'badlink',
},
posted: '2018-03-28',
children: [{
uid: 0,
body: 'This is a comment, it\'s angry!',
poster: {
uid: 0,
name: 'VivaLaPanda',
aviUrl: 'badlink',
},
posted: '2018-03-20',
children: [{
uid: 0,
body: 'This is a comment, it\'s neutral!',
poster: {
uid: 0,
name: 'Steve',
aviUrl: 'badlink',
},
posted: '2018-03-20',
children: [{
uid: 0,
body: 'This is a comment, it\'s neutral!',
poster: {
uid: 0,
name: 'Craig',
aviUrl: 'badlink',
},
posted: '2018-04-10',
children: []
}, ]
}, ]
},
{
uid: 0,
body: 'This is a comment, it\'s happy!',
poster: {
uid: 0,
name: 'Craig',
aviUrl: 'badlink',
},
posted: '2018-03-28',
children: []
},
]
};
Edit:
While I've marked this as answered because there is an answer, I'm still really interested in seeing something more elegant/efficient.
Edit2:
For posterity: I ended up deciding that any Firebase solution was hopelessly convoluted and just used DGraph for the data, with Firebase sitting in front for Auth.
This is tough since the structure you have is naturally recursive. The obvious options are each comment is a new document in a collection and each reply is a single document in the same collection.
Each comment as a new document could work something like this. Each comment has a "postId" attribute which dictates which post it belongs to. Some comments, those which are replies to other comments, have a "replyToId". These two attribute in conjunction allow your client app to:
Get the top level comments (look for comments with the correct postId and which don't have replyToId). Top level comments allows you to limit the size of payloads if you need to worry about that in the future.
Get all comments (look for comments with the correct postId only). If you don't care about payload sizes you can get everything and figure out the tree structure on the client.
Get replies to a particular comment if you want "see replies" YouTube style comments interaction (looks for comments which have a particular replyToId). This works well in conjunction with 1. for limiting payload sizes.
But the logic here is obviously complex.
Your children approach could be really messy if there are a lot of people commenting on eachother. A nicer approach would be the following structure for every comment:
// single comment
postUid // <- random generated by firebase
{
postedBy: userUid
postedTime: timestamp
postIsChildOfUid: postUid // <- reference to an other post (optional if the comment didn't respond to another comment(top-level comment))
}
This doesn't even require nesting at all :). You can generate easily now a comment tree with this approach, but this has to be client side. But that should be easy!

Publishing role names into a table Meteor alanning:roles and aldeed:tabular

I'm having trouble displaying the created roles in a table. I'm using alanning:roles and aldeed:tabular. To create the table I have:
TabularTables.RolesAdmin = new Tabular.Table({
name: "Roles",
collection: Meteor.roles,
pub:"rolesadmin",
allow: function (userId) {
return Roles.userIsInRole(userId, 'admin');
},
columns: [
{
data: "name",
title: "Role Name",
},
],
});
And the publication looks like this:
Meteor.publish( 'rolesadmin', function() {
return Meteor.roles.find( {}, { fields: { "name": 1 } } );
});
When running the app the table only displays "Processing..." thus there is an error and it is not able at access/find the data?
I'm getting the following exception in the server terminal:
Exception from sub rolesadmin id 6c6x3mDzweP8MbB9A
Error: Did not check() all arguments during publisher 'rolesadmin'
If I check in mongo db.roles.find(), there is no role with 6c6x3mDzweP8MbB9A id. What does this error refer to?
From the meteor-tabular docs:
To tell Tabular to use your custom publish function, pass the
publication name as the pub option. Your function:
MUST accept and check three arguments: tableName, ids, and fields
MUST publish all the documents where _id is in the ids array.
MUST do any necessary security checks
SHOULD publish only the fields listed in the fields object, if one is > provided.
MAY also publish other data necessary for your table
So it looks like you'll need to account for those three arguments mentioned in the documentation appropriately. I'm not sure you actually need a custom pub for this, though, based on what you are publishing.

With Meteor, how can I update a document based on MongoDB's ObjectID()?

I created a child array of objects in a document. Each of these array objects have:
children: [
{
_id: ObjectID("lkajsdflkajdsf"),
title: "Something"
}, ...
]
I'm getting ObjectId undefined error when trying to update a document:
Category.update(
{ "_id": "C2Rcjivw96htJSHRq", "children._id": ObjectId("1c46382a25d3888165dd338a") },
{ "$set": { "children.$.title": "Hello World" }}
);
As you can see I'm attempting to update a specific array object by it's associated _id. This does not work. I was reading this thread: Meteor collection update with traditional id
But it's a little outdated, and I'm also getting errors when trying to use it.
Is there a solid method for handling things in this fashion? I can do this in Mongo shell with no problem, but not through Meteor methods.
Thanks!

Adding more fields to Meteor user accounts

I am using mrt add accounts-ui-bootstrap-dropdown and mrt add accounts-password to get a simple login page running on my app.
The accounts users gives me a nice hash containing ids, createdAt, emails, etc.
If I wanted to add other fields in this hash so I can make use of them later, how would I do that? For example, I want then to also enter their given name and surname:
"given_name": "John", "surname": "Doe"
Users are special objects in meteor ; you don't want to add fields in the user but in the users profile.
From the doc :
By default the server publishes username, emails, and profile.
If you want to add properties like surname when you create the account, you should use in the Account.onCreateUser server-side hook : http://docs.meteor.com/#accounts_oncreateuser
Accounts.onCreateUser(function(options, user) {
//pass the surname in the options
user.profile['surname'] = options.surname
return user
}
If you want to update a user after, you can do it from the client that way :
Meteor.users.update({_id:Meteor.user()._id}, { $set: {what you want to update} });
By default, the users base will allow that (the current user may update itself). If you don't trust your users and want to ensure that everything is properly update, you can also forbid any updates from the client and make them via a Meteor.call() and proceed to the checkings server-side. But this would be sad.
Edit :
As said in the comments, adding options via the standard account-ui won't be possible. You'll only be able to update the user after the registration. To add options when you subscribe, you'll have to make you own form.
I won't insult you by writing html markup, but here is what you want to have after the submit event (and after the various checking) :
var options = {
username: $('input#username')[0].value,
emails: [{
address: $('input#email')[0].value,
verified: false
}],
password: $('input#password')[0].value,
profile: {
surname: $('input#surname')
},
};
Accounts.createUser( options , function(err){
if( err ) $('div#errors').html( err.message );
});
You only need the account-base package ; not the account-ui.
Login with the social networks is cake :
Meteor.loginWithFacebook({
requestPermissions: ['email', 'user_birthday', 'user_location']
}, function(error){loginCallBack(error);});
About the answer ram1 made :
This is not the way meteor works. You do not "POST" a form. You want all your client / server communication done via the websocket. The equivalent of what you are talking about is making a "Meteor.call('myserverfunction', myarguments, mycallback)" of a server method from the client and you pass the arguments you want the server to use.
But this is not the way you will get the best of meteor. There is the philosophy you want to work with :
you have datas in your local mini mongo you got from the server
you update locally those datas in your base / view
meteor do his magic to transmit those updates to the server
there the server can answer : ok, updates saved, this is seamless for you. Or answer : nop ! reverse the changes (and you can implement an error notification system)
(it can answer no because you don't have the permission to update this field, because this update break a rule you did set up...)
All you do is setting permissions and controls on the databases server-side. That way, when an honest client make an update, he sees the result instantly ; way before it has been pushed to the server and send to the other clients. This is latency compensation, one of the seven principles of meteor.
If you modify a data via Meteor.call, you will do that :
send an update to the server
the server checks and update the base
the server send the update to the clients (including you)
your local base updates and your view update => you see your update
=> this is what you had in yesterday app ; meteor allow you to build a today app. Don't apply the old recipes :)
The accepted answer has the HOW right, but the WHERE is outdated information. (Yes, this would be better as a comment on the answer, but I can't do that yet.)
From the Meteor 1.2 documentation:
The best way to store your custom data onto the Meteor.users collection is to add a new uniquely-named top-level field on the user document.
And regarding using Meteor.user.profile to store custom information:
🔗Don’t use profile
There’s a tempting existing field called profile that is added by
default when a new user registers. This field was historically
intended to be used as a scratch pad for user-specific data - maybe
their image avatar, name, intro text, etc. Because of this, the
profile field on every user is automatically writeable by that user
from the client. It’s also automatically published to the client for
that particular user.
Basically, it's probably fine to store basic information such as name, address, dob, etc in the profile field, but not a good idea to store anything beyond that as it will, by default, be writeable by the client and vulnerable to malicious users.
I had the same problem and managed to do it only with Accounts.createUser:
Accounts.createUser({
email: email,
password: password,
profile: {
givenName: 'John',
surname: 'Doe',
gender: 'M'
}
}
Thats very simple way and it works. Just add your desired variables in the profile section and it should be ready. Hope it helps someone.
I ended up using https://atmospherejs.com/joshowens/accounts-entry which offers an extraSignUpFields config option.
From the documentation (https://github.com/ianmartorell/meteor-accounts-ui-bootstrap-3/blob/master/README.md):
Custom signup options
You can define additional input fields to appear in the signup form, and you can decide wether to save these values to the profile object of the user document or not. Specify an array of fields using Accounts.ui.config like so:
Accounts.ui.config({
requestPermissions: {},
extraSignupFields: [{
fieldName: 'first-name',
fieldLabel: 'First name',
inputType: 'text',
visible: true,
validate: function(value, errorFunction) {
if (!value) {
errorFunction("Please write your first name");
return false;
} else {
return true;
}
}
}, {
fieldName: 'last-name',
fieldLabel: 'Last name',
inputType: 'text',
visible: true,
}, {
fieldName: 'gender',
showFieldLabel: false, // If true, fieldLabel will be shown before radio group
fieldLabel: 'Gender',
inputType: 'radio',
radioLayout: 'vertical', // It can be 'inline' or 'vertical'
data: [{ // Array of radio options, all properties are required
id: 1, // id suffix of the radio element
label: 'Male', // label for the radio element
value: 'm' // value of the radio element, this will be saved.
}, {
id: 2,
label: 'Female',
value: 'f',
checked: 'checked'
}],
visible: true
}, {
fieldName: 'country',
fieldLabel: 'Country',
inputType: 'select',
showFieldLabel: true,
empty: 'Please select your country of residence',
data: [{
id: 1,
label: 'United States',
value: 'us'
}, {
id: 2,
label: 'Spain',
value: 'es',
}],
visible: true
}, {
fieldName: 'terms',
fieldLabel: 'I accept the terms and conditions',
inputType: 'checkbox',
visible: true,
saveToProfile: false,
validate: function(value, errorFunction) {
if (value) {
return true;
} else {
errorFunction('You must accept the terms and conditions.');
return false;
}
}
}]
});
The official Meteor Guide provides a comprehensive answer with an example code:
The best way to store your custom data onto the Meteor.users collection is to add a new uniquely-named top-level field on the user document.
https://guide.meteor.com/accounts.html#custom-user-data

Resources