title: How to properly setup a polymorphic model using Sequelize and GraphQL
body: <p>I am currently trying to learn React by developing a meal planner app for myself.  Coming from a traditional business programming world I have decided on using MySQL as a backend, and Sequelize/GraphQL as my data interface.</p>
  
  <p>I have a data model that goes as follows:<br/>
  -Users have Meals<br/>
  -Meals have the properties of day, type (breakfast, lunch, etc) and "MealItems"<br/>
  -MealItems can be either a FoodItem or a Recipe<br/>
  -Recipes are basically a collection of FoodItems</p>
  
  <p>I implemented that idea with this data schema (really quick mockup done in Access): <a href="https://i.stack.imgur.com/kjzvx.png" rel="noreferrer">Meal Planner Model</a></p>
  
  <p>I have managed to code up a Sequelize model that creates the tables and constraints exactly how I want.  I used the n:m association example from the official Sequelize documentation to create the MealItems lookup table that should allow for the model to dynamically return either a FoodItem or Recipe, based on the scope ("ItemType").  (But I don't know if I did that part correctly as I can't manage to actually pull the data through anything other than raw SQL queries.)</p>
  
  <p>The full source code for my project can be found here: (the relevant data components are under './src/data')
  <a href="https://github.com/philspins/NourishMe" rel="noreferrer">https://github.com/philspins/NourishMe</a></p>
  
  <p>Sequelize model:</p>
  
  <pre><code>//
  // 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"});
  </code></pre>
  
  <p>I have GraphQL types and queries setup to return all data except for Meal.  I can not get it to return anything except for the values that are actually in the MealItem table.  I was able to link up FoodItem to Recipe no problem, and retrieve a JSON package that has FoodItems embedded in Recipes, but can't figure out how to do the same thing with MealItems.  This is the model as I have it working now: [Visualization of GraphQL model as it is][3]
  But I would like to have Meals be able to have either FoodItems or Recipes embedded in the output rather than MealItems.</p>
  
  <p>And this is my GraphQL code, as I have it working:</p>
  
  <pre><code>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
          }
      })
  });
  </code></pre>
  
  <p><br />
  What I <em>think</em> I need to do, in order to get the MealType to dynamically return either a FoodType or RecipeType instead of MealItemType, is something along the lines of this.  But this is what I can't get to work, and is the cause of this extremely lengthy question.</p>
  
  <pre><code>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)
          }
      }
  });
  </code></pre>
  
  <p>Current query, and output:</p>
  
  <pre><code>{
    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
          }
        ]
      }
    }
  }
  </code></pre>
  
  <p>Desired query, and output:</p>
  
  <pre><code>{
    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
              }
          }
        ]
      }
    }
  }
  </code></pre>