Creating Publish with joins inside an array

So, here is my problem:

I have two Schemas on my MongoDB and I need to show a list of one of them, but including info of the other one. Here are my Schemas:

Ingredient = new SimpleSchema({
	componentId: {
		type: String
	},
	unit: {
		type: String
	},
	amount: {
		type: Number
	}
});

RecipeSchema = new SimpleSchema({
	name: {
		type: String,
		label: "Name"
	},
	
	desc: {
		type: String,
		label: "Description",
		optional: true
	},
	
	ingredients: {
		type: [Ingredient]
	},
	
	author: {
		type: String,
		label: "Author",
		autoValue: function() {
			return this.userId
		},
		autoform: {
			type: "hidden",
			label: false
		}
	},
	
	createdAt: {
		type: Date,
		label: "Created At",
		autoValue: function() {
			return new Date()
		},
		autoform: {
			type: "hidden",
			label: false
		}
	}
});

On the Ingredient Schema, you will notice that the first field is actually componentId, which should match with another collection from the DB called Components. On this Components collection, I have the ID field and a name of the component, which should match with the ingredient inside the recipe.

How do I create a Publish of the recipes, showing the name of the ingredient/component instead of the ID? (I could use native way or cottz:publish-relations package, but I haven’t had success with any of them).

Have you looked at reywood:publish-composite?

I just tried this one. I made a publish like this on the server:

Meteor.publishComposite('topTenRecipes', {
    find: function() {
        // Find top ten
        return Recipes.find({}, { sort: { name: 1 }, limit: 10 });
    },
    children: [
        {
            find: function(recipe) {
                // Find post author. Even though we only want to return
                // one record here, we use "find" instead of "findOne"
                // since this function should return a cursor.
                return Meteor.users.find(
                    { _id: recipe.authorId },
                    { limit: 1, fields: { profile: 1 } });
            }
        },
        {
            find: function(recipe) {
                // Find ingredients on recipe
                return Components.find(
                    { _id: recipe.componentId },
                    { sort: { name: 1 } });
            }
        }
    ]
});

The list of recipes is still working, and there is no error. I changed also the {{componentId}} to {{name}} to show the ingredient name, but still doesn’t show.

I don’t actually know how to move on from here to show the name of the ingredients instead of the IDs.

Try modifying your Components query:

return Components.find(
  { _id: { $in: recipe.componentId } },
  { sort: { name: 1 } });
}

Thanks for the answers, Rob!

I actually did like this:

return Components.find(
    { _id: { $in: recipe.ingredients.componentId } },
    { sort: { name: 1 } }
);

Because inside my recipe there is this array with the ingredients. Both ways, mine and yours, there was a big error on the terminal, but the understandable part was:

Exception in queued task: TypeError: Cannot read property ‘componentId’ of undefined

Any suggestions?

I made some progress here. I stopped using any packages to do the join and I’m doing all in pure Meteor.
The thing is: I managed how to do the join for example of userId to Meteor.users for example, but my previous problem still persist.

I don’t know how to structure the code to join an array inside a schema to another table. For example: I have recipes with ingredients inside it, but the ingredients are actually inside another table, and I only have the ID of each of those in an array inside the Recipe Schema. So what I have so far is the Schema that I sent on the first post in this thread, and this code:

Meteor.publish('recipes', function(){
	return Recipes.find({  });
});

Meteor.publish('recipeDetails', function(id){
	check(id, String);
	return Recipes.find({ author: this.userId, _id: id });
});

and

Template.Recipes.onCreated(function() {
	var self = this;
	self.autorun(function() {
		self.subscribe('recipes');
	});
});

Template.Recipes.helpers({
	recipes: function() {
		return Recipes.find();
	}
});

Template.RecipeItem.onCreated(function() {
	this.editMode = new ReactiveVar(false);
});

Template.RecipeItem.helpers({
	author: function() {
		return Meteor.users.findOne(this.author);
	},
	updateRecipeId: function() {
		return this._id;
	},
	editMode: function() {
		return Template.instance().editMode.get();
	}
});

and then the template:

<template name="Recipes">
	{{#if $.Session.get 'newRecipe'}}
		{{> NewRecipe}}
	{{else}}
		<button class="add-recipe">New Recipe</button>
	{{/if}}
	<section class="recipes">
		{{#if Template.subscriptionsReady}}
			{{#each recipes}}
				{{> RecipeItem}}
			{{else}}
				<p>Please add a recipe.</p>
			{{/each}}
		{{else}}
			<p>Loading</p>
		{{/if}}
	</section>
</template>

<template name="RecipeItem">
	<article class="recipe-item">
		<h3>{{name}}</h3>
		<p>Author: {{author.profile.firstName}} {{author.profile.lastName}}</p>
		<ul>
			{{#each ingredients}}
				<li>{{name}} - {{amount}}</li>
			{{/each}}
		</ul>
		<a href="/recipe/{{_id}}">View Details</a>
		<a href="" class="trash">Delete</a>
		<a href="" class="pencil">Edit</a>		
		{{#if editMode}}
			{{> quickForm collection="Recipes" id=updateRecipeId type="update" doc=this autosave=true}}
		{{/if}}
	</article>
</template>

So right now the {{name}} inside the ingredients, is blank, because I only have the componentId inside the array. Anybody?