初学者了解GraphQL-第二部分

192 阅读8分钟

初学者了解GraphQL-第二部分

欢迎回到 "理解GraphQL的初学者 "系列的第二部分。在本教程中,我们将建立关于食物的GraphQL字段如果你没有读过本系列的第一部分,请在阅读本部分之前先读一读。

作为复习,GraphQL是一种用于API的数据操作和查询语言。实施GraphQL的两个主要好处是

  1. 能够描述你想要回来的结构作为你的响应。
  2. 只需要一个端点来消耗一个或多个资源。

学习成果

  • 检查GraphQL的文件目录

  • 识别根字段和对象字段之间的区别

  • 根据现有的Ruby on Rails模型创建一个GraphQL对象

  • 创建一个GraphQL根字段,以定义你的响应结构

  • 使用GraphQL根字段来查询数据库中的数据

  • 检查GraphQL端点如何工作

在你开始之前

下载资源库,以便在本教程中继续学习。该资源库已经设置了GraphQL所需的模型和宝石。下载完毕后,给数据库装上种子。

下面的模型是

食物

属性类型
idBigint
名称字符串
原产地符号
形象形象
创建时间时间戳
update_at时间戳

营养

属性类型
id大数
food_id大数
服务质量绳子
卡路里脂肪
总脂肪脂肪
反式脂肪饱和脂肪
饱和脂肪字符串
胆固醇字符串
字符串
字符串
total_carbohydrate字符串
膳食纤维字符串
糖类膳食纤维
蛋白质蛋白质
维生素_a字符串
维生素_c蛋白质
钙质
碱性物质
创建时间时间戳
update_at时间戳

GraphQL文件结构

所有与GraphQL有关的东西都在"/app"下名为 "graphql"的文件夹中找到。打开你的IDE编辑器,看看 "graphql"下的文件结构。

graphql文件夹的目录结构 在黄色高亮框中,这里有两个目录:

  1. "Mutations"
    这个文件夹包含将修改(创建、更新或删除)数据的类。
  2. "Types"
    这个文件夹包含了定义将被返回的内容的类。以及可以调用的查询类型(mutation_type.rb 和 query_type.rb)。

在红色高亮框中,有一个重要的文件需要注意。

graphql文件夹的目录结构 food_app_schema.rb这个类,定义了你可以进行的查询:

class FoodAppSchema < GraphQL::Schema
  # All mutation queries can be found in mutation_type.rb (Types::MutationType).
  mutation(Types::MutationType)
  # All non-mutation queries can be found in query_type.rb (Types::QueryType).
  query(Types::QueryType)

  # ...
end

创建你的第一个GraphQL查询 all_food

我们要创建一个查询,为我们返回一个所有食物的列表。要做到这一点,我们需要创建字段,有两种类型的字段:

  1. 根字段根据选择的对象字段定义你的响应字段的结构。它们是GraphQL服务器的入口点(类似于端点)。
  2. 对象字段是一个对象的属性。

我们创建一个新的GraphQL对象,称为food 。在你的终端上运行rails g graphql:object food 。这将创建文件food_type.rb ,其中充满了在你的食物表中发现的所有属性,在db/schema.rb 。你生成的food_type.rb将看起来像。

module Types
  class FoodType < Types::BaseObject
    field :id, ID, null: false
    field :name, String, null: true
    field :place_of_origin, String, null: true
    field :image, String, null: true
    field :created_at, GraphQL::Types::ISO8601DateTime, null: false
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
  end
end

这个类包含了所有的对象字段,揭示了一个对象的特定数据。接下来,我们需要创建根字段,使我们能够向GraphQL服务器请求我们想要的东西。转到query_type.rb文件,这是一个包含所有根字段的类。

module Types
  class QueryType < Types::BaseObject
    # Add `node(id: ID!) and `nodes(ids: [ID!]!)`
    include GraphQL::Types::Relay::HasNodeField
    include GraphQL::Types::Relay::HasNodesField

    # Add root-level fields here.
    # They will be entry points for queries on your schema.

    # TODO: remove me
    field :test_field, String, null: false,
      description: "An example field added by the generator"
    def test_field
      "Hello World!"
    end
  end
end

删除字段test_field 和它的方法。创建一个叫做all_food 的字段,如下所示。由于食物既是单数又是复数,我们用all_food 来表示复数。

field :all_food, [Types::FoodType], null: false, description: 'Get all the food items.'

def all_food
  Food.all
end

字段的格式如下:

  1. 字段名 (:all_food)。
  2. 该字段的返回类型 ([Types::FoodType])。
  3. 该字段是否永远为空(null: false)。将此设置为false,意味着该字段永远不会为空。
  4. 该字段的描述 (description: "Get all the food items.")。

祝贺你,你已经创建了你的第一个GraphQL查询!让我们去测试一下。让我们去测试一下吧

如何编写和运行你的GraphQL查询

为了测试你新创建的查询,我们使用游乐场,GraphiQL,来执行all_food 查询。要访问GraphiQL,请将以下URI添加到你的网络地址:localhost:3000/graphiql

你会看到这个页面:

GraphiQL游戏场

页面的左边是我们要写查询的地方。右侧将返回对该查询的响应。

靠近左上角的GraphiQL文本旁边有三个按钮。 GrapiQL操场菜单

  1. 这个按钮将执行你的查询。
  2. 这个按钮将重新格式化你的查询,使之看起来漂亮。
  3. 这个按钮将显示你以前运行的所有查询。
  4. 在菜单栏的右角有一个名为 **"<文档 "**的按钮

文件菜单项 如果你点击右上角的 **"< 文档 "**按钮,你可以找到基于你的模式的所有可能的查询。

文件探索器 这些查询被分成两组,查询和突变。"查询",包含所有不修改数据的查询。修改数据的查询可以在 "突变 "中找到。点击**"查询。查询"**,可以找到我们刚刚创建的“all_food” 查询。

查询屏幕 在点击**"query**:查询",你会发现你可以进行的所有可能的查询。如果你点击[Food!]! ,你会看到我们可以询问的所有可能的字段。

all_food查询中的字段 这些是你可以在all_food 查询中使用的所有可能的字段。记住,GraphQL允许我们准确地描述我们需要的东西。比方说,我们只想要所有食品的ID和名称。我们把查询写成

query {
  allFood {
    id
    name
  }
}

点击执行按钮来运行查询,你会得到以下回应:

{
  "data": {
    "allFood": [
      {
        "id": "1",
        "name": "Spaghetti"
      },
      {
        "id": "2",
        "name": "Beef Chow Mein"
      },
      {
        "id": "3",
        "name": "Tortilla de patatas"
      }
    ]
  }
}

干得好!现在,创建另一个查询来获取imageplace_of_origin 字段。

query {
  allFood {
    image
    placeOfOrigin
  }
}

你会得到这样的回应:

{
  "data": {
    "allFood": [
      {
        "image": "https://www.not-a-link.com/spaghetti.png",
        "placeOfOrigin": "Italy"
      },
      {
        "image": "https://www.not-a-link.com/beef-chow-mein.png",
        "placeOfOrigin": "China"
      },
      {
        "image": "https://www.not-a-link.com/tortilla-de-patatas.png",
        "placeOfOrigin": "Spain"
      }
    ]
  }
}

幕后发生了什么?

回顾第一部分,GraphQL有一个单一的 "智能 "端点,将所有不同类型的RESTful动作捆绑在一个端点下。这就是当你发出请求并获得响应时发生的事情。 当你执行查询时:

  1. 你用你的请求调用graphql端点(例如,查询和变量)。
  2. 然后graphql端点调用graphql_controller的execute方法来处理你的请求。
  3. 该方法渲染了一个包含迎合你的请求的响应的JSON。
  4. 你会得到一个响应。

自己动手试试 #1

尝试实现根字段称为 营养.如 所有食物,它返回所有的营养事实。

如果你需要任何帮助,请参考这个gist,其中包括一个样本查询和响应:https://gist.github.com/ShopifyEng/7c196bf443bdf26e55f827d65ee490a6

在现有的查询中增加一个字段

你可能已经注意到,营养表包含一个外键,即一个食品项目有一个营养事实。目前,它在模型层面上有关联,但在GraphQL层面上没有使用。为了让人们在查询食物时也能得到营养事实,我们需要给食物添加一个营养字段。

food_type.rb中添加以下字段。

field :nutrition, Types::NutritionType, null: true

让我们执行以下查询,我们想知道每种食物的serving sizecalories

query {
  allFood {
    name
    nutrition {
      servingSize
      calories
    }
  }
}

你会得到这样的回复:

{
  "data": {
    "allFood": [
      {
        "name": "Spaghetti",
        "nutrition": {
          "servingSize": "100 g",
          "calories": "158"
        }
      },
      {
        "name": "Beef Chow Mein",
        "nutrition": {
          "servingSize": "100 g",
          "calories": "459"
        }
      },
      {
        "name": "Tortilla de patatas",
        "nutrition": {
          "servingSize": "100 g",
          "calories": "149"
        }
      }
    ]
  }
}

Hooray!我们现在知道了每种食物的食用量和卡路里!

到目前为止,我们学会了如何创建根字段来查询一个特定资源的所有数据。让我们写一个查询,以查看基于id 的数据。

写一个带参数的查询

在query_type.rb中,我们需要添加另一个名为food 的根字段,它需要并接受一个名为id 的参数:

field :food, Types::FoodType, null: false do
  description 'Get a food item based on id.'
  argument :id, ID, required: true
end

def food(id:)
  Food.find(id)
end

在GraphiQL上,让我们执行这个查询:

query {
  food(id: 1) {
    name
    nutrition {
      servingSize
      calories
    }
  }
}

你会得到这样的回应:

{
  "data": {
    "food": {
      "name": "Spaghetti",
      "nutrition": {
        "servingSize": "100 g",
        "calories": "158"
      }
    }
  }
}

自己试一试 #2

这一次,创建一个名为find_food 的根字段,该字段基于place_of_origin ,返回一组数据。

如果你需要任何帮助,请参考这个gist,其中包括一个样本查询和响应:https://gist.github.com/ShopifyEng/1f92cee91f2932a0ef665594418764d3

由于我们已经到了本教程的结尾,让我们来回顾一下我们所学到的东西吧

  1. 如果存在同名的模型,GraphQL会生成并填充一个对象。
  2. 根字段定义了你的响应的结构,是你的GraphQL服务器的入口点。
  3. 对象字段是一个对象的属性。
  4. 所有请求都由graphql_controller的execute方法处理,并返回一个JSON响应。

我希望你喜欢创建一些GraphQL查询!有一件事你可能还想知道,我们如何更新这些ActiveRecord对象?在理解GraphQL的第三部分,我们将继续创建称为突变的查询,创建、更新或删除数据。

如果你想看第二部分的完成代码,请查看名为part-2-solution的分支。