I'm using the following package in my project - https://github.com/matteodem/meteor-easy-search
Has anyone used it and was able to set custom mongo selectors for the query parameter? The leaderboard example isn't very clear to me. I need to be able to pass meteor user id to:
EasySearch.createSearchIndex('producers', {
'collection': Producers,
'field': ['name', 'producerIdNumber', 'blocksCount', 'totalHectares', 'totalArea'],
'limit': 8,
'use' : 'mongo-db',
'sort': function() {
return { 'created': -1, 'name': -1 };
},
'query': function() {
var selector = {};
return selector
}
});
How can pass or get the meteor user id? EasySearch.createSearchIndex function runs on both server and client.
I don't have the answer to your problem - but I may be able to point you in the right direction. If you are using the meteor-accounts package, and you need to pull the user ID out of the Meteor.users() collection - you first have to publish your users.
On the server-code ->
Meteor.publish(null, function() {
return Meteor.users.find({}, {
fields: {
username: 1,
profile: 1
}
});
});
on the client, you should be able to return Meteor.users.find() or findOne() to get a userId. not the complete answer but may help?
I was able to do this like so
'query': function(searchString, opts) {
// Default query that will be used for the mongo-db selector
var query = EasySearch.getSearcher(this.use).defaultQuery(this, searchString);
if (this.props.formName != '') {
query.formName = this.props.formName;
}
if (this.props.producerId != '') {
query.producerId = this.props.producerId;
}
if (this.props.blockUnitCodeSubCode != '') {
query.blockUnitCodeSubCode = this.props.blockUnitCodeSubCode;
}
if (this.props.created.length != 0) {
query.created = {$gte:new Date(this.props.created[0]), $lt:new Date(this.props.created[1])};
}
Related
I have the following document structure:
ProductDocument {
_id: "a",
price: 12,
starredByUserIds: [
"user1id",
"user2id",
"user3id",
]
}
For security, I want to ensure that a given user cannot see the other user's starredByUserIds by performing a query through a client console.
i.e. user3 should only be able to see his own respective entry:
ProductDocument {
_id: "a",
price: 12,
starredByUserIds: [
"user3id",
]
}
whilst a non-logged-in user should only be able to see:
ProductDocument {
_id: "a",
price: 12,
starredByUserIds: [
]
}
I can't seem to define the right Publish command. I'd like to be able to do something like:
Meteor.publish('Products', function() {
return Products.find( {}, { fields: { starredByUserIds: this.userId }} );
})
but 'fields' doesn't accept/match arbitrary string values.
How can this be achieved?
I think the below query should answer your requirement:
Products.find({}, {starredByUserIds: {$elemMatch:{$eq:this.userId}});
Following snippet would work:
Meteor.publish('Products', function() {
return Products.find( {starredByUserIds: this.UserId}, { fields: { starredByUserIds: 0 }} );
})
Explanation:
Here the query selector starredByUserIds: this.UserId will return documents only which has current user's Id in its starredByUserIds array.
I'm omitting starredByUserIds array while sending it to the client, because you it will either contain the current user's Id if the user is logged in or empty if the user is not logged in, you can regenerate it.
With those security concerns, maybe you should change your data model and put the 'stars' in a different collection. If you don't want to do that, then you must change the code of your publish functions to something like this:
Meteor.publish('Products', function () {
return Products.find().fetch().map(function (product) {
if (this.userId && product.starredByUserIds.indexOf(this.userId) != -1) {
product.starredByUserIds = [this.userId];
} else {
product.starredByUserIds = [];
}
return product;
});
});
I have a Meteor application with a publish of:
Meteor.publish('my_items', function() {
var selector = {owner_id: this.userId};
var items = ItemOwnership.find(selector, {fields: {item_id: 1}}).fetch();
var itemIds = _.pluck(items, 'item_id');
return Items.find({
_id: {$in: itemIds},
item_archived_ts: { $exists: false }
});
});
and a subscription of this:
Meteor.subscribe('my_items');
The application allows for the user to add items to the 'Items' collection and this is done by calling a server method. The Items collection on the server is updated with the new record, but the client-side equivalent collection is not showing the new record. Is there anything obviously wrong with what I am doing, or some way to debug this?
p.s. there are no client/server-side errors occurring?
I found a way to accomplish this using the reywood:publish-composite Meteor package. Here is the publish that achieves this:
Meteor.publishComposite('my_items', {
find: function () {
var selector = {owner_id: this.userId};
return ItemOwnership.find(selector, {fields: {item_id: 1}});
},
children: [
{
find: function(IOrecord){
return Items.find({
_id: IOrecord.item_id,
item_archived_ts: { $exists: false }
});
}
}
]
});
I’m having issues getting two dependant types of data from a PouchDB database.
I have a list of cars that I get like so:
localDB.query(function(doc) {
if (doc.type === ‘list’) {
emit(doc);
}
}, {include_docs : true}).then(function(response) {
console.log(“cars”, response);
// Save Cars List to app
for(var i = 0; i < response.rows.length; i++) {
addToCarsList(response.rows[i].id, response.rows[i].carNumber);
}
console.log(“Cars List: " + carsListToString());
return response;
}).then(function(listRecord) {
listRecord.rows.forEach(function(element, index){
console.log(index + ' -> ', element);
localDB.query(function(doc) {
console.log("filtering with carNb = " + element.carNb);
if (doc.type === 'defect' && doc.listId == getCurrentListId() && doc.carNb == element.carNb ) {
emit(doc);
}
}, {include_docs : false}).then(function(result){
console.log("defects", result);
}).catch(function(err){
console.log("an error has occurred", err);
});
});
}).catch(function(err) {
console.log('error', err);
});
Here's what happens. After getting the list of cars, then for each cars I would like to query the defects and store then in some arrays. Then when all that querying is done, I want to build the UI with the data saved.
But what's happening is that the forEach gets processed quickly and does not wait for the inner async'd localDb.query.
How can I query some documents based on an attribute from a parent query? I looked into promises in the PouchDB doc but I can't understand how to do it.
(please forget about curly quotes and possible lint errors, this code was anonymized by hand and ultra simplified)
The method you are looking for is Promise.all() (execute all promises and return when done).
However, your query is already pretty inefficient. It would be better to create a persistent index, otherwise it has to do a full database scan for every query() (!). You can read up on the PouchDB query guide for details.
I would recommend installing the pouchdb-upsert plugin and then doing:
// helper method
function createDesignDoc(name, mapFunction) {
var ddoc = {
_id: '_design/' + name,
views: {}
};
ddoc.views[name] = { map: mapFunction.toString() };
return ddoc;
}
localDB.putIfNotExists(createDesignDoc('my_index', function (doc) {
emit([doc.type, doc.listId, doc.carNb]);
})).then(function () {
// find all docs with type 'list'
return localDB.query('my_index', {
startkey: ['list'],
endkey: ['list', {}],
include_docs: true
});
}).then(function (response) {
console.log("cars", response);
// Save Cars List to app
for(var i = 0; i < response.rows.length; i++) {
addToCarsList(response.rows[i].id, response.rows[i].carNumber);
}
console.log("Cars List: " + carsListToString());
return response;
}).then(function (listRecord) {
return PouchDB.utils.Promise.all(listRecord.rows.map(function (row) {
// find all docs with the given type, listId, carNb
return localDB.query('my_index', {
key: ['defect', getCurrentListId(), row.doc.carNb],
include_docs: true
});
}));
}).then(function (finalResults) {
console.log(finalResults);
}).catch(function(err){
console.log("an error has occurred", err);
});
I'm using a few tricks here:
emit [doc.type, doc.listId, doc.carNb], which allows us to query by type or by type+listId+carNb.
when querying for just the type, we can do {startkey: ['list'], endkey: ['list', {}]}, which matches just those with the type "list" because {} is the "higher" than strings in CouchDB object collation order.
PouchDB.utils.Promise is a "hidden" API, but it's pretty safe to use if you ask me. It's unlikely we'll change it.
Edit Another option is to use the new pouchdb-find plugin, which offers a simplified query API designed to replace the existing map/reduce query() API.
Another approach would be to pull both the list docs and the defect docs down at the same time then merge them together using a reduce like method that will convert them into an array of objects:
{
_id: 1,
type: 'list',
...
defects: [{
type: 'defect'
listId: 1
...
}]
}
By pulling the list and the defects down in one call you save a several calls to the pouchdb query engine, but you do have to iterate through every result to build your collection of lists objects with and embedded array of defects.
// This is untested code so it may not work, but you should get the idea
var _ = require('underscore');
// order documents results by list then defect
var view = function (doc) {
if (doc.type === 'list') {
emit([doc._id, doc.carNumber, 1);
} else if (doc.type === 'defect') {
emit([doc.listId, doc.carNb, 2])
}
}
localDB.query(view, { include_docs: true })
.then(function(response) {
return _(response.rows)
.reduce(function(m, r) {
if (r.key[2] === 1) {
// initialize
r.doc.defects = [];
m.push(r.doc)
return m;
}
if (r.key[2] === 2) {
var list = _(m).last()
if (list._id === r.key[0] && list.carNumber === r.key[1]) {
list.defects.push(r.doc);
}
return m;
}
}, []);
})
.then(function(lists) {
// bind to UI
});
With couch, we found reducing calls to the couch engine to be more performant, but I don't know if this approach is better for PouchDB, but this should work as a solution, especially if you are wanting to embed several collections into one list document.
In non Meteor Server-Side calls to mongodb it is possible make the following chained-option call to the database
collection.find( { myField: { $gte: myOffset } ).limit( myLimit ).sort( { mySortField : 1 } );
where myField, myOffset, myLimit and mySortField may be resolved from elsewhere at run-time.
This pattern is very useful to create such a run-time generated generic query.
Meteor seems to insist on the non-chained options pattern of
collection.find( { { myField: { $gte: myOffset } }, { limit: myLimit, sort: { mySortField : 1 }} );
and I am having problems 'building up' a working Find Query as required above from js objects as described
in previous questions 17362401 and 10959729
Would anyone like to help?
Edited to show usage of variable:
I do it this way. You send two hashes, where the first is the where clause, and all else are peer level keys.
var locations;
var myfield = 'gps';
search = {
sureties: {
$in: sureties
}
}
search[myfield] = {
$near: this.gps,
$maxDistance: kilometers
};
locations = Agents.find(search, {
fields: {
name: 1,
phone: 1
},
limit: limit,
sort: { field1 : 1 }
}).fetch();
The chained pattern is not possible in Meteor, neither server side nor on the client. But the params pattern is as universal, you should be able to create any query you need with those params.
In short, I want to do:
Meteor.publish('items', function(){
return Item.find({categoryId: Categories.find({active: true} });
});
The flag 'active' as part of 'Categories' changes regularly.
I also tried unsub/resub to the Items collection by leveraging reactivity on the Categories collections, and it works, unfortunately it re-triggers on ANY modification to the Categories collection, regardless if it affected the 'active' flag or not.
What are my options?
Nothing solved the issue of the items not being 'deleted' locally when the category is flagged as inactive on the server. Solution (ish) is to:
Client:
Categories.find({active: true}).observeChanges({
added: function(){
itemsHandle && itemsHandle.stop();
itemsHandle = Meteor.subscribe("items");
}
});
Server:
Meteor.publish('items', function(){
var category = Categories.findOne({active: true});
return category && Items.find({categoryId: Categories.findOne({active: true}._id);
});
I realize this isn't perfect (still uses client side code), but it works and its the cleanest I could think of. I hope it helps someone!
A possible solution is to create a dependency object, watch for all categories change, and trigger the dep change if the active flag was toggled. Something along these lines:
var activeCount = Categories.find({active: true}).count();
var activeDep = new Deps.Dependency();
Deps.autorun(function() {
var activeCountNow = Categories.find({active: true}).count();
if(activeCountNow !== activeCount) {
activeCount = activeCountNow;
activeDep.changed();
}
});
Meteor.publish('items', function(){
activeDep.depend();
return Item.find({categoryId: Categories.find({active: true} });
});
Note: I'm only verifying whether the number of active categories have changes so that I don't have to keep the active list in the memory. This may or may not be appropriate depending on how your app works.
Edit: Two-sided flavor mentioned in the comments:
Client:
var activeCount = Categories.find({active: true}).count();
var activeDep = new Deps.Dependency();
Deps.autorun(function() {
var activeCountNow = Categories.find({active: true}).count();
if(activeCountNow !== activeCount) {
activeCount = activeCountNow;
activeDep.changed();
}
});
Deps.autorun(function(){
activeDep.depend();
Meteor.subscribe('items', new Date().getTime());
});
Server:
Meteor.publish('items', function(timestamp) {
var t = timestamp;
return Item.find({categoryId: Categories.find({active: true} });
});
Meteor.startup(function() {
Categories.find().observe({
addedAt: function(doc) {
trigger();
},
changedAt: function(doc, oldDoc) {
if(doc.active != oldDoc.active) {
trigger();
}
},
removedAt: function(oldDoc) {
trigger();
}
});
});
Now, the trigger function should cause the publish to rerun. This time it's easy when it's on the client (change subscription param). I'm not sure how to do this on the server - perhaps run publish again.
I use the following publish to solve a similar issue. I think it is only the one line nesting of queries that limits the reactivity. Breaking one query out inside the publish function seems to avoid the issue.
//on server
Meteor.publish( "articles", function(){
var self= this;
var subscriptions = [];
var observer = Feeds.find({ subscribers: self.userId }, {_id: 1}).observeChanges({
added: function (id){
subscriptions.push(id);
},
removed: function (id){
subscriptions.splice( subscriptions.indexOf(id)) , 1);
}
});
self.onStop( function() {
observer.stop();
});
var visibleFields = {_id: 1, title: 1, source: 1, date: 1, summary: 1, link: 1};
return Articles.find({ feed_id: {$in: subscriptions} }, { sort: {date: -1}, limit: articlePubLimit, fields: visibleFields } );
});
//on client anywhere
Meteor.subscribe( "articles" );
Here is another SO example which gets the search criteria from the client through subscribe if you decide that is acceptable.
Update: Since the OP struggled to get this going I made a gist and launched a working version on meteor.com. If you just need the publish function it is as above.