05 任务A 创建应用程序

优质
小牛编辑
121浏览
2023-12-01

我们的第一个开发任务就是创建网站入口,让我们可以维护商品信息,包括创建商品,编辑已经创建的商品以及删除不需要的商品等等。我们会通过小步迭代的方式开发应用程序,而小步的意思是「在可度量的几分钟内」。一般情况下,我们的迭代包含多个步骤,比如迭代 C 中会有步骤 C1,C2,C3 等等。当前这个迭代中只包含两步。让我们开始吧。

迭代 A1:创建商品维护应用

Depot 应用的核心其实是一个数据库。在后面的流程开展之前需要进行相应的安装,配置和测试,这样可以避免遇见许多令人头疼的问题。如果你不确定你需要什么,就默认安装并让事情继续。如果你知道自己的需要,在 Rails 中也很简单,你可以描述你的配置即可。

创建 Rails 应用

在 2.1 节,我们已经了解了如何创建新 Rails 应用。这里我们要再做一遍。进入命令行窗口,键入 rails new 并紧随项目名称。这个例子中,我们的项目叫做 depot,所以要确认自己没有处于一个已经存在应用内容的文件夹中,并且输入如下内容:

rails new depot

我们会看到许多输出内容。当输出内容完毕后我们会看到一个新的文件夹—— depot 已经被成功创建。我们将在这个文件夹中进行接下来的工作。

Windows 系统用户需要用 dir /w 替换 ls -p 命令。

创建数据库

对于这个应用,我们将使用开源数据库 SQLite(如果你需要跟随本书编码你也需要使用这个数据库)。我们会使用 SQLite 3。

SQLite 3 是 Rails 开发中的默认数据库,并会跟随 Rails 一同被安装。所以关于 SQLite 3 的安装步骤也不需要,也没有指定的用户名和密码需要处理。此时,你应该也体会到按照步骤进行的好处了(或者,如同 Rails 相关人员说的那样,约定大于配置)。

如果使用 SQLite 3 外的数据库对于你来说是重要的,你需要创建数据库的命令以及分配不同的权限。你也会在《Getting Started Rails Guide》中发现一些有用的线索。

生成脚手架

在图例 Initial guess at application data 中,我们描绘了 products 表的内容。现在让我们将其实现。我们需要创建一个数据库表和一个 Rails 的 model,这个 model 可以让应用操作相应的表,还有一些 views 构成用户入口,以及一个 controller 编排应用。

所以,让我们创建 model,views,controller 和迁移 product 表。在 Rails 中,你可以通过一个命令让 Rails 根据提供的 model 生成脚手架。需要注意的是,下面的命令行中我们使用的是单数形式—— Product。在 Rails 中,model 会自动匹配自身类名复数形式的表。在我们的示例中,我们将 model 命名为 Product,所以 Rails 会将它与名为 products 的表关联。(那它如何找到到相应表呢?开发环境下,config/database.yml 文件会告诉 Rails 在哪里可以找到表。对于 SQLite 3 用户来说,它就是 db 文件夹中的一个文件。)

这里要注意,由于命令比较长为了适应书籍页面我们做了排版调整。我们将命令调整为多行形式,会通过在每行的最后一个字符放置反斜杠标识进行了换行,但最后一行末尾不会出现反斜杠标识,这样你就可以在命令窗口输入更多信息。Windows 系统用户需要用插入符(^)替代反斜杠。

rails generate scaffold Product \
title:string description:text image_url:string price:decimal

生成器会创建一堆文件。令我们感兴趣的还是迁移文件,类似 20121130000001_create_products.rb 这样名字的文件。

一个迁移表示我们想要对数据做出的一次修改,在独立数据库中表现为一个源文件。这些修改可能是更新数据库 schema 和表中的数据。我们通过应用这些迁移修改数据库,我们也可以将其撤销回滚数据库。我们会在 22 章完整地介绍迁移。现在,我们直接使用就行。

迁移文件有基于 UTC 时间戳的前缀(20121130000001),一个名称(create_products)和文件扩展名(.rb,因为是 Ruby 代码)。

你可以看到时间戳前缀是变化的。实际上,在本书中使用的时间戳纯粹是虚构的。真实情况下,你的时间戳是不连续的,因为他们代表着迁移文件被创建的时间。

进行迁移

尽管我们已经告诉了 Rails 每个属性的基本数据类型,但还是让我们来修正一下价格的定义,价格应该是 8 位的数字,并且其中 2 位数字是小数位。

class CreateProducts< ActiveRecord::Migration
  def change
    create_table :products do |t|
      t.string :title
      t.text :description
      t.string :image_url
      t.decimal :price, precision: 8, scale: 2

      t.timestamps
    end
  end
end

现在我们完成了相应的修改,我们要将迁移应用到开发环境数据库中。我们通过 rake 命令完成。Rake 就像随叫随到的助手,你可以让它做任务,这个任务就已经被它完成。在当前示例中,我们要让 Rake 应用尚未应用到数据库的迁移。

rake db:migrate

就是这样。Rake 会查找所有还没有应用过的迁移并应用它们。在上面示例中,product 表是由 development 部分的 database.yml 文件定义添加至数据库中。

所有的准备工作已经完成。我们已经将 Depot 作为 Rails 项目建立起来了。我们创建了开发数据库,并配置了应用与数据库之间的连接。我们创建了 product controller 和 Product model,并且通过迁移创建了对应的 products 表。还有一些 view 也已经创建。是时候纵览一下所有的操作了。

查看商品列表

通过这三个命令我们已经创建了应用以及它的数据库(或者说是已存在的数据库中的一张表,如果你不是使用的 SQLite 3 数据库)。

在我们担忧这节之后还会发生什么事情之前,让我们试试崭新的应用。

首先,我们通过 Rails 启动本地服务器。

rails server

就如同启动测试程序时一样,这个命令可以在本地启动一个 web 服务器,端口为 3000。如果你运行服务器时报错为 Address already in use,意味着你已经在机器上运行了一个 Rails 服务。如果你紧跟本书的步骤在第 4 章时应该正常查看到「Hello, World!」。在控制台可以通过 Ctrl-C 将服务停止。如果你在 Windows 上运行,你还会看见显示 Terminate batch job (Y/N)?。如果如此,回复 y。

让我们连接上应用。要记住,我们在浏览器中填写的 URL 包含端口号,和 controller 的名字小写。

这其实显得有些无聊。界面显示了空的商品列表内容。让我们添加一些信息。点击 New Product 链接,添加一个图例 Form for add new products 所示的表单。

这些表单都是简单的 HTML 模板,就像在 2.2 节创建的一样。实际上,我们也可以修改它。让我们把描述栏位修改为多行内容。

我们也会在第 8 章讨论更多的信息。但就目前来说,我们尝试调整一个栏位,并将内容填满其中即可。

点击创建按钮,你应该可以新的商品已经被成功创建。如果你现在点击 Back 链接,你应该在列表中看见新的商品。

或许界面还不那么完美,但它已经可以正常使用了,并且我们可以向客户展示并获得他们的认可。她可以在其他的链接上操作(展示详情,编辑已经存在的商品等等)。我们向她解释,这只是第一步,我们也知道这个应用还比较粗糙,不过我们希望能尽早获得她的反馈。(在任何书本中,这都只仅仅需要 4 个命令)。

现在,你通过 4 个命令就已经完成了许多事情。在我们继续之前,让我们再试试另一个命令。

rake test

包含在命令输出结果中的应该有两行,分别是「0 failures, 0 errors」。这是对 model 和 controller 的测试,由 Rails 与脚手架一起生成。此时测试还比较小,但我们已经能简单了解到功能已经存在,同时也可以让我们更加自信。当你跟随流程完成了第 II 部分的章节时,我们是鼓励你频繁地运行这些命令的,可以更好地指出及追踪错误。我们会在 7.2 节更加详细讨论相关内容。

要注意的是,如果你使用的是 SQLite 3 以外的数据库,这步可能会失败。你需要检查一下 database.yml 文件,并且看看 23.1 节讲的注意事项。

迭代 A2:美化列表

我们的客户还有一个请求(客户总是有更多的要求,难道不是吗?)商品的列表比较简陋。我们可以把它美化一下吗?而且,我们可以通过图片 URL 显示图片吗?

我们现在面临着困难。作为开发者,我们面对这种请求时都会先深吸一口气,然后摇摇头,并细语一句「你在想什么呢?」同时,我们又想卖弄一番。结果,事实上我们愉快地通过 Rails 完成了这种修改,同时也唤醒了我们值得依赖的编辑器。

在我们进行太多步骤前,如果我们有一个一致的测试数据集辅助工作再好不过。我们可以使用脚手架生成器接口和来自浏览器的类型数据。不过,如果我们这样做的话,以后的开发人员也会在我们的代码基础上这样做。并且,如果我们正在实现一个项目的一部分的话,团队中其他成员也必须确认我们的数据。如果我们可以通过可控的方式将需要的数据加载至表中是最好的。我们肯定可以做到,Rails 提供了导入种子数据的功能。

现在可以开始了,我们简单地修改一下 db 文件夹中的 seeds.rb 文件。

我们接着向 products 表添加要填充的代码。通过调用 model Product 的 create!() 方法实现。接下来是文件中的一段摘录。相比于亲手编写这个文件,或许你更加想从线上下载一份基本代码。

如果你已经进行到此处,就拷贝文件到你应用的 app/assets/images 路径下。要提醒你的是,seeds.rb 脚本在加载新数据之前会将 products 表中已经存在的数据删除。如果你已经花费了几个小时通过应用录入了自己的数据,你可能并不想运行它。

Product.delete_all
# . . .
Product.create!(title: 'CoffeeScript',
  description: 
    %{<p>
        CoffeeScript is JavaScript done right. It provides all of JavaScript's
    functionality wrapped in a cleaner, more succinct syntax. In the first
    book on this exciting new language, CoffeeScript guru Trevor Burnham
    shows you how to hold onto all the power and flexibility of JavaScript
    while writing clearer, cleaner, and safer code.
      </p>},
  image_url:   'cs.jpg',    
  price: 36.00)
# . . .

(注意代码中用 %{...} 标识。这是双引号的另一种语法,方便开发者编写较长的字符串。还要注意的是,因为使用了 Rails 的 create!() 方法,如果记录不能正常插入会由于验证错误而抛出异常。)

运行以下命令,使用测试数据填充 products 表吧。

rake db:seed

现在就让我们整理一下商品列表吧。处理界面我们需要两步,首先定义一组样式规则,然后将这些规则通过页面中定义的 HTML 的 class 属性关联。

我们需要放置样式定义的地方。如果我们继续探索 Rails,我们很容易发现此处,之前运行的 generate scaffold 命令已经加载了所有必要的基础工作。现在,我们可以把空的 products.css.scss 样式表放入 app/assets/stylesheets 路径下。

// Place all the styles related to the Products controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

.products {
  table {
    border-collapse: collapse;
  }

  table tr td {
    padding: 5px;
    vertical-align: top;
  }

  .list_image {
    width:  60px;
    height: 70px;
  }

  .list_description {
    width: 60%;

    dl {
      margin: 0;
    }

    dt {
      color:        #244;
      font-weight:  bold;
      font-size:    larger;
    }

    dd {
      margin: 0;
    }
  }

  .list_actions {
    font-size:    x-small;
    text-align:   right;
    padding-left: 1em;
  }

  .list_line_even {
    background:   #e0f8f8;
  }

  .list_line_odd {
    background:   #f8b0f8;
  }
}

如果你打算下载这份文件,要确认好文件更新的时间。如果时间戳不是最新的,Rails 在服务重启前不会更新变动。你也可以通过自己喜欢的编辑器保存此文件以修改它的时间戳。在 Mac OS X 和 Linux 中,你可以通过 touch 命令完成。

仔细观察样式表,你会看见 CSS 规则进行了内嵌,dl 的规则是定义在 .list_description 中的,并且它们都定义在 products 中。这样可以减少重复,因此也更易阅读,编写,理解和维护。

至此,你已经了解了以 erb 结尾的内嵌 Ruby 表达式和声明的文件。但如果你注意到这个文件是以 scss 结尾的话,你可能已经在猜测它是根据 Sassy Css 预处理后再作为 css 进行服务的。没错,就如同你猜测的一样。

再声明一下,就像 ERB 一样,SCSS 也不会干扰编写正确的 CSS。SCSS 只是提供了扩展语法,使你更加容易编写和维护样式表。所有的 SCSS 都会被翻译为 CSS 让浏览器能够解析。你也可以通过《Pragmatic Guide to Sass》探索相应的细节。

最后,我们要定义一下样式表中用到的 products 类。如果你仔细查看 .html.erb 文件,你就会发现它并没有引用任何样式表。你也不会找到通常声明引用的 HTML <head> 标签。Rails 为了保持文件分离,于是就为整个应用创建了标准的界面环境。这个文件叫做 application.html.erb,它是一个 Rails 的布局文件,就在 layouts 路径下。

<!DOCTYPE html>
<html>
<head>
  <title>Depot</title>
  <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
  <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
  <%= csrf_meta_tags %>
</head>
<body class='<%= controller.controller_name %>'>

<%= yield %>

</body>
</html>

因为 Rails 一次性将所有样式表都加载了,我们只需要约定页面与 controller 关联的详细规则即可。使用 controller_name 作为类名就能轻松实现,并且我们也是这样做的。

现在我们已经可以使用样式表了,我们将会使用基于表格的模板,编辑 app/views/products 路径下的 index.html.erb 文件并替换脚手架生成器 view。

<h1>Listing Products</h1>

<table>
  <tbody>
    <% @products.each do |product| %>
      <tr class="<%= cycle('list_line_even', 'list_line_odd') %>">
        <td><%= image_tag(product.image_url, class: 'list_image') %></td>
        <td class="list_description">
          <dl>
            <dt><%= product.title %></dt>
            <dd><%= truncate(strip_tags(product.description), length: 80) %></dd>
          </dl>
        </td>
        <td class="list_actions">
          <%= link_to 'Show', product %>
          <%= link_to 'Edit', edit_product_path(product) %>
          <%= link_to 'Destroy', product, method: :delete, data: { confirm: 'Are you sure?' } %>
        </td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Product', new_product_path %>

上述模板中也使用了一些 Rails 的特性。

  • 列表中的行可以变更背景颜色。通过调用 Rails 的辅助方法 cycle() 设置的每行的样式为 list_line_evenlist_line_odd,每行可以自动连续切换样式。

  • truncate() 辅助方法用来限制显示描述的前 80 个字符。调用 truncate() 之前我们还调用了 strip_tags() 方法,用以去除描述中的 HTML 标签。

  • 找到 link_to 'Destroy' 这行。找到 data: {confirm: 'Are you sure?'} 参数。如果你点击这个链接,Rails 会让浏览器提示对话框显示确认信息,然后再删除商品。(你也可以看看 75 页侧边栏关于这个操作的相关信息)。

我们加载了一些测试数据到数据库,我们重写了 index.html.erb 文件用以展示商品列表,我们也填充了 products.css.scss 样式表,并且样式表也通过 application.html.erb 布局文件加载至了我们的页面。现在,让我们再次通过浏览器浏览 http://localhost:3000/products,商品列表的显示应该如下图所示。

a-slightly-prettier-view.png

此时,我们可以自豪地向客户展示商品列表,并且她也很乐意。现在我们是时间创建店面了。

完成的事

这章中我们为商店应用做了些准备工作。

  • 我们创建了开发环境数据库

  • 我们通过迁移创建和修改了开发数据库的 shcema

  • 我们创建了 products 表,并通过脚手架生成器编写了应用对表进行维护

  • 为了显示商品列表,我们还修改了整个应用级的布局,并渲染指定 controller 的视图

我们所做的并不需要过多的努力,事情自然就走上正轨并高速运转。数据库对应用是至关重要的,但也不要过分担心,实际上多数情况下我们都会稍晚一些才抉择数据库,一开始我们使用 Rails 默认提供的数据库就好。

这个阶段获取正确的 model 反而是更加重要的。就像我们之前看到的,简单的数据抉择并不总是能完全捕获 model 所有的实质属性,在这个小应用中我们要在下一步处理这个问题。

自习天地

有些知识需要你自己尝试:

  • 如果你觉得有些迷乱,你可以回滚迁移。输入如下命令:
rake db:rollback

你的 schema 将会立即回撤,products 表也将消逝。再次运行 rake db:migrate 又将再次创建它。你也会重新加载种子数据。更多信息可以查看 22 章。

  • 我们会发现版本控制是一种很好保存你当前工作成果的方式。推荐你使用 Git,你只需要极少的配置就可以,仅仅提供名字和邮箱即可。

git config --global --add user.name "Sam Ruby"

git config --global --add user.email rubys@interwingly.net

你可以通过下面的命令验证配置:

git config --global --list

Rails 也提供了 .gitignore 文件,这个文件可以让 Git 知道哪些文件不需要进行版本控制。

# See http://help.github.com/ignore-files/ for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
#   git config --global core.excludesfile '~/.gitignore_global'

# Ignore bundler config.
/.bundle

# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal

# Ignore all logfiles and tempfiles.
/log/*.log
/tmp

要注意文件名是以点开头的,基于 Unix 的操作系统默认情况下不会显示这个文件。可以通过 ls -a 查看到。

此时你已经完成了配置。只需要初始化仓库,添加文件并提交就可以了。

git init

git add .

git commit -m "Depot Scaffold"

看起来并不那么令人激动,但这确实意味着你可以更加自由地试验了。即使重写或者删除了一个文件都不需要在意,你永远可以通过一个命令将其还原。

git checkout .