尝试在多对多关系中创建实例和多个相关实例

时间:2017-04-14 23:32:37

标签: node.js postgresql express sequelize.js

我正在尝试使用联结表创建一个具有多对多关系的实例和多个相关实例。

在创建多个相关实例时,我还需要为联结表上的属性添加值。我不知道是否缺乏引起我问题的续集或承诺的知识。

我正在使用的代码如下。此代码确实将项添加到数据库,但我需要在操作完成后重定向,这不起作用。

基本上,我需要创建一个食谱。一旦创建,我需要创建成分并将它们与该配方相关联。成分存储在来自HTML页面上的表单的数组中。在关联Ingredients时,我需要将recipe_quantity添加到RecipeIngredients表中,这是关系的直接部分(联结表)。

global.db.Recipe.belongsToMany(
    global.db.Ingredient, 
    { 
        as: 'Ingredients', 
        through: global.db.RecipeIngredients, 
        foreignKey: 'recipe_id' 
    });
global.db.Ingredient.belongsToMany(
    global.db.Recipe, 
    { 
        as: 'Recipes', 
        through: global.db.RecipeIngredients, 
        foreignKey: 'ingredient_id' 
    });

router.post('/new', ensureLoggedIn, bodyParser.json(), function (req, res) {
    var recipeName = req.body.recipe_name;
    var steps = req.body.steps;
    var ingredients = req.body.ingredients;
    var ingredientQty = {};
    var currentIngredient;
    var ingredientsToAdd = [];

    db.Recipe.create({
        recipe_name: recipeName,
        directions: steps,
        FamilyId: req.user.FamilyId,
        CreatedBy: req.user._id
    })
    .then(function (recipe) {
        for (var i = 0; i < ingredients.length; i++) {

            currentIngredient = ingredients[i];
            ingredientQty[currentIngredient.ingredient_name] = 
currentIngredient.quantity;

            db.Ingredient.findOrCreate({
                where: { 
                    ingredient_name: currentIngredient.ingredient_name, 
                    FamilyId: req.user.FamilyId 
                }
            })
            .spread(function (ingredient, created) {
                if (created) {
                    console.log("Added Ingredient to DB: " + 
                    currentIngredient.ingredient_name);
                }

            ingredient.Recipes = {
                ingredient_quantity: 
                    ingredientQty[ingredient.ingredient_name]
            };
            ingredient.CreatedBy = req.user._id;
            recipe.addIngredient(ingredient)
            .then(function () {
                console.log("Added Ingredient " + ingredient.ingredient_name 
                + " to Recipe " + recipe.recipe_name);
            });
        })
    }

})
.finally(function(recipe){
    res.redirect('/recipes');
});
});

非常感谢任何帮助。我知道我遇到了问题,因为我试图在循环中使用promises,我只是不知道我还能做到这一点。

2 个答案:

答案 0 :(得分:1)

使用sequelize,您可以一步创建对象及其关联对象,提供您正在创建的所有对象都是。这也称为嵌套创建。请参阅this链接并向下滚动到标题为&#34;使用关联创建&#34;

的部分

即将到来,RecipeIngredient之间存在多对多关系,RecipeIngredients为联接表。

假设您要创建一个新的Recipe对象,例如:

var myRecipe = {
  recipe_name: 'MyRecipe',
  directions: 'Easy peasy',
  FamilyId: 'someId',
  CreatedBy: 'someUserId'
}

还有一系列成分对象,例如:

var myRecipeIngredients = [
  { ingredient_name: 'ABC', FamilyId: 'someId'},
  { ingredient_name: 'DEF', FamilyId: 'someId'},
  { ingredient_name: 'GHI', FamilyId: 'someId'}]

// associate the 2 to create in 1 step
myRecipe.Ingredients = myRecipeIngredients;

现在,您可以一步创建myRecipe及其关联的myRecipeIngredients,如下所示:

Recipe.create(myRecipe, {include: {model: Ingredient}})
.then(function(createdObjects){
   res.json(createdObjects);
})
.catch(function(err){
   next(err);
});

这就是全部!!
Sequelize将在Recipe中创建1行,在Ingredient中创建3行,在RecipeIngredients中创建3行以关联它们。

答案 1 :(得分:0)

我能够解决我遇到的问题。答案here帮助我找到了解决方案。 我发布下面的解决方案,以防其他人遇到类似的问题。我创建了一个变量来存储来自Recipe.create()的Promise,我使用Promise.map来查找表单数据中的所有成分。因为findOrCreate返回一个包含Promise的数组,如果该项是创建的,则返回一个布尔值,然后我必须从Promise.map函数的结果中获取实际的成分。所以我使用JavaScript array.map()函数从数组中获取第一个项目。最后,再次使用Promise.map将每个成分添加到配方

var ingredients = req.body.ingredients,
    recipeName = req.body.recipeName,
    ingredientsQty = {}; // Used to map the ingredient and quantity for the 
                         // relationship, because of the Junction Table

var recipe = models.Recipe.create({recipe_name: recipeName});

// Use Promise.map to findOrCreate all ingredients from the form data
Promise.map(ingredients, function(ing){
    ingredientsQty[ing.ingredient_name] = ing.ingredient_quantity;
    return models.Ingredient.findOrCreate({ where: { ingredient_name: ing.ingredient_name}});
})

// Use JavaScript's Array.prototype.map function to return the ingredient 
// instance from the array returned by findOrCreate
.then(function(results){
    return results.map(function(result){
        return result[0];
    });
})

// Return the promises for the new Recipe and Ingredients
.then(function(ingredientsInDB){
    return Promise.all([recipe, ingredientsInDB]);
})

// Now I can use Promise.map again to create the relationship between the / 
// Recipe and each Ingredient
.spread(function(addedRecipe, ingredientsToAdd){
    recipe = addedRecipe;
    return Promise.map(ingredientsToAdd, function(ingredientToAdd){
        ingredientToAdd.RecipeIngredients = {
            ingredient_quantity: ingredientsQty[ingredientToAdd.ingredient_name]
        };

        return recipe.addIngredient(ingredientToAdd);
    });
})

// And here, everything is finished
.then(function(recipeWithIngredients){
   res.end
});