使用Knex.js创建嵌套返回模型

时间:2017-11-27 03:39:07

标签: javascript node.js knex.js hapi.js

我正在使用Knex.js在Hapi.js路由中查询MySQL数据库。以下代码有效,但需要嵌套查询:

{
    path: '/recipes',
    method: 'GET',
    handler: (req, res) => {
        const getOperation = Knex.from('recipes')
        // .innerJoin('ingredients', 'recipes.guid', 'ingredients.recipe')
        .select()
        .orderBy('rating', 'desc')
        .limit(10)
        .then((recipes) => {
            if (!recipes || recipes.length === 0) {
                res({
                    error: true,
                    errMessage: 'no recipes found'
                });
            }

            const recipeGuids = recipes.map(recipe => recipe.guid);
            recipes.forEach(r => r.ingredients = []);
            const getOperation2 = Knex.from('ingredients')
                .whereIn('recipe', recipeGuids)
                .select()
                .then((ingredients) => {
                    recipes.forEach(r => {
                        ingredients.forEach(i => {
                            if (i.recipe === r.guid) {
                                r.ingredients.push(i);
                            }
                        });
                    });
                    res({
                        count: recipes.length,
                        data: recipes
                    });
                });
        });
    }
}

有没有办法用Knex.js创建一个返回模型,它具有与父id / guid匹配的嵌套对象,这样我就没有嵌套的promises?

3 个答案:

答案 0 :(得分:2)

简短回答:不。

使用Knex,您可以检索与SQL相同的数据,这是基于记录的,而不是基于对象的,因此您最接近的可能是使用连接来允许只执行一次选择来检索单个数组有元素:食谱,指导,成分。这将重复配方&每种成分的guid,通过使用嵌套对象来避免。 (有关此示例,请参阅@Fazal的以下答案。)

作为另一种选择,您可以将配料存储为配方表中的“blob”字段,但我不相信MySQL会允许您创建数组字段,因此在检索数据时,您必须将字段转换为数组。并在将其更新到表中之前将其从Array转换。例如:storableData = JSON.stringify(arrayData)arrayData = JSON.parse(storableData)

我会建议其他一些方法来帮助您改进代码。 (是的,我知道,这里不是问题):

  1. 将路由功能与数据处理分开。
  2. 此外,将数据操作功能与检索分开。
  3. 使用throw& .catch用于创建和处理不成功的响应。
  4. 路由,数据检索和数据操作的分离使得测试,调试和未来理解变得更容易,因为每个函数都具有更多的原子性。

    抛出/捕获不成功的进程条件使得通过允许在路由器响应处理中放置(大部分时间)单个.catch来更简单地进行更全面的错误处理(Hapi.js甚至可以执行此操作.catch为你???)。

    另外,请参阅我为记录错误添加的其他.catch.on('query-error'。您可能希望使用不同的日志记录机制而不是控制台。我用温斯顿。请注意.on('query-error'不是.catch。仍然会抛出一个Error(),并且必须在某处处理,这只会为您提供有关靠近源的失败的良好信息。

    (抱歉,以下代码未经测试)

    path: '/recipes',
    method: 'GET',
    handler: (req, res) => {
            return getRecipeNIngredients()
                .then((recipes) => {
                    res({
                        count: recipes.length,
                        data: recipes
                    });
                })
                .catch((ex) => {
                    res({
                        error: true,
                        errMessage: ex.message
                    });
                });
    };
    
        function getRecipeNIngredients() {
            let recipes = null;
            return getRecipes()
                .then((recipeList) => {
                    recipes = recipeList;
                    const recipeGuids = recipes.map(recipe => recipe.guid);
                    recipes.forEach(r => r.ingredients = []);
                    return getIngredients(recipeGuids);
                })
                .then((ingredients) => {
                    recipes.forEach(r => {
                        ingredients.forEach(i => {
                            if (i.recipe === r.guid) {
                                r.ingredients.push(i);
                            }
                        });
                    });
                    return recipes;
                })
                .catch((ex) => {
                    console.log(".getRecipeNIngredients ERROR ex:",ex); // log and rethrow error.
                    throw ex;
                });
        };
    
        function getRecipes() {
            return Knex.from('recipes')
                // .innerJoin('ingredients', 'recipes.guid', 'ingredients.recipe')
                .select()
                .orderBy('rating', 'desc')
                .limit(10)
                .on('query-error', function(ex, obj) {
                    console.log("KNEX getRecipes query-error ex:", ex, "obj:", obj);
                })
                .then((recipes) => {
                    if (!recipes || recipes.length === 0) {
                        throw new Error('no recipes found')
                    }
                })
        };
        function getIngredients(recipeGuids) {
            return Knex.from('ingredients')
                .whereIn('recipe', recipeGuids)
                .select()
                .on('query-error', function(ex, obj) {
                    console.log("KNEX getIngredients query-error ex:", ex, "obj:", obj);
                })
        };
    

    我希望这很有用! 加里。

答案 1 :(得分:0)

您可以轻松避免嵌套查询。只需使用子查询作为 -

knex.select('*')
  .from(function () {
    this.select('*').from('recipes').limit(10).as('recipes'); // limit here
  })
  .leftJoin('ingredients', 'ingredients.recipe_id', 'recipes.guid')
  .then((rec) => {
    console.log(rec);
  })

看几行代码。

答案 2 :(得分:0)

我创建了一个返回嵌套对象的库,即使它具有打字稿的类型

Nested Knex

import * as n from 'nested-knex';

n.array(
  n.type({
    id: n.number("recipe.id", { id: true }),
    title: n.string("recipe.title"),
    ingredients: n.array(
      n.type({
        id: n.number("ingredients.id", { id: true }),
        title: n.string("ingredients.title")
      })
    )
  })
)
  .withQuery(
    knex
      .from("recipes")
      .innerJoin("ingredients", "recipes.guid", "ingredients.recipe")
      .select()
      .orderBy("rating", "desc")
      .limit(10)
  )
  .then(recipes => {});

所以食谱甚至都有类型

enter image description here