Meteor Publish-Composite nesting issue - meteor

Problem:
I have a number of groups that each have members that belong to different groups. Each member has a title (role) in each group.
I’m trying to list all the groups and display each member in the group and their title.
I’m using reywood:publish-composite, and everything is working except I can’t get the title of each member to display.
I think the problem is in the Template.groupMembers.helpers file
title: function() {
console.log(this.roleId); // this shows up in the console for each member
return Titles.findOne({titleId: this.roleId}); // but this doesn’t work
},
Collections:
groups {
"_id" : "xFSzAHBEps2dSKcWM",
"name" : "Generic Group",
"logo" : "generic-logo-hi.png"
}
members {
"_id" : "vyDtiaKKukZYQdFvs",
"groupId" : "xFSzAHBEps2dSKcWM",
"memberId" : "hRx8GBTyB5X8iQQ52",
"roleId" : "1"
}
Meteor.users {
"_id" : "hRx8GBTyB5X8iQQ52",
"profile" : {
"name" : "Bob Lorros"
},
}
titles {
"_id" : "bYsKpsyYtyKR8NYpm",
"titleId" : 1,
"title" : "Staff (non-voting)"
}
server/publications/publications.js
Meteor.publishComposite('groupMembers', {
find: function() {
return Groups.find({}, {
sort: {name: 1}
});
},
children: [
{
find: function() {
return Titles.find();
},
find: function(group) {
return Members.find({groupId: group._id});
},
children: [
{
find: function(member) {
return Meteor.users.find({_id: member.memberId});
}
},
]
},
]
});
client/templates/test/test.js
Template.groupMembers.helpers({
groupMembers: function() {
return Groups.find({}, {
sort: {name: 1}
});
},
members: function() {
return Members.find({groupId: this._id});
},
title: function() {
console.log(this.roleId); // this shows up in the console for each member
return Titles.findOne({titleId: this.roleId}); // but this doesn’t work
},
memberName: function() {
return Meteor.users.findOne(this.memberId);
},
});
client/templates/test/test.html
<template name="groupMembers">
<h4>Group - Members</h4>
{{#each groupMembers}}
<b>{{name}}</b><br>
{{#each members}}
{{memberName.profile.name}}
- title = {{title.title}}
<br>
{{/each}}
<br>
{{/each}}
</template>
Output :
This is the ouput

Looking at this from a completely different perspective, I actually think you could use alanning:roles to accomplish exactly what you're looking for. You can use the role as the 'title' in this case and the 'group' to replace your groups. Here's the documentation:
https://github.com/alanning/meteor-roles

Not sure but I think your second find may be overriding your first. Instead of:
find: function() {
return Titles.find();
},
find: function(group) {
return Members.find({groupId: group._id});
},
Try returning an array of cursors.
find: function() {
return [
Titles.find(),
Members.find({groupId: group._id})
];
},
I don't understand however why Titles is a child of GroupMembers when the query for titles is all titles. Did you mean to have a query there?

I think your publishComposite is causing the problem, each object in the children array should have only one find and zero or more children. Also the second parameter in your publication must be a function and not a JSON object. Try this,
Meteor.publishComposite('groupMembers', function () {
return {
find: function() {
return Groups.find({}, {
sort: {name: 1}
});
},
children: [{
find: function() {
return Titles.find();
}
},
{
find: function(group) {
return Members.find({groupId: group._id});
},
children: [{
find: function(member) {
return Meteor.users.find({_id: member.memberId});
}
}]
}]
};
});
You can also improve performance by moving Titles.find to the root level
Meteor.publishComposite('groupMembers', function () {
return [{
find: function() {
return Titles.find();
}
}, {
find: function() {
return Groups.find({}, {
sort: {name: 1}
});
},
children: [{
find: function(group) {
return Members.find({groupId: group._id});
},
children: [{
find: function(member) {
return Meteor.users.find({_id: member.memberId});
}
}]
}]
}];
});

Related

Meteor: Reactivity in publications with nested mongo queries

I have a collection like this one:
//Groups Collection
{
_id:1,
members: [
{
memberId: 'A1B2',
content: [1,2,3]
},
{
memberId: 'C10B',
content: [4,5,6]
}
]
},
{
_id:2,
members: [
{
memberId: 'A1B2',
content: [7,8,9]
},
{
memberId: 'F804',
content: [10,11,12]
}
]
}
and another Collection like this one:
//Users Collection
{
_id: 'A1B2',
name: 'Newton'
},
{
_id: 'C10B',
name: 'Gauss'
},
{
_id: 'F804',
name: 'Leibniz'
}
And I need a publication with all ids of users in Groups array with _Id = 1. I tryed:
Meteor.publish('themembers',idGroup,function() {
return Users.find({_id:{$in:Groups.findOne(idGroup).members.map(function(e) {return e.memberId})}});
});
then I subscribe:
Template.problem.onCreated(function() {
Meteor.subscribe('themembers',1);
});
Now I can access the members in the helper:
Template.problem.helpers({
members: function() {
return Users.find();
}
});
and the helpers works right too.
But now, If I add a new member to the group, it not appears in the list, ... my subscriptions seems not to be reactive.
What I doing Wrong?
Typical issue with Meteor:
You should look at peerlibrary:reactive-publish
https://github.com/peerlibrary/meteor-reactive-publish

belongsTo - hasMany - getting instance from belongsTo - sequelize

Provided I have following models:
module.exports = function (sequelize, DataTypes) {
var WorkingCalendar = sequelize.define('WorkingCalendar', {
date: DataTypes.DATEONLY,
isWorking: DataTypes.BOOLEAN,
}, {
indexes: [{
unique: true,
fields: ['PeriodId', 'date']
}]
}, {
classMethods: {
associate: function (models) {
WorkingCalendar.belongsTo(models.Period);
}
}
});
return WorkingCalendar;
};
module.exports = function(sequelize, DataTypes) {
var Period = sequelize.define('Period', {
name: DataTypes.STRING,
numberOfPeriods: DataTypes.INTEGER
}, {
classMethods: {
associate: function(models) {
Period.hasMany(models.WorkingCalendar);
}
}
});
return Period;
};
And then trying to get the Period through the WorkingCalendar as follows:
return models.WorkingCalendar
.findAll({
attributes: [
'PeriodId',
'date'
],
include: [
{ model: models.Period }
],
group: ['date', 'PeriodId']
});
I'm getting following error: Unhandled rejection Error: Period is not associated to WorkingCalendar!
Yet it does work the other way around.
My question:
Why can't I get the Period through the WorkingCalendar? And what do I have to do to make sure I can?
I have already tried putting the foreignKey attribute on the association as wel as the as binding but to no avail sadly. Any help would be very welcome!
So finally found it.
The indexes should be in the same object as classMethods
WRONG
module.exports = function (sequelize, DataTypes) {
var WorkingCalendar = sequelize.define('WorkingCalendar', {
date: DataTypes.DATEONLY,
isWorking: DataTypes.BOOLEAN,
}, {
indexes: [{
unique: true,
fields: ['PeriodId', 'date']
}]
}, {
classMethods: {
associate: function (models) {
WorkingCalendar.belongsTo(models.Period);
}
}
});
return WorkingCalendar;
};
RIGHT
module.exports = function (sequelize, DataTypes) {
var WorkingCalendar = sequelize.define('WorkingCalendar', {
date: DataTypes.DATEONLY,
isWorking: DataTypes.BOOLEAN,
}, {
indexes: [{
unique: true,
fields: ['PeriodId', 'date']
}],
classMethods: {
associate: function (models) {
WorkingCalendar.belongsTo(models.Period);
}
}
});
return WorkingCalendar;
};

Receive data many-to-many relation Firebase

I have an extended question to this question.
What if the player belong to more than one team?
I have this
json
"Players" : {
"-YRHd4IjrjsBXx__B" : {
"name" : "The best forward",
"creationDate" : "2016-02-26 15:50:39",
"teams" : {
"-KAByPeIz4IjrjsBXx__B" : true,
"-KEFPuCXcqOah_GJwsMCu" : true,
"-KEwuQxvGpYTEJ7YQ58-l" : true,
"-KKF8vPtf8J7cfqFh2PLm" : true
},
},
etc...
}
players-service.js
getPlayers: function(teamid) {
var Players = {};
var teamsIndex = ref.child('teams/' + teamid + '/players/');
var playersIndex = ref.child('players/');
teamsIndex.on('child_added', function(snapshot) {
var playerKey = snapshot.key;
playersIndex.child(playerKey).on('value', function(playersnap){
$timeout(function() {
console.log("key", playerKey);
players[playerKey] = playersnap.val();
});
});
});
teamIndex.on('child_removed', function(snapshot) {
$timeout(function(snapshot) {
delete players[snapshot.key()];
});
});
return players;
}
But it returns a list of object. I know that I could probably query/change the data structure to/in firebase and return it as a $firebaseArray which I prefer as I use angularfire.
You usually structure your data depending on how you want to retrieve them.
From my understanding (correct me if I'm wrong) you want to get all the players in a team. For this purpose I would use this structure:
"Players": {
"player1": {...},
"player2": {...},
"player3": {...}
},
"Teams': {
"team1": {...},
"team2": {...}
},
"TeamPlayers" : {
"team1": {
"player1": true,
"player2": true
},
"team2": {
"player1": true,
"player3": true
}
}
Or using an array
"TeamPlayers" : {
"team1": [
0: "player1",
1: "player2"
]
}

Meteor.js : SearchSource + Publish composite

I'm currently creating a research engine for my app.
Until now, I used Publish composite + iron router : The user could had filters to search for some specific set of users.
Now, I want him to be able to look for some keywords too. For that I downloaded and tested the great SearchSource package.
The problem is that the SearchSource server side definition only seems to allow to return one cursor.
How could I combine the two logics ? Even if it's tricky, please, share.
Of course I could make an autorunned subscription where I look for every users loaded on the client and then subscribe to the additionnal documents, but it is not really the most performant and beautifull thing to do.
Some data :
Here is my current Publish Composite for filters :
Meteor.publishComposite("findTalkers", function(page, langs){
//console.log("Find Talkers");
//console.log("page : " + page);
//console.log("langs : " + langs);
if (langs.length)
{
return ({
find: function()
{
if (langs && langs.length)
{
var test = {$in: langs};
preSelectedUsers = [],
selector = {
_id: {$ne: this.userId},
"profile.completed": true,
"profile.firstName": {$exists: true},
"profile.languages.native": {$exists: false},
"profile.languages.lang": test
};
Counts.publish(this, "nbUsers", Meteor.users.find(selector, {
fields: {_id: 1}
}), {noReady: false, nonReactive: true});
if (page > 1)
{
preSelectedUsers = Meteor.users.find(selector, {
sort: {'profile.talkname': 1},
limit: 25,
skip: (25 * (page || 1)),
fields: {_id: 1}
}).fetch();
var i = -1;
while (preSelectedUsers[++i])
preSelectedUsers[i] = preSelectedUsers[i]._id;
}
if (page > 1)
selector._id = {$in: preSelectedUsers};
return Meteor.users.find(selector, {
fields: userFields,
sort: {'profile.talkname': 1},
limit: 25
});
}
},
children: [
{
// Finding user's profile picture if it is not url
find: function(user)
{
if (user && user.profile && user.profile.avatar.type != "url")
return Images.find({_id: user.profile.avatar.data}, {sort: {uploadedAt: -1}, limit: 1});
}
}
]
});
}
else
{
return ({
find: function()
{
return Meteor.users.find({_id: "flush"});
}
});
}
});
Here is my research with SearchSource :
Client :
var searchOptions = {
keepHistory: 1000 * 60 * 5,
localSearch: true
},
SearchSources = {
talkersSearch: new SearchSource('users', ['profile.talkname'], searchOptions)
};
Router.map(function(){
this.route('talkers/:page?',
{
template: "talkers",
onBeforeAction: function(pause){
(Meteor.user() && Meteor.user().profile.completed)
? this.next()
: this.render('/profile');
},
waitOn: function(){
var filters = MatesFilter.find().fetch(),
i = -1;
while (filters[++i])
filters[i] = filters[i].value;
if (filters.length)
{
return Meteor.subscribe("findTalkers", (this.params.page || 1), filters, function(){
Session.set('numberuser', Counts.get("nbUsers"));
});
}
return Meteor.subscribe('myself');
}
});
}
Template.talkers.helpers({
getPackages: function() {
return SearchSources.talkersSearch.getData({
transform: function(matchText, regExp) {
return matchText.replace(regExp, "<b>$&</b>")
},
sort: {isoScore: -1}
});
}
}
Template.talkers.events({
"keyup #header-search": _.throttle(function(e) {
Session.set("matesSearch", $(e.target).val().trim());
console.log("Searching for : " + text);
SearchSources.talkersSearch.search(Session.get("matesSearch"), {
page: (this.params.page || 1),
filters: filters
});
}, 200)
}
SERVER :
SearchSource.defineSource('users', function(searchText, options) {
var options = {sort: {"profile.talkname": -1}, limit: 25};
if(searchText)
{
var regExp = buildRegExp(searchText);
selector = { $or: [
{ "profile.talkname": regExp },
{ "profile.bio": regExp }
] };
return Meteor.users.find(selector, options).fetch();
}
return ;
});
All this Gives me two sources from which I can get users. I'd want to get a mean to merge the two ides (a composition of publication INSIDE the search, for example).
Thanks you.

How can I trigger autocomplete with multiple rules.

I have an autocomplete on a text box which shows zipcode, City, State. Currently when I start typing zipcode [e.g. 55414] the autocomplete works and starts to show the relevant zip,city and State. But I can't figure out how to trigger autocomplete if I start typing a city name. I want both of these triggers on the textbox. I tried to add another rule in the rules array but it doesn't work. ZipCodes collection has _id, city, state fields. _id is zipcode.
Template.search.helpers({
settings : function () {
return {
position: "bottom",
limit: 20,
rules: [{
collection: ZipCodes,
field: "_id",
template: Template.userPill
}]
}
}
Thanks in advance
I think you are using meteor-autocomplete
In that case you can use selector option
Template.search.helpers({
settings : function () {
return {
position: "bottom",
limit: 20,
rules: [{
collection: ZipCodes,
field: "_id",
template: Template.userPill,
selector: function(match) {
var regex;
regex = new RegExp(match, 'i');
return {
$or: [
{
'_id': regex
}, {
'city': regex
}
]
};
},
}]
}
}
})
I know this answer is too late for you, but it can be helpful to somebody in the future. Just do this inside your server-side publish function.
Meteor.publish('ZipCodesPublication', function(selector, options) {
let limitTo = Math.abs(options.limit) > 50 ? 50 : options.limit,
defaultSelector = selector._id,
regEx = defaultSelector.$regex,
regExOptions = defaultSelector.$options,
customSeletor = {
$or: [
{
city: {
$regex: regEx,
$options: regExOptions
}
},
{
_id: {
$regex: regEx,
$options: regExOptions
}
}
]
};
Autocomplete.publishCursor(Clients.find(customSeletor), this);
this.ready();
});
And just do this on the client:
Template.search.helpers({
settings : function () {
return {
position: "bottom",
limit: 20,
rules: [{
collection: 'ZipCodes',
subscription: 'ZipCodesPublication',
field: "_id",
template: Template.userPill
}]
}
}

Resources