【问题标题】:How to properly setup a polymorphic model using Sequelize and GraphQL如何使用 Sequelize 和 GraphQL 正确设置多态模型
【发布时间】:2017-06-25 00:11:29
【问题描述】:

我目前正在尝试通过为自己开发一个膳食计划应用程序来学习 React。来自传统的商业编程世界,我决定使用 MySQL 作为后端,并使用 Sequelize/GraphQL 作为我的数据接口。

我的数据模型如下:
-用户吃饭
-膳食具有日期、类型(早餐、午餐等)和“MealItems”的属性
-MealItems 可以是 FoodItem 或 Recipe
-Recipes 基本上是 FoodItems 的集合

我用这个数据模式实现了这个想法(在 Access 中完成了非常快速的模型):Meal Planner Model

我已经成功编写了一个 Sequelize 模型,它可以完全按照我的需要创建表和约束。我使用官方 Sequelize 文档中的 n:m 关联示例来创建 MealItems 查找表,该表应该允许模型根据范围(“ItemType”)动态返回 FoodItem 或 Recipe。 (但我不知道我是否正确地完成了该部分,因为我无法通过原始 SQL 查询以外的任何方式实际提取数据。)

我的项目的完整源代码可以在这里找到:(相关的数据组件在'./src/data'下) https://github.com/philspins/NourishMe

续集模型:

//
// Model definitions
// -----------------------------------------------------------------------------    
import DataType from "sequelize";
import Model from "../sequelize";

const FoodItem = Model.define("FoodItem",
{

    Name: { type: DataType.STRING(100) },
    Quantity: { type: DataType.STRING(32) },
    Weight: { type: DataType.INTEGER },
    Calories: { type: DataType.INTEGER },
    Protein: { type: DataType.DOUBLE },
    Carbs: { type: DataType.DOUBLE },
    Fat: { type: DataType.DOUBLE },
    Fibre: { type: DataType.DOUBLE },
    ImageURL: { type: DataType.TEXT }
});

const Recipe = Model.define("Recipe",
{
    Name: { type: DataType.STRING(100) },
    Instructions: { type: DataType.TEXT },
    ImageURL: { type: DataType.TEXT }
});

const Ingredient = Model.define("Ingredient");

const Meal = Model.define("Meal",
{
    Day: { type: DataType.DATE }
});

const MealType = Model.define("MealType",
{
    Name: { type: DataType.STRING(100) }
});

const MealItem = Model.define("MealItem",
{
    id: {type: DataType.INTEGER, primaryKey: true, autoIncrement: true},
    ItemType: { type: DataType.STRING(100) },
    ItemID: { type: DataType.STRING(100) },
    Quantity: { type: DataType.DOUBLE }
},
{
    instanceMethods: {
        getItem: function() {
            return this["get" + this.get("ItemType").substr(0,1).toUpperCase() + this.get("ItemType").substr(1)]();
        }
    }
});

//
// Recipe and FoodItem relations
// -----------------------------------------------------------------------------
Recipe.FoodItems = Recipe.belongsToMany(FoodItem, {
    through: Ingredient,
    as: "FoodItems"
});
FoodItem.Recipes = FoodItem.belongsToMany(Recipe, {
    through: Ingredient,
    as: "Recipes"
});


//
// Meals relationships with Recipe and FoodItem
// -----------------------------------------------------------------------------
Meal.belongsToMany(Recipe, {
    through: MealItem,
    foreignKey: "ItemID",
    constraints: false,
    scope: {
        ItemType: "Recipe"
    }
});
Recipe.belongsToMany(Meal, {
    through: MealItem,
    foreignKey: "ItemID",
    constraints: false,
    as: "Recipe"
});
Meal.belongsToMany(FoodItem, {
    through: MealItem,
    foreignKey: "ItemID",
    constraints: false,
    scope: {
        ItemType: "FoodItem"
    }
});
FoodItem.belongsToMany(Meal, {
    through: MealItem,
    foreignKey: "ItemID",
    constraints: false,
    as: "FoodItem"
});


//
// Other Meal relationships
// -----------------------------------------------------------------------------
Meal.MealItems = Meal.hasMany(MealItem, {foreignKey: {allowNull: false}, onDelete: "CASCADE"});
Meal.User = User.hasMany(Meal, {foreignKey: {allowNull: false}, onDelete: "CASCADE"});
Meal.MealType = MealType.hasMany(Meal, {foreignKey: {allowNull: false}, onDelete: "CASCADE"});

我设置了 GraphQL 类型和查询以返回除 Meal 之外的所有数据。除了 MealItem 表中实际存在的值之外,我无法让它返回任何内容。我能够将 FoodItem 链接到 Recipe 没有问题,并检索在 Recipes 中嵌入了 FoodItems 的 JSON 包,但无法弄清楚如何用 MealItems 做同样的事情。这是我现在使用的模型:[GraphQL 模型的可视化][3] 但我希望 Meals 能够在输出中嵌入 FoodItems 或 Recipes 而不是 MealItems。

这是我的 GraphQL 代码,因为它正在运行:

import {GraphQLObjectType,
    GraphQLList,
    GraphQLNonNull,
    GraphQLID,
    GraphQLString} from "graphql";
import {resolver, attributeFields} from "graphql-sequelize";
import {Meal, 
    Recipe, 
    FoodItem as 
    FoodModel, 
    MealItem as MealItemModel} from "../models";

const FoodType = new GraphQLObjectType({
    name: "FoodItem",
    fields: attributeFields(FoodModel),
    resolve: resolver(FoodModel)
});

const RecipeType = new GraphQLObjectType({
    name: "Recipe",
    fields: {
        id: { type: new GraphQLNonNull(GraphQLID) },
        Name: { type: GraphQLString },
        Instructions: { type: GraphQLString },
        ImageURL: { type: GraphQLString },
        Ingredients: {
            type: new GraphQLList(FoodType),
            resolve: resolver(Recipe.FoodItems) }
    }
});

const MealTypeType = new GraphQLObjectType({
    name: "MealType",
    fields: attributeFields(MealType)
});

const MealItemType = new GraphQLObjectType({
    name: "MealItem",
    fields: attributeFields(MealItemModel),
    resolve: resolver(MealItemModel)
});

const MealType = new GraphQLObjectType({
    name: "Meal",
    fields: {
        id: { type: new GraphQLNonNull(GraphQLID) },
        Day: { type: DateType },
        UserId: { type: GraphQLID },
        MealTypeId: { type: GraphQLID },
        MealItems: {
            type: new GraphQLList(MealItemType),
            resolve: resolver(Meal.MealItems)
        }
    }
});

const Meals = {
    type: new GraphQLList(MealType),
    resolve: resolver(Meal)
};

const schema = new Schema({
    query: new ObjectType({
        name: "Root",
        fields: {
            Meals
        }
    })
});


认为为了让 MealType 动态返回 FoodType 或 RecipeType 而不是 MealItemType,我需要做的事情就是这样。但这是我无法开始工作的原因,也是这个极其冗长的问题的原因。

function resolveMealItemType(value){
    if(value.ItemType == "Recipe"){return RecipeType;}else{return FoodType;}
}

const MealItemType = new GraphQLUnionType({
    name: "MealItem",
    types: [RecipeType, FoodType],
    resolveType: resolveMealItemType
});

const MealType = new GraphQLObjectType({
    name: "Meal",
    fields: {
        id: { type: new GraphQLNonNull(GraphQLID) },
        Day: { type: DateType },
        UserId: { type: GraphQLID },
        MealTypeId: { type: GraphQLID },
        MealItems: {
            type: new GraphQLList(MealItemType),
            resolve: resolver(Meal.MealItems)
        }
    }
});

当前查询和输出:

{
  Meal {
    Day
    MealTypeId
    UserId
    MealItems {
      ItemType
      ItemID
    }
  }
}

{
  "data": {
    "Meal": {
      "Day": "2017-02-07T16:18:47.000Z",
      "MealTypeId": "1",
      "UserId": "1",
      "MealItems": [
        {
          "ItemType": "Recipe",
          "ItemID": 1
        },
        {
          "ItemType": "FoodItem",
          "ItemID": 25
        }
      ]
    }
  }
}

所需的查询和输出:

{
  Meal {
    Day
    MealTypeId
    UserId
    MealItems {
      ... on FoodItem {
        Name
        Quantity
        Weight
        Calories
        Carbs
        Protein
        Fat
        Fibre
      }
      ... on Recipe {
        Name
        Instructions
        Ingredients {
          Name
          Quantity
          Weight
          Calories
          Carbs
          Protein
          Fat
          Fibre
        }
      }
    }
  }
}


{
  "data": {
    "Meal": {
      "Day": "2017-02-07T15:30:10.000Z",
      "MealTypeId": "1",
      "UserId": "1",
      "MealItems": [
        {
          "Name": "Fish, Halibut, Pacific",
          "Quantity": "4 oz uncooked",
          "Weight": 113,
          "Calories": 124,
          "Carbs": 0,
          "Protein": 24,
          "Fat": 3,
          "Fibre": 0
        },
        {
          "Name": "Test Recipe 1",
          "Instructions": "Recipe instructions go here...",
          "Ingredients": [
            {
              "Name": "Fish, Halibut, Pacific",
              "Quantity": "4 oz uncooked",
              "Weight": 113,
              "Calories": 124,
              "Carbs": 0,
              "Protein": 24,
              "Fat": 3,
              "Fibre": 0
            },
            {
              "Name": "Sardines (herring), canned in olive oil",
              "Quantity": "1 can (3.2 oz)",
              "Weight": 91,
              "Calories": 191,
              "Carbs": 0,
              "Protein": 23,
              "Fat": 11,
              "Fibre": 0
            }
        }
      ]
    }
  }
}

【问题讨论】:

  • 你最终解决了这个问题吗?
  • @iwasrobbed 不,我能让它工作的最好方法是这样的:{ Meals{ Day MealType { Name } User { login } FoodItems { Name Quantity Weight Calories Carbs Protein Fat Fibre } Recipes { Name Instructions Ingredients { Name Quantity Weight Calories Carbs Protein Fat Fibre } } } }
  • 至少感谢来源;它帮助我建立了最初的多态关联
  • @iwasrobbed 别担心,祝你好运!如果你能正常工作,我很乐意看到结果。

标签: sequelize.js graphql


【解决方案1】:

不要直接在 GraphQLObjectType 内设置密钥 resolver

...
const FoodType = new GraphQLObjectType({
    name: "FoodItem",
    fields: attributeFields(FoodModel),
    resolve: resolver(FoodModel), // <--- this is wrong, it won't be used
    // Only set resolvers inside fields, not on the root of the object
});
...

那么您使用GraphQLUnionType 是对的。
但是在MealType 的解析函数中,您应该返回FoodItems 和Recipes 的合并数组,而不是MealItems 表的条目。您是在与工会说“我要返回 FoodItem 或 Recipe 的列表”,所以这就是您应该做的。

所以,

const MealType = new GraphQLObjectType({
    ...
    fields: {
        ...
        MealItems: {
            type: new GraphQLList(MealItemType),
            resolve: (meal, args, context) => {
                return Promise.all([
                    Meal.getFoodItems(), // pseudo-code
                    Meal.getRecipes(), // pseudo-code
                ])
                .then(([ foodItems, recipes ]) => foodItems.concat(recipes));
            },
        },
        ...
    },
});

【讨论】:

  • 越来越近了,但是当我使用 Meal.get...() 时收到此错误:"TypeError: _models.Meal.getFoodItems is not a function at resolve (/var/www/html/nourish/src/data/types/MealType.js:46:11)" 当我使用 resolve(Meal.FoodItems) 时,它说 foodItems.concat 不是一个函数。
  • 我刚刚将这段代码添加到我的主 Express 服务器 models.Meal.getFoodItems().then(item =&gt; { logger.info(JSON.stringify(item), null, 2); }); 并确认无论出于何种原因,Sequelize 都没有创建 getFoodItems 函数。
  • 哦,是的,当然。我只是在使用模拟名称 :) 尽你所能获取食物,获取食谱也是如此
  • 这里的目的只是为了展示 GraphQL 推理,而不是 sequelize 部分
  • 您需要避免我的回答是您需要同时获取 FoodItemsRecipes 并将合并的数组返回给 GraphQL
猜你喜欢
  • 2020-05-30
  • 1970-01-01
  • 1970-01-01
  • 2021-07-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多