I've been digging Bookshelf documentation but I can't find enough information to get 5 random users from the database. Right now, the following code retrieves all users.
User.fetchAll().then((users) => {
});
However, is it possible to do something like this?
User.take(5).random().get().then((users) => {
// random 5 users in users collection
});
I really don't want to write raw SQL but I'm fine doing it inside ORM using .query callbacks if necessary.
There's a shuffle method in the included methods brought in from lodash.
User.shuffle().take(5).get().then((users) => {
// random 5 users in users collection
});
Related
Since Firebase security rules cannot be used to filter children, what's the best way to structure data for efficient queries in a basic multi-user application? I've read through several guides, but they seem to break down when scaled past the examples given.
Say you have a basic messaging application like WhatsApp. Users can open chats with other groups of users to send private messages between themselves. Here's my initial idea of how this could be organized in Firebase (a bit similar to this example from the docs):
{
users: {
$uid: {
name: string,
chats: {
$chat_uid : true,
$chat2_uid: true
}
}
},
chats: {
$uid: {
messages: {
message1: 'first message',
message2: 'another message'
}
}
}
}
Firebase permissions could be set up to only let users read chats that are marked true in their user object (and restrict adding arbitrarily to the chats object, etc).
However this layout requires N+1 selects for several common scenarios. For example: to build the home screen, the app has to first retrieve the user's chats object, then make a get request for each thread to get its info. Same thing if a user wants to search their conversations for a specific string: the app has to run a separate request for every chat they have access to in order to see if it matches.
I'm tempted to set up a node.js server to run root-authenticated queries against the chats tree and skip the client-side firebase code altogether. But that's defeating the purpose of Firebase in the first place.
Is there a way to organize data like this using Firebase permissions and avoid the N+1 select problem?
It appears that n+1 queries do not necessarily need to be avoided and that Firebase is engineered specifically to offer good performance when doing n+1 selects, despite being counter-intuitive for developers coming from a relational database background.
An example of n+1 in the Firebase 2.4.2 documentation is followed by a reassuring message:
// List the names of all Mary's groups
var ref = new Firebase("https://docs-examples.firebaseio.com/web/org");
// fetch a list of Mary's groups
ref.child("users/mchen/groups").on('child_added', function(snapshot) {
// for each group, fetch the name and print it
String groupKey = snapshot.key();
ref.child("groups/" + groupKey + "/name").once('value', function(snapshot) {
System.out.println("Mary is a member of this group: " + snapshot.val());
});
});
Is it really okay to look up each record individually? Yes. The Firebase protocol uses web sockets, and the client libraries do a great deal of internal optimization of incoming and outgoing requests. Until we get into tens of thousands of records, this approach is perfectly reasonable. In fact, the time required to download the data (i.e. the byte count) eclipses any other concerns regarding connection overhead.
I'm doing my meteor app and it has 1 Collection: Students
In Server I made a Publish that receives 3 params: query, limit and skip; to avoid client to subscribe all data and just show the top 10.
I have also 3 Paths:
student/list -> Bring top 10, based on search input and pagination (using find);
student/:id -> Show the student (using findOne)
student/:id/edit -> Edit the student (using findOne)
Each Template subscribe to the Students collection, but every time the user change between this paths, my Template re-render and re-subscribe.
Should I make just one subscribe, and make the find based on this "global" subscription?
I see a lot of people talking about Template level subscription, but I don't know if it is the better choice.
And about making query on server to publish and not send all data, I saw people talking too, to avoid data traffic...
In this case, when I have just 1 Collection, is better making an "global" subscription?
You're following a normal pattern although it's a bit hard to tell without the code. If there many students then you don't really want to publish them all, only what is really necessary for the current route. What you should do is figure out why your pub-sub is slow. Is it the find() on the server? Do you have very large student objects? (In which case you will probably want to limit what fields are returned). Is the search you're running hitting mongo indexes?
Your publication for a list view can have different fields than for a individual document view, for example:
Meteor.publish('studentList',function(){
let fields = { field1: 1, field2: 1 }; // only include two fields
return Students.find({},fields);
});
Meteor.publish('oneStudent',function(_id){
return Students.find(_id); // here all fields will be included
});
How can I get access to every meteor user object? I tried this, but it shows Meteor.users does not have function forEach.
Meteor.users.forEach((user) => {
console.log("userId", user._id);
});
Then I tried this, but it says userId is undefined.
_.toArray(Meteor.users).forEach((user) => {
console.log("userId", user._id);
});
So how can I get it? Thanks
Meteor.users is a Mongo collection. Mongo collections provide the map method to iterate through all found elements, but first, you have to find them. If you want to map through all the users without exception, just map without arguments, like this:
Meteor.users.find().map(user => console.log(user));
There, user will be an object that represents a user, i.e. similar to what you retrieve with Meteor.user().
Another way to iterate through all users would be to first fetch them in an instance of Array and then apply lodash or underscore to it:
const users = Meteor.users.find().fetch();
_.map(users, user => console.log(user));
I am obviously not understanding the publish/subscribe system.
I removed the insecure/autopublish packages.
In my server/publish.js I have:
Meteor.publish("profiles", function () {
return Meteor.users.find({}, {
profile: 1,
status: 1,
services: 0
});
});
In my client/lib/collection.js I have:
Meteor.subscribe("profiles");
Profiles = new Mongo.Collection("profiles");
Now, what I expected was to have a Profiles collection on the client which only contains the profile and status parts of the user document, and does not contain the services part (which has provate info like email and such).
However, on the client the Profiles collection is empty, while the Meteor.users() is available and has everything in it so that anyone who knows how to open the console can see private data about all users...
Anyone know what I'm doing wrong here?
EDIT: Interestingly, when I remove the publish profiles completely, then on the client side the Meteor.users has only the current user in it and only the profile section of it. Now I am totally confused.
UPS, the Meteor .find syntax is a little different from the standard mongodb .find syntax. I needed to add the "fileds" keyword and then it works as expected.
(almost, since my Profiles collection is still empty on the client...?)
Meteor.publish("profiles", function () {
return Meteor.users.find({}, {
fields: {
"services": 0
}
});
});
EDIT: Ok, thanks to Thai Tran I finally realized that in fact I DID misunderstand the publish/subscribe system. I wrongly thought the publish "name" on the server is going to be the collection name on the client. Now that I understand how it works it is clear why my Profiles collection is empty.
I have a server side mongo collection called Profiles.
I need to publish and subscribe to the entire collection of Profiles if user: adminId.
That way the administrator can edit, updated, etc... each Profile collection item.
But I want users to be able to see their Profile record.
So I tried this...
CLIENT SIDE
MyProfile = new Meteor.Collection("myprofile");
Meteor.subscribe('profiles');
Meteor.subscribe('myprofile');
COMMON - CLIENT AND SERVER SIDE
Profiles = new Meteor.Collection("profiles");
SERVER SIDE - The publishing and subscribing of profiles works fine.
// this returns all profiles for this User
// if they belong to an ACL Group that has acl_group_fetch rights
Meteor.publish("profiles", function() {
var user_groups = Groups.find({users: this.userId()});
var user_groups_selector = [];
user_groups.forEach(function (group) {
user_groups_selector.push(group._id);
});
return Profiles.find( {
acl_group_fetch: {
$in: user_groups_selector
}
});
});
Here is where the problem seems to begin. The Profiles.find is returning collection items because I can output them to the console server side. But for some reason the publish and subscribe is not working. The client receives nothing.
// return just the users profile as myprofile
Meteor.publish("myprofile", function() {
return Profiles.find({user: this.userId()});
});
Any ideas what I am doing wrong. I want to be able to publish collections of records that User A can insert, fetch, update, delete but User B (C, D and E) can only see their record.
I think your issue is more on the MongoDB side than with meteor. Given your case I'd do two collections (Group and Profile).
Each document in the Group collection would feature an array containing DBRefs to documents in the Profile collection (actually users so I would think about renaming the Profile collection to User as imo that's more intuitive).
Same for the Profile collection and its documents; each document in the profile collection (representing a user) would have an array field containing DBrefs to groups the user belongs to (documents inside the Group collection).
I'm not entirely sure of how you're checking for the error or not, but I think you may be running into a gotcha I ran into. When you publish data with the Profiles collection, even though the pub/sub calls use the 'myprofile' name, the data always is always available in the collection that you're returning a cursor for... in this case, the data you publish in the 'myprofile' publication will show up in the 'profiles' collection on the client. The publish call doesn't create a 'myprofile' collection on the client. So if you're trying to find() on the 'myprofile' collection you won't see any data. (Meteor/MongoDB won't complain that the collection doesn't exist because they always will lazily create it when you reference it.)
I think the problem here is that you only need the one collection: Profiles.
So if you just remove the offending line
MyProfile = new Meteor.Collection("myprofile");
Everything should work fine (you'll have both datasets within the Profiles collection).