三、存储数据和处理集合

在前一章中,我们学习了如何构建模板并在其中显示数据。 我们构建了应用的基本布局,并在首页列出了一些帖子示例。

在本章中,我们将在服务器上的数据库中持续添加 post 示例。 我们将学习如何在客户端访问这些数据,以及 Meteor 如何在客户端和服务器之间同步数据。

在本章中,我们将涵盖以下主题:

  • 在 Meteor 中存储数据
  • 达到顶点集合
  • 向集合中添加数据
  • 查询集合中的数据
  • 更新集合中的数据
  • “数据库无处不在”是什么意思
  • The difference between the server's and the client's databases

    注释

    如果你已经直接读了这一章,并想要遵循这些例子, 下载前一章的代码示例从这本书的 web 页面在 https://www.packtpub.com/books/content/support/17713从 GitHub 库 https://github.com/frozeman/book-building-single-page-web-apps-with-meteor/tree/chapter2

    这些代码示例还将包含所有样式文件,因此我们不必担心在过程中添加 CSS 代码。

Meteor 和数据库

Meteor 目前默认使用 MongoDB 在服务器上存储数据,尽管也有计划用于关系数据库的驱动程序。

注释

如果您有冒险精神,可以尝试社区构建的 SQL 驱动程序之一,例如https://atmospherejs.com/numtel/mysqlnumtel:mysql包。

MongoDB 是一个 NoSQL 数据库。 这意味着它基于平面文档结构,而不是关系表结构。 它的文档方法非常适合 JavaScript,因为文档是用 BJSON 编写的,而 BJSON 与 JSON 格式非常相似。

Meteor 有一个数据库无处不在的方法,这意味着我们有相同的 API 来查询数据库在客户端和服务器上。 然而,当我们在客户端查询数据库时,我们只能访问我们发布给客户端的数据。

MongoDB 使用的数据结构叫做集合,相当于 SQL 数据库中的表。 集合包含文档,其中每个文档都有自己唯一的 ID。 这些文档是类似 json 的结构,可以包含带值的属性,甚至可以包含多个维度,如下所示:

{
  "_id": "W7sBzpBbov48rR7jW",
  "myName": "My Document Name",
  "someProperty": 123456,
  "aNestedProperty": {
    "anotherOne": "With another string"
  }
}

这些集合用于在服务器的 MongoDB 以及客户端minimongo集合中存储数据,这是一个内存中的数据库,模拟真实的 MongoDB 的行为。

注释

我们将在本章的末尾讨论更多关于minimongo的内容。

MongoDB API 允许我们使用简单的基于 json 的查询语言从集合中获取文档。 我们可以通过附加选项只请求特定字段对返回的文档进行排序。 这些功能非常强大,特别是在客户端,可以以各种方式显示数据。

设置集合

为了看到所有这些的实际操作,让我们通过创建我们的第一个集合来开始它。

我们在my-meteor-blog文件夹中创建一个名为collections.js的文件。 我们需要在根文件夹中创建它,以便在客户机和服务器上都可用。 现在让我们将以下代码添加到collections.js文件:

Posts = new Mongo.Collection('posts');

这将使Posts变量全局可用,因为我们还没有使用var关键字,这将把它限制在这个文件的范围内。

Mongo.Collection是用于查询数据库的 API,有以下基本方法:

  • insert:该方法用于将文档插入数据库
  • update:该方法用于更新文档或其中的一部分
  • upsert:该方法用于插入或更新文档或文档的一部分
  • remove:该方法用于从数据库中删除文档
  • find:该方法用于查询数据库中的文档
  • findOne:这个方法用于只返回第一个匹配的文档

添加帖子示例

为了查询数据库中的帖子,我们需要添加一些帖子示例。 这必须在服务器上完成,因为我们希望持久地添加它们。 添加示例 post,执行以下步骤:

  1. 我们在my-meteor-blog/server文件夹中创建一个名为main.js的文件。 在这个文件中,我们将使用Meteor.startup()函数在服务器启动时执行代码。
  2. 然后我们添加 post 示例,但仅当集合为空时。 所以为了防止这种情况,我们在每次重启服务器时都添加它们,如下所示:

现在,当查看终端时,我们应该看到类似于下面的截图:

Adding post examples

注释

我们还可以使用 Mongo 控制台添加虚拟数据,而不是在代码中编写。

为了使用 Mongo 控制台,我们使用$ meteor启动 Meteor 服务器,然后在第二个终端运行$ meteor mongo,这将带我们到 Mongo shell。

在这里,我们可以简单地使用 MongoDB 的语法添加文档:

db.posts.insert({title: 'My First entry',
 slug: 'my-first-entry',
 description: 'Lorem ipsum dolor sit amet.',
 text: 'Lorem ipsum dolor sit amet...',
 timeCreated: 1405065868,
 author: 'John Doe'
}
)

查询集合

当我们保存更改时,服务器重启了。 此时,Meteor 在我们的数据库中添加了 5 个例子。

注释

如果服务器没有重新启动,那就意味着我们在代码中的某个地方犯了语法错误。 当我们手动重新加载浏览器或检查终端时,我们将看到 Meteor 给我们的错误,我们可以修复它。

如果我们搞砸了数据库中的一些东西,我们总是可以在终端中使用$ meteor reset命令重置它。

我们可以通过在浏览器中打开控制台并输入以下命令来查看这些帖子:

Posts.find().fetch();

这将返回一个包含 5 个元素的数组,每个元素都是我们的示例文章。

为了在首页列出这些新插入的帖子,我们需要用以下代码行替换home.js文件中postsListhelper 的内容:

Template.home.helpers({
  postsList: function(){
    return Posts.find({}, {sort: {timeCreated: -1}});
  }
});

正如我们所看到的,我们直接在助手中返回了集合游标。 这个返回值随后被传递给home模板中的{{#each}}块助手,该助手将在渲染postInList模板时遍历每个帖子。

注释

注意,Posts.find()返回一个游标,这在{{#each}}块帮助程序中使用时效率更高,而Posts.find().fetch()将返回一个包含文档对象的数组。 使用fetch(),我们可以在返回文档之前对其进行操作。

我们将一个 options 对象作为第二个参数传递给find()函数。 我们传递的选项将根据timeCreated-1对结果进行排序。 -1表示按降序排序(1表示升序)。

现在,当我们查看我们的浏览器时,我们会看到我们所有的 5 篇文章都被列出了,如下面的所示:

Querying a collection

更新集合

现在,我们知道了如何插入和获取数据,让我们看看如何更新数据库中的数据。

如前所述,我们可以使用浏览器的控制台来处理数据库。 在下一个示例中,我们将仅使用控制台来查看当我们更改数据时,Meteor 如何响应地更改模板。

为了能够在我们的数据库中编辑一篇文章,我们首先需要知道其条目的_id字段。 为了找到答案,我们需要输入以下命令:

Posts.find().fetch();

这将返回Posts集合中的所有文档,因为我们没有传递任何特定的查询对象。

数组返回,我们需要看一下最后一项,与我的第五个条目标题,并将_id字段复制到剪贴板使用C Cmd+(或【显示】Ctrl + C*如果我们在 Windows 或 Linux)。*

****### 注释

我们还可以简单地使用Posts.findOne(),它将给出它找到的第一个文档。

现在我们有了_id,我们可以简单地通过输入以下命令来更新我们的第五篇文章的标题:

Posts.update('theCopied_Id', {$set: {title: 'Wow the title changed!'}});

当我们执行这个命令时,我们会注意到第五篇文章的标题已经更改为我们的新标题,如果我们现在重新加载页面,我们会看到标题保持不变。 这意味着对数据库的更改是持久的。

要在客户端看到 Meteor 的反应,打开另一个浏览器窗口,导航到http://localhost:3000。 当我们现在通过执行以下命令再次更改标题时,我们将看到所有客户端实时更新:

Posts.update('theCopied_Id', {$set: {title: 'Changed the title again'}});

数据库无处不在

在 Meteor 中,我们可以使用浏览器控制台来更新数据,这意味着我们可以从客户端更新数据库。 这是因为 Meteor 会自动将这些变化同步到服务器,并相应地更新数据库。

这是因为我们的项目默认添加了autopublishinsecure核心包。 autopublish包自动将所有文档发布到每个客户端,而insecure包允许每个客户端通过其_id字段更新数据库记录。 显然,这在原型设计中很有效,但在生产中不可行,因为每个客户端都可以操作我们的数据库。

如果我们删除了insecure包,我们将需要添加“允许和拒绝”规则,以确定什么客户端可以更新,什么不可以更新; 否则,所有更新将被拒绝。 我们将在后面的章节中讨论如何设置这些规则,但目前这个包很适合我们,因为我们可以立即操作数据库。

在下一章中,我们将看到如何手动只向客户端发布某些文档。 我们将从删除autopublish包开始。

客户端和服务器集合之间的差异

Meteor 有一个数据库无处不在方法。 这意味着它在客户机和服务器上提供相同的 API。 数据流使用发布订阅模型进行控制。

在服务器上有真实的 MongoDB 数据库,它持久地存储数据。 在客户端,Meteor 有一个名为minimongo的包,这是一个纯内存数据库,模拟了 MongoDB 的大部分查询和更新功能。

每次客户端连接到 Meteor 服务器时,Meteor 都会下载客户端订阅的文档,并将其存储在本地的minimongo数据库中。 从这里开始,它们可以在模板中显示或通过函数进行处理。

当客户端更新文档时,Meteor 将其同步回服务器,在持久化存储到数据库之前,通过任何允许/拒绝函数传递。 反之亦然; 当服务器端数据库中的文档发生变化时,它将自动与订阅它的每个客户端同步,使每个连接的客户端保持最新。

小结

在本章中,我们学习了如何在 Meteor 的 MongoDB 数据库中持久化存储数据。 我们还了解了如何查询集合和更新文档。 我们理解“数据库无处不在”的方法意味着什么,以及 Meteor 如何保持每个客户端最新。

为了更深入地了解 MongoDB,并查询和更新集合,请查看以下资源:

你可以在https://www.packtpub.com/books/content/support/17713或在 GitHubhttps://github.com/frozeman/book-building-single-page-web-apps-with-meteor/tree/chapter3上找到本章的代码示例。

在下一章中,我们将看到如何使用发布和订阅来控制数据流,以便只向客户端发送必要的文档。****