初学者了解GraphQL-第二部分
欢迎回到 "理解GraphQL的初学者 "系列的第二部分。在本教程中,我们将建立关于食物的GraphQL字段如果你没有读过本系列的第一部分,请在阅读本部分之前先读一读。
作为复习,GraphQL是一种用于API的数据操作和查询语言。实施GraphQL的两个主要好处是
- 能够描述你想要回来的结构作为你的响应。
- 只需要一个端点来消耗一个或多个资源。
学习成果
-
检查GraphQL的文件目录
-
识别根字段和对象字段之间的区别
-
根据现有的Ruby on Rails模型创建一个GraphQL对象
-
创建一个GraphQL根字段,以定义你的响应结构
-
使用GraphQL根字段来查询数据库中的数据
-
检查GraphQL端点如何工作
在你开始之前
下载资源库,以便在本教程中继续学习。该资源库已经设置了GraphQL所需的模型和宝石。下载完毕后,给数据库装上种子。
下面的模型是
食物
| 属性 | 类型 |
| id | Bigint |
| 名称 | 字符串 |
| 原产地 | 符号 |
| 形象 | 形象 |
| 创建时间 | 时间戳 |
| update_at | 时间戳 |
营养
| 属性 | 类型 |
| id | 大数 |
| food_id | 大数 |
| 服务质量 | 绳子 |
| 卡路里 | 脂肪 |
| 总脂肪 | 脂肪 |
| 反式脂肪 | 饱和脂肪 |
| 饱和脂肪 | 字符串 |
| 胆固醇 | 字符串 |
| 钠 | 字符串 |
| 钾 | 字符串 |
| total_carbohydrate | 字符串 |
| 膳食纤维 | 字符串 |
| 糖类 | 膳食纤维 |
| 蛋白质 | 蛋白质 |
| 维生素_a | 字符串 |
| 维生素_c | 蛋白质 |
| 钙质 | 钙 |
| 铁 | 碱性物质 |
| 创建时间 | 时间戳 |
| update_at | 时间戳 |
GraphQL文件结构
所有与GraphQL有关的东西都在"/app"下名为 "graphql"的文件夹中找到。打开你的IDE编辑器,看看 "graphql"下的文件结构。
graphql文件夹的目录结构
在黄色高亮框中,这里有两个目录:
- "Mutations"
这个文件夹包含将修改(创建、更新或删除)数据的类。 - "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
我们要创建一个查询,为我们返回一个所有食物的列表。要做到这一点,我们需要创建字段,有两种类型的字段:
- 根字段根据选择的对象字段定义你的响应字段的结构。它们是GraphQL服务器的入口点(类似于端点)。
- 对象字段是一个对象的属性。
我们创建一个新的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
字段的格式如下:
- 字段名 (
:all_food)。 - 该字段的返回类型 (
[Types::FoodType])。 - 该字段是否永远为空(
null: false)。将此设置为false,意味着该字段永远不会为空。 - 该字段的描述 (
description: "Get all the food items.")。
祝贺你,你已经创建了你的第一个GraphQL查询!让我们去测试一下。让我们去测试一下吧
如何编写和运行你的GraphQL查询
为了测试你新创建的查询,我们使用游乐场,GraphiQL,来执行all_food 查询。要访问GraphiQL,请将以下URI添加到你的网络地址:localhost:3000/graphiql 。
你会看到这个页面:

GraphiQL游戏场
页面的左边是我们要写查询的地方。右侧将返回对该查询的响应。
靠近左上角的GraphiQL文本旁边有三个按钮。
GrapiQL操场菜单
- 这个按钮将执行你的查询。
- 这个按钮将重新格式化你的查询,使之看起来漂亮。
- 这个按钮将显示你以前运行的所有查询。
- 在菜单栏的右角有一个名为 **"<文档 "**的按钮 。
文件菜单项
如果你点击右上角的 **"< 文档 "**按钮,你可以找到基于你的模式的所有可能的查询。
文件探索器
这些查询被分成两组,查询和突变。"查询",包含所有不修改数据的查询。修改数据的查询可以在 "突变 "中找到。点击**"查询。查询"**,可以找到我们刚刚创建的“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"
}
]
}
}
干得好!现在,创建另一个查询来获取image 和place_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动作捆绑在一个端点下。这就是当你发出请求并获得响应时发生的事情。
当你执行查询时:
- 你用你的请求调用graphql端点(例如,查询和变量)。
- 然后graphql端点调用graphql_controller的execute方法来处理你的请求。
- 该方法渲染了一个包含迎合你的请求的响应的JSON。
- 你会得到一个响应。
自己动手试试 #1
尝试实现根字段称为 营养.如 所有食物,它返回所有的营养事实。
如果你需要任何帮助,请参考这个gist,其中包括一个样本查询和响应:https://gist.github.com/ShopifyEng/7c196bf443bdf26e55f827d65ee490a6
在现有的查询中增加一个字段
你可能已经注意到,营养表包含一个外键,即一个食品项目有一个营养事实。目前,它在模型层面上有关联,但在GraphQL层面上没有使用。为了让人们在查询食物时也能得到营养事实,我们需要给食物添加一个营养字段。
在food_type.rb中添加以下字段。
field :nutrition, Types::NutritionType, null: true
让我们执行以下查询,我们想知道每种食物的serving size 和calories 。
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
由于我们已经到了本教程的结尾,让我们来回顾一下我们所学到的东西吧
- 如果存在同名的模型,GraphQL会生成并填充一个对象。
- 根字段定义了你的响应的结构,是你的GraphQL服务器的入口点。
- 对象字段是一个对象的属性。
- 所有请求都由graphql_controller的execute方法处理,并返回一个JSON响应。
我希望你喜欢创建一些GraphQL查询!有一件事你可能还想知道,我们如何更新这些ActiveRecord对象?在理解GraphQL的第三部分,我们将继续创建称为突变的查询,创建、更新或删除数据。
如果你想看第二部分的完成代码,请查看名为part-2-solution的分支。