Say I have a Meteor site where I want a function to search for a document in a collection and display it to the user.
This collection is very large and is not feasible to keep all on the client.
I have a publish statement that specifies what documents need to be pushed to the user.
Meteor.publish('listing', function (search) {
return TestObjs.find({"name": search });
});
And I have a search input textbox which when it changes I'd like to subscribe to the publication to get the records that I need.
'change #searchText' : function(e,t){
Session.set("searchTerm", $("#searchText").val());
Meteor.subscribe("listing", $("#searchText").val());
}
So every time I search for text, client is getting sent the documents it needs to be displayed.
The problem is, the subscribe call doesn't delete any old documents from the client for previous search terms. So the first problem is the more the user uses the search function, the more documents are going to be stored on the client. Secondly, if I want to display the search results for the current search term, I cannot just go
Template.home.helpers({
listOfObjs: function () {
return TestObjs.find().fetch();
}
});
This will return all record on the client which would be all records that match the current and any previous search term.
I have to replicate the publish statement
Template.home.helpers({
listOfObjs: function () {
return TestObjs.find({"name": Session.get("searchTerm") }).fetch();
}
});
This works, but I'm essentially duplicating the logic in multiple places. This is just a simple example but what if my search algorithm was more complicated with paging etc.
So I was wondering, what is the recommended Meteor way to return search results for collections that sit on the server?
Answer without try. Hope you looking for this.
var list;
'change #searchText' : function(e,t){
Session.set("searchTerm", $("#searchText").val());
if (list)
list.stop();
list = Meteor.subscribe("listing", $("#searchText").val());
}
From the doc
stop() Cancel the subscription. This will typically result in the server directing the client to remove the subscription's data from the client's cache.
Related
I am creating a sns-like web application, and trying to implement a "liking a post" function. Basically each post has data in firestore, such as the caption of the post, the image of the post, number of likes on the post and etc. What I want to do is, if a user clicks a like button on any post, the number of likes on the post clicked will be incremented by 1. However, I do not know how I can restricting each user can like only one time on each post. My current code works for incrementing the number of likes but the user can like a post as many times as the user clicks.
I am totally a beginner in firebase, react and javascript and would be really appreciated for any help. Thank you.
here is my code working for incrementing a number of likes.
const likeHandle = async () => {
const docRef = doc(db, "posts", postId);
await updateDoc(docRef, {
noLikes: noLikes + 1
});
}
You won't be able to do this at scale inside the same document where you are tracking the number of likes, because there is a max size to each document.
What you will have to do instead is store a record of each pair of user and like in its own document in a separate collection, and check to make sure that any new like requests are not already represented in that other collection. Your UI should also probably disable likes if there is a record for that pair of user and post so that the user is not presented with an option that you don't want them to take.
There is no direct way to limit how often a user can write a specific piece of data.
What you can do however is:
Add a usersWhoVoted field with UIDs to the document, or a userWhoVoted subcollection under it.
Write both the increased number of likes and the UID to the database in one operation (using a transaction/batched write if you chose a subcollection).
Use security rules that only allow the user to increment the count if they also added their UID in that operation, and only decrease the count if they removed it (if that is also a use-case you want to support).
You should probably also use the atomic increment operation, to prevent depending on client-side state that can be outdated (although your security rules should catch and reject that situation anyway).
I'm trying to learn Meteor, and currently trying to wrap my head around publications and subscriptions. I'm following the discover meteor book and one main point just doesn't make sense to me and was hoping some explanations in simple terms can be shared.
So a publication is what "fetches" the data from the mongo database to store within Meteor:
Meteor.publish('posts', function() {
return Posts.find();
});
Then on the client side I subscribe to the publication. Woopy
Meteor.subscribe('posts');
What doesn't make sense is the Template Helpers. Originally Discover Meteor tells you to create an array of static posts that iterate through each post using a template helper. Well now that I'm turning things dynamically my template helper becomes:
Template.postsList.helpers({
posts: function () {
return Posts.find();
}
});
What's the point of running the Posts.find() on both the server and client template helper?
Posts in publishing are server side collection. Posts in helpers are client-side collection which contains all published Posts. If you have thousands of posts you usually don't want to publish all Posts because it will take a few seconds to download the data.
You should publish just the data you need.
Meteor.publish('posts', function(limit) {
return Posts.find({}, { limit: limit});
});
When you call this subscribe function, client-side collection Posts will contain just 100 posts.
var limit = 100;
Meteor.subscribe('posts', limit);
In publications, you can setup exactly which Posts you want to store within the Posts collection on the client.
In your publish method, you could only return posts which contain the letter 'A'.
A typical use case is selecting objects which belongs to a user or a set of users.
I want to publish and subscribe subset of same collection based on different route. Here is what I have
In /server/publish.js
Meteor.publish("questions", function() {
return Questions.find({});
});
Meteor.publish("questionSummaryByUser", function(userId) {
var q = Questions.find({userId : userId});
return q;
});
In /client/main.js
Deps.autorun(function() {
Meteor.subscribe("questions");
});
Deps.autorun(function () {
Meteor.subscribe("questionSummaryByUser", Session.get("selectedUserId"));
});
I am using the router package (https://github.com/tmeasday/meteor-router). They way i want the app to work is when i go to "/questions" i want to list all the questions by all the users and when i visit "/users/:user_id/questions", I want to list questions only by specific user. For this I have setup the "/users/:user_id/questions" route to set the userid in "selectedUserId" session (which i am also using in "questionSummaryByUser" publish method.
However when i see the list of questions in "/users/:user_id/questions" I get all the questions irrespective of the user_id.
I read here that the collections are merged at client side, but still could not figure a solution for the above mentioned scenario.
Note that I just started with Meteor, so do not know in and outs of it.
Thanks in advance.
The good practice is to filter the collection data in the place where you use it, not rely of the subset you get by subscribe. That way you can be sure that the data you get is the same you want to display, even when you add further subscriptions to the same collection. Imagine if later you'd like to display, for example, a sidebar with top 10 questions from all users. Then you'd have to fetch those as well, and if you have a place when you display all subscribed data, you'll get a mess of every function.
So, in the template where you want to display user's questions, do
Template.mine.questions = function() {
return Questions.find({userId: Meteor.userId()});
};
Then you won't even need the separate questionSummaryByUser channel.
To filter data in the subscription, you have several options. Whichever you choose, keep in mind that subscription is not the place in which you choose the data to be displayed. This should always be filtered as above.
Option 1
Keep everything in a single parametrized channel.
Meteor.publish('questions', function(options) {
if(options.filterByUser) {
return Questions.find({userId: options.userId});
} else {
return Questions.find({});
}
});
Option 2
Make all channel return data only when it's needed.
Meteor.publish('allQuestions', function(necessary) {
if(!necessary) return [];
return Questions.find({});
});
Meteor.publish('questionSummaryByUser', function(userId) {
return Questions.find({userId : userId});
});
Option 3
Manually turn off subcriptions in the client. This is probably an overkill in this case, it requires some unnecessary work.
var allQuestionsHandle = Meteor.subscribe('allQuestions');
...
allQuestionsHandle.stop();
Question related somewhat to: Ember.js: retrieve random element from a collection
I've two routes: randomThing route and things route.
The former displays a... random thing from an API (GET /things/random) (there is a button to "Get another random thing"), the latter: displays all things: (GET /things).
The problem is that EVERY TIME when I click on Get another random thing and new thing is displayed and I go to recipes route this newly displayed random thing is added to the collection...
Action to get random thing performs a find("random") as suggested in related question and sets this.content to this value.
What is wrong here?
EDIT:
I'm using ember-data and my route is like this:
App.ThingsRoute = Ember.Route.extend({
model: function() {
return App.Thing.find();
}
});
The problem is that EVERY TIME when I click on Get another random thing and new thing is displayed and I go to recipes route this newly displayed random thing is added to the collection...
This is expected behavior. App.Thing.find() does not simply query the api and return results. Instead find() returns an array containing of all Things ember knows about. It includes objects returned by past calls to find(), objects created client-side via App.Thing.createRecord(), and of course individual objects queried via App.Thing.find('random'). After returning this array, find() and kicks off another API call and if that returns additional records they are pushed onto the array.
What is wrong here?
It does not sound like anything is wrong per-se. If you want to prevent random things from showing up in the ThingsRoute, you'll need to change that route's model to be a filter instead of just returning every Thing. For example:
App.ThingsRoute = Ember.Route.extend({
model: function() {
//Kick off query to fetch records from the server (async)
App.Thing.find();
//Return only non-random posts by applying a client-side filter to the posts array
return App.Thing.filter(function(hash) {
if (!hash.get('name').match(/random/)) { return true; }
});
}
});
See this jsbin for a working example
To learn more about filters I recommend reading the ember-data store-model-filter integration test
First of all, thanks to the guys of DocumentCloud for releasing those two super-useful tools.
Here goes my question(s):
I'm trying to use visulasearch.js in a backbone.js app.
In my app I have a basic index.html and a myapp.js javascript file wich contains the main application done with backbone.js
I use CouchDB as data storage, and I successfully can retrieve in a restful way all the data to be put in the collection.
I must retrieve the search query given by visualsearch.js and use it to filter a collection.
I surely need a view for the searchbox, to trigger an event when enter is hit, but..
Should I initialze the searchbox externally to myapp.js, within an additional js file or my index.html page (as suggested in the visualsearch mini.tutorial)?
Or I should initialize it within the searchbox view (myapp.js)? This latter solution seems to be too tricky (it was what I was trying to do, but even when I succeed, it's too complicated and I lost the simplicity of bacbone mvc).
Let's say I succeed in retrieving the search string as a JSON object like {name:'Fat DAvid', address:'24, slim st', phone:'0098876534287'}. Once done that, which function can I use to retrieve, in the collection, only the models whose fields match with the given string. I understand that I should do a map or a filter, but those function seems to serve natively for slightly different tasks.
a. is it really the best way to filter results? It charges the client (which must filter the results), while making a new query (a view or a filter) to CouchDB would be quite simple and, considered the small amount of data and the low access rate to the site, not so expensive. However, making all the filtering action client-side, it's much simpler than making new view(or list or filters) in CouchDB and linking it the the backbone.js view
You can initialize your VisualSearch.js search box right in your myapp.js. Just make sure you keep a reference to it so you can then extract out the facets and values later.
For example:
var visualSearch = VS.init({...})
// Returns the unstructured search query
visualSearch.searchBox.value()
// "country: "South Africa" account: 5-samuel title: "Pentagon Papers""
// Returns an array of Facet model instances
visualSearch.searchQuery.facets()
// [FacetModel<country:"South Africa">,
// FacetModel<account:5-samuel>,
// FacetModel<title:"Pentagon Papers">]
If you have these models in a Backbone collection, you can easily perform a filter:
var facets = visualSearch.searchQuery.models;
_.each(facets, function(facet) {
switch (facet.get('category')) {
case 'country':
CountriesCollection.select(function(country) {
return country.get('name') == facet.get('value');
});
break;
// etc...
}
});