改进 Ruby on Rails 的脚手架
在 2007 年 5 月号的 Linux Journal 中,我描述了我最初涉足 Ruby 编程世界的经历,将 Ruby 与 CGI 和 AJAX 结合起来,制作了一个基于 Web 的以太网分析器。虽然我乐于将那个特定的解决方案组合在一起,但我真正了解 Ruby 的原因是让我能够使用 Ruby on Rails,这个备受推崇的 Web 应用程序框架 (WAF)。
我研究了 Perl 和 Python 领域中可用的许多 WAF。早在 2005 年 3 月号的 Linux Journal 中,我就描述了 Maypole,Perl 的第一个 WAF 之一。从那时起,我探索了 Catalyst(Maypole 的一个分支)、Jifty 和 Gantry。尽管我广泛使用 Perl 并承认对其喜爱,但 Rails 引起了我的注意,我只是想满足一下这种渴望。
对于任何希望以任何有意义的方式使用 Rails 的人,我有一个建议:首先了解 Ruby。最初,由于我对 Ruby 的不熟悉,我很难理解 Rails 在做什么。当我改变方法并搁置 Rails 以便正确学习 Ruby 时,我对 Rails 的第二次尝试更有意义了。它也更有效率。
毫无疑问,Rails 是一个伟大的 WAF,值得所有不断涌来的赞誉。然而,当你第一次开始使用 Rails 时,框架生成的默认网页一点也不令人印象深刻。事实上,它们非常丑陋,这可能会让人有点失望,特别是如果所有你需要的只是一个快速的 Web 应用程序模型。诚然,这些默认布局旨在被更美观的东西取代:专业设计的 CSS 网页。而且,公平地说,Rails 的人们确实不遗余力地强调这一事实。然而,如果你时间紧迫,停下来设计一些用户友好的网页是一种拖累。像我这样匆忙的类型需要漂亮、现代的 CSS 样式来快速而简陋地完成任务。这就是 ActiveScaffold 的用武之地。
ActiveScaffold 构建在标准 Rails 环境之上,是一个插件,用该项目网站的话说,“为您提供丰富的动态创建的优点”。对于 Rails 开发人员来说,这种优点意味着 ActiveScaffold 提供了一组漂亮的 CSS 页面和方法,用于与您的数据库表进行交互。ActiveScaffold 最初通过对现有 Rails 应用程序进行一个微不足道的代码更改来实现这一点,这有点引人注目。
在本文中,我重新开发了我在 2005 年使用 Maypole 创建的基于 Web 的足球俱乐部数据库应用程序,这次使用 Rails 和 ActiveScaffold 作为开发平台。为了给过程增加一点变化,我使用了 PostgreSQL 作为我的数据库,因为在阅读了 Reuven Lerner 撰写的优秀系列文章,比较 PostgreSQL 和 MySQL 后,我决定尝试一下 PostgreSQL(参见 2007 年 4 月、5 月和 6 月号的 LJ)。
如果您没有安装 PostgreSQL(并且您正在使用 Ubuntu 或其他基于 Debian 的发行版),安装非常简单
sudo apt-get install postgresql
如果您的 GNU/Linux 发行版不支持 apt,请使用您的软件包管理器下载并安装 PostgreSQL。在 PostgreSQL 运行的情况下,成为系统上的 postgres 用户并创建一个新的 soccer_manager 用户
sudo su - postgres createuser -U postgres soccer_manager
请务必对 createuser 程序提出的每个问题回答 n(否),因为 soccer_manager 需要被限制为仅在 soccer 数据库中工作(我们稍后会创建它)。选择 n 是为了在这个阶段有意限制授予的权限。接下来,创建一个名为 soccer_development 的数据库
createdb -U postgres soccer_development
创建数据库和用户后,进入 PostgreSQL 交互式终端 (psql),并为 soccer_manager 提供密码以及使用 soccer_development 数据库的用户权限
psql postgres=# alter user soccer_manager with postgres-# password 'soccer_manager_password'; postgres=# grant all privileges on database postgres-# soccer_development to soccer_manager; postgres=# \q
请注意 quit 命令 \q 的使用,它退出 psql。至此,我们直接使用 PostgreSQL 的工作就完成了。我们可以以 soccer_manager 用户身份登录 psql,并开始使用标准 SQL 在数据库中创建表,但我们将让 Rails 为我们处理这些细节(稍后会详细介绍)。
我假设您的 GNU/Linux 系统上已经安装了 Ruby。如果不是这种情况,请从 Ruby 网站(参见资源)从源代码安装它,或者从您的发行版的软件包管理器安装 Ruby 软件包(Ubuntu 上的 ruby-full 软件包应包含您需要的所有内容)。要安装和使用 Rails,需要将 RubyGems 软件包管理器安装到您的 Ruby 环境中。如果您的发行版的软件包管理器中没有 RubyGems,请访问 RubyForge 上的 RubyGems 下载页面(参见资源),选择最适合您环境的 RubyGems 版本,然后下载关联的文件。安装非常简单(请注意,您正在使用的版本可能与此处显示的版本不同)
tar zxvf rubygems-1.3.0.tgz cd rubygems-1.3.0 sudo ruby setup.rb
如果您正在使用 Ubuntu(或其表亲之一),请使用 apt 安装 RubyGems 软件包
sudo apt-get install rubygems
安装 RubyGems 后,您现在可以安装 Rails
sudo gem install rails
当提示时,请务必安装所有建议的依赖项。此步骤需要一段时间才能完成,但这证明了 Rails 的简单性,一旦此命令完成,您就可以开始使用了。我使用基于 Perl 的 WAF 遇到的一个问题是安装可能是一场噩梦,特别是当不同版本的各种 CPAN 模块抛出兼容性和依赖性错误时。值得庆幸的是,Rails 没有这种令人沮丧的问题!
我在 Ubuntu 上使用 Rails 时确实遇到了一个小问题,这与 rails 命令安装在 /usr/bin/ 中有关,因为它不在那里。Ubuntu 希望您使用 apt-get 安装 Rails,但由于我想要最新最好的 Rails,所以我选择了 RubyGems 安装方法。要解决这个小问题,请创建一个指向 rails 命令的链接,如下所示
sudo ln -s /var/lib/gems/1.8/bin/rails /usr/bin/rails
由于我们使用 PostgreSQL 作为我们的数据库,我们需要下载并安装 PostgreSQL Ruby gem。这也很简单
sudo gem install postgres
如果这导致错误,请确保安装了 Ruby 的开发库(在 Ubuntu 上称为 ruby1.8-dev),以及 PostgreSQL 的开发库(称为 libpq-dev)。如果仍然出现编译时错误(例如,由于找不到头文件),请改用此命令(应在一行中输入)
POSTGRES_INCLUDE=/usr/include/postgresql \ sudo gem install postgres
此时,Ruby、PostgreSQL、PostgreSQL gem 和 Rails 已安装并准备就绪。
在您选择的目录中,键入以下命令
rails soccer_club --database=postgresql
此命令创建一个名为 soccer_club 的新 Rails 应用程序,从而产生 Rails 的长列表消息,并创建一个名为 soccer_club 的新目录。
让我们向我们的应用程序添加一些数据库表。首先更改为新创建的 soccer_club 目录。
我们可以使用一系列 SQL CREATE TABLE 语句创建必要的表,耐心地将它们输入到 PostgreSQL 的 psql 命令行工具中。但是,Rails 提供了一种称为数据库迁移的技术,允许您在不直接使用 SQL 的情况下操作数据库表。迁移在更高的级别上运行,使 Web 开发人员免受底层 SQL 方言的影响。在我们创建迁移之前,让我们告诉我们的 Rails 应用程序要使用哪个数据库,并提供用户名/密码组合。
编辑与您的 Rails 应用程序关联的 config/database.yml 文件,并将 development 部分更改为如下所示(请注意,Rails 建议了一些默认值,但对于我们的应用程序,这些值需要更改)
development: adapter: postgresql encoding: unicode database: soccer_development username: soccer_manager password: soccer_manager_password
在我的 Ubuntu 系统上,PostgreSQL 配置为期望来自用户名等于当前登录用户的用户 ID 的连接。这称为 IDENT 身份验证。这意味着要使用用户 ID soccer_manager 访问 soccer_development 数据库,我们需要以 soccer_manager 身份登录到 GNU/Linux。这不是我们想要的(也不是 Rails 想要的),所以我们需要对相应的 PostgreSQL 配置文件 (/etc/postgresql/8.3/main/pg_hba.conf) 的底部进行快速更改,注释掉ident sameuser行并添加密码行,如下所示
# "local" is for Unix domain socket connections only local all all password # local all postgres ident sameuser
在编辑之后,有必要停止/启动 PostgreSQL 以应用更改
sudo /etc/init.d/postgresql-8.3 stop sudo /etc/init.d/postgresql-8.3 start
要检查 Rails 与数据库的连接是否一切正常,请在您的 Rails 应用程序的顶层目录中键入以下内容
rake db:migrate
会产生单行输出(在我的系统上的 /home/barryp/rails/soccer_club 中),这是 Rails 告诉我们数据库连接一切正常的表示方式。任何其他消息都可能表明存在错误。如果问题不是很清楚(当然,假设您遇到了问题),请尝试附加--trace到上述 rake 命令的末尾。
Rails 可以帮助创建我们的数据库表,我们需要三个表:一个用于保存我们足球运动员的信息,另一个用于保存球队数据,另一个用于维护医疗状况。为了简单起见,让我们假设每个球员都属于一个球队,并且可以有一种医疗状况(或根本没有)。让我们告诉 Rails 关于表的信息
ruby script/generate model player ruby script/generate model squad ruby script/generate model condition
Rails 中的模型让我们从我们的 Web 应用程序与我们的数据对话。上述每个命令都会产生八行输出,同时 Rails 会执行其操作。请注意,每个命令都包含在 db/migrate 目录中生成的文件。这些是我们的数据库迁移。此时,事情变得不那么以 SQL 为中心,而更像 Rails,因为 Rails 提供了一种独立于数据库的方式来定义我们的表。要查看实际效果,请编辑 db/migrate/xxxxxxxxx_create_players.rb 文件(其中 xxxxxxxxx 是 Rails 生成的唯一日期/时间字符串),将 self.up 方法更改为如下所示
def self.up create_table :players do |t| t.integer :squad_id, :condition_id t.string :name, :address, :contact_tel_no t.date :date_of_birth t.timestamps end end
这是 Rails 高级方式,告诉您的数据库创建表。表中的每一列都有唯一的名称和数据类型。请注意,除了您可能期望每个球员拥有的列(姓名、地址等)之外,我们还添加了两个整数列,它们将链接到球队和状况表。使用迁移的酷之处在于,无论您使用哪个数据库,Rails 都会根据需要生成正确的数据库特定 SQL 语句。让我们定义另外两个表。编辑 db/migrate/xxxxxxxxxx_create_squads.rb,将 self.up 方法更改如下
def self.up create_table :squads do |t| t.string :name t.timestamps end end
最后,更改 db/migrate/xxxxxxxxxx_create_conditions.rb,使其 self.up 方法如下所示
def self.up create_table :conditions do |t| t.string :name t.timestamps end end
现在到了有趣的部分,在命令提示符下键入以下内容
rake db:migrate
屏幕上应滚动显示类似于以下的输出
(in /home/barryp/rails/soccer_club) == CreatePlayers: migrating ===================== -- create_table(:players) -> 0.1916s == CreatePlayers: migrated (0.1918s) ============ == CreateConditions: migrating ================== -- create_table(:conditions) -> 0.0183s == CreateConditions: migrated (0.0185s) ========= == CreateSquads: migrating ====================== -- create_table(:squads) -> 0.0309s == CreateSquads: migrated (0.0311s) =============
发生的事情是 Rails 已连接到后端数据库并创建了三个必需的表。请注意,没有程序员编写的 SQL 代码!Rails 处理所有繁琐的 SQL 细节。对于那些不相信我的读者,请以 soccer_manager 身份登录 PostgreSQL,并沉浸在 Rails 为您创建的表架构的荣耀中。
此时,通常会使用 Rails 生成一些脚手架代码,然后查找 CSS 参考来美化整个内容。这是可行的,但这需要时间。现在,让我们使用 Rails 使用以下三个命令生成空控制器
ruby script/generate controller player ruby script/generate controller squad ruby script/generate controller condition
这些命令中的每一个都会产生七行输出。请注意,在 app/controllers 目录中生成了一个 Ruby 文件。这些是源代码文件,将包含我们要添加到 Rails 应用程序的任何业务逻辑。我们将在稍后这样做。要完成默认的 Rails 设置,我们需要指定我们的表关系。编辑 app/models/player.rb 使其如下所示
class Player < ActiveRecord::Base belongs_to :condition belongs_to :squad end
ActiveScaffold 由一群热心的 Rubyist 编写和维护,他们居住在 activescaffold.com/team。ActiveScaffold 是一个 Rails 插件,因此,它被安装到现有的 Rails 项目中,所以让我们首先这样做。从您的 Rails 应用程序的顶层目录,键入以下内容(应在一行中输入)
git clone git://github.com/activescaffold/active_scaffold.git \ vendor/plugins/active_scaffold && \ rm -rf vendor/plugins/active_scaffold/.git
此命令获取 ActiveScaffold 并将其安装到您的 Rails 应用程序中。当此过程完成时,在您的 Rails 应用程序的 vendor/plugins/ 目录中创建了一个名为 activescaffold 的新目录。为了使插件发挥其魔力,我们需要创建一个将在我们的整个 Rails 应用程序中使用的应用程序级布局。这是一个基本布局,我们需要在 app/views/layouts 目录中创建它,它被称为 application.rhtml
<html> <head> <title>Soccer Club Database System</title> <%= javascript_include_tag :defaults %> <%= active_scaffold_includes %> </head> <body> <%= yield %> </body> </html>
这是一个简单的,基本上是空的 HTML 页面。请注意 <%= 和 %> 标签中包含的代码。这些标签允许我们从 HTML 模板中执行 Ruby 代码。第一组这样的标签向我们的页面添加一组 JavaScript 例程;第二组引入 ActiveScaffold 的优点,第三组执行 Ruby yield 方法。在我们应用程序中创建的任何布局(无论是我们手动创建的还是由 Rails 或 ActiveScaffold 动态创建的)都将包装在 application.rhtml 布局中,其内容根据需要替换 yield 的调用。创建默认布局后,我们需要编辑我们现有的每个控制器以开启 ActiveScaffold。这是编辑后 app/controllers/player_controller.rb 文件应显示的样子
class PlayerController < ApplicationController active_scaffold :player end
向 app/controllers/squad_controller.rb 和 app/controllers/condition_controller.rb 文件添加类似的代码行,然后启动您的 Rails 应用程序
ruby script/server
启动您的浏览器并加载 http://localhost:3000/player 页面。看看图 1,它显示了默认的 ActiveScaffold 球员列表——看起来很棒。请注意,ActiveScaffold 已经发现了三个表之间的链接并提取了适当的数据值。另请注意,我已向我的 Web 应用程序添加了一些示例数据。不幸的是,列的顺序有点令人不满意,当我们查看默认的 ActiveScaffold 球员表单时,这一点尤其明显,如图 2 所示。此表单按字母顺序显示表列,这不是我们想要的。此外,提供对球队和医疗状况数据访问的子表单很酷,但我们想要的是应用程序的简单下拉列表。值得庆幸的是,调整 ActiveScaffold 的默认行为并不困难,我们将在稍后看到。
另一个问题(如果您一直在关注,您可能已经注意到)是与 date_of_birth 值关联的日期范围非常受限,使用 1997 年作为最早的开始年份。由于我们所有的足球运动员都出生于 1990 年代初期,我们需要某种方法来调整任何输入日期的开始年份。ActiveScaffold(以及 Rails)也可以在这里提供帮助。
让我们首先修复列的顺序。更改 app/controllers/player_controller.rb 文件使其如下所示
class PlayerController < ApplicationController active_scaffold :player do |c| c.columns = [:name, :squad, :address, :date_of_birth, :contact_tel_no, :condition ] c.columns[:squad].ui_type = :select c.columns[:condition].ui_type = :select end end
在此代码中,我们为 activescaffold 方法提供了一个配置代码块,我们在其中指定列的顺序,此外还将与球队和状况数据关联的 ui_type 设置为 :select。这解决了我们的排序问题,并将球队和状况选择机制设置为标准下拉列表。
解决日期问题需要为球员表创建 Rails 助手方法。编辑 app/helpers/player_helper.rb 文件,并添加以下代码
module PlayerHelper def date_of_birth_form_column(record, input_name) date_select :record, :date_of_birth, :name => input_name, :start_year => 1990 end end
名称奇怪的 date_of_birth_form_column 助手方法调用 ActiveScaffold 提供的 date_select 方法,该方法允许我们调整与我们的 date_of_birth 数据关联的最早开始日期。完成这些更改后,重新启动 Rails 应用程序并重新加载浏览器窗口。图 3 显示了改进后的球员列表,图 4 显示了我们球员数据录入表单的最终版本。我相信您会同意,这两个屏幕看起来都很棒。花时间玩玩 ActiveScaffold 免费提供的附加功能,包括每个列标题上的排序链接。
要了解更多关于 Rails 的信息,我强烈推荐 The Pragmatic Programmers 编写的 Agile Web Development with Rails(现在是第二版,第三版即将推出),以及 O'Reilly Media 的 Rails Cookbook。要了解更多关于 ActiveScaffold 的信息,请查看 ActiveScaffold 网站上提供的编写良好的文档和代码示例(参见资源)。正如我希望本文所展示的那样,将一个丑陋的默认 Rails 应用程序变成您可能想要炫耀的东西并不需要太多努力!
资源
Web 上的 Ruby:www.ruby-lang.org
RubyGems RubyForge 存储库:rubyforge.org/projects/rubygems
ActiveScaffold 网站:activescaffold.com
Rails 插件存储库:agilewebdevelopment.com/plugins
Paul Barry 的“Ajax 增强的基于 Web 的以太网分析器”(LJ,2007 年 5 月):www.linuxjournal.com/article/9614
Paul Barry 的“18 行代码的数据库驱动的 Web 应用程序”(LJ,2005 年 3 月):www.linuxjournal.com/article/7937
Reuven Lerner 撰写的优秀系列文章,比较 PostgreSQL 和 MySQL(LJ 2007 年 4 月、5 月和 6 月号):www.linuxjournal.com/article/9571, www.linuxjournal.com/article/9618 和 www.linuxjournal.com/article/9649
Paul Barry (paul.barry@itcarlow.ie) 在爱尔兰卡洛理工学院任教。在此网站上了解更多关于他所做的事情:glasnost.itcarlow.ie/~barryp。