Francis's Octopress Blog

A blogging framework for hackers.

蓄势待发!Rails 3 介绍

蓄势待发!Rails 3 介绍

在过去的两年中,Ruby on Rails 应用程序框架已经具备了一个由托管和服务提供商组成的一体式行业、一套功能广泛且令人印象深刻的开发工具,还有各种在 Ruby 用语里称为 gemsplug-ins 的补充库 — 可增强软件的功能。例如 Engine Yard 和 Heroku 就是两个提供虚拟的、方便的 Rails 软件托管的公司;Oink 和 Bullet 分别用来显示内存使用和性能;Clearance 和 Sunspot 则用来提供现成的身份验证和快速的索引式搜索。

常用缩略语

  • CRUD:创建、读取、更新、删除
  • HTML:超文本标记语言
  • REST:具象状态传输
  • SQL:结构化查询语言

自 2007 年以来,Rails 社区也已不断壮大。全球有众多的 Rails 开发人员充满活力、团结互助,并且渴望不断地改进该软件。可以毫不夸张地说,Rails 的改进应归功于社区,因为是程序员之间不断的相互超越使得构建出的软件越来越完善。如此不断地循环,各个功能才得以从初期阶段快速发展,逐渐变的实用、功 能不断强大并完善,最终成为不可或缺的工具。在很多情况下,社区认为最基本的 gems 和 plug-ins 都会保留到 Rails 核心中。Rails 的 named scopes(一个查询快捷方式),可实现与 nested forms 相同的功能。这是一个新增的功能,可取代以前的尝试,并在相同的 HTML 表单中创建和编辑多个模型。实际上,对于 Rails 开发人员来说最困难的任务或许就是跟上变化的节奏。(幸运的是,目前有一些 Ruby 和 Ruby on Rails 的每周播客,这些播客会组织并呈现最新趋势以及分享最佳实践。)

Rails 下一个主要的发布是 Rails 版本 3,该版本继续保持工具包的快速改进。与以往相同,该软件仍然非常 “固执”,即仍然偏向于约定优于配置。Rails 的核心组件始终存在,即 RESTful 路径、关系、验证、模板和数据库抽象化。不过,这些组件的许多内部内容已经被重写或者进行了改进。最明显的并且很大程度上借用了 Merb 的理念的就是很多 Rails 的基本功能不再密切结合。例如,以前只对 Rails 应用程序可用的数据验证便捷功能现在成为独立的组件,并且可以包含在 Vanilla Ruby 代码中。如呈现部件和模板等控制器功能,现在也是相互独立的,并且可以嵌入到任一库中。

在这篇文章中,您将会了解 Rails 3 及其众多变化和附加功能,以及如何从头创建一个新的 Rails 3 应用程序。截至 2010 年 2 月中旬,Rails 3 还只是预发布的 Beta 版,核心小组正在收集补丁、反馈和文档,以准备在夏季前发布正式版本。不过当前的 Rails 3 版本足够用于构建应用程序以及了解诸多新增功能。

大改动、小变化

Rails 3 中变动的数量太多,无法在此全部列出。要阅读带有补充资料的完整列表,请查询 Rails 3 发布通知(请参阅 参考资料 中的链接)。此处,仅介绍一些很可能会影响到开发人员的一些变动:

  • 一条命令可以控制所有事件。有了 Rails 3,无需在每一个应用程序中使用整套脚本(script/server、script/generate 以及其他),使用一条命令即可取代其功能,该命令恰如其分地被命名为 rails。例如,在以前需要键入 ./script/console 的地方,您现在只需键入 rails consolerails 命令还可以像以前一样生成新的应用程序。其运行方式依据其是否在现有的 Rails 应用程序中启动而有所不同。
  • 可为依赖性提供具体的解决方案。协调和解决 gem 依赖性是个棘手的问题。与可用 gems 的集合一样,gem 的修订也是随系统的不同而有所变化。因为具有这样的多样化,所以很难广泛地部署或共享一个 Rails 应用程序。Rails 3 引入了 Bundler,这是一个专门用于管理依赖性的实用程序(因此无需再使用 config.gem)。您可以在应用程序根目录内一个名为 Gemfile 的目录中声明依赖性。Bundler 将下载和存储所有指定的 gem。您甚至可以在应用程序中 “打包” gem ,以便阻止从外部存储库下载。
  • 不带查询语句的查询功能。一直以来,Rails 已经可以充分运用特定于领域的语言 (DSL) — 考虑一下 has_onevalidates_numericality_of — 有一个明显的例外:数据库查询。可以确定地说,Rails 的动态搜寻器便捷、易用,但混合使用选项哈希值如 :conditions:order:limit 非常常见,因为都是 find_by_sql 语句。Rails 3 合并了 relational algebra,这是专门设计用于表示查询的 DSL。基本命令包括 project(用于选择列)、where(用于表示条件)、join(用于指定关系)、takeskip(分别用于限定和抵消),以及 group(用于聚集)等其他属性。
  • 用于模糊样板代码的控制器。Rails 控制器的核心操作 —newcreateeditupdate — 通常不变,尤其是当控制器大部分用于 CRUD 操作时。事实上,控制器生成器的输出 ./script/generate controller 一般不需要进一步的修改就可以满足需要。考虑到这些相似性,Rails 3 引入了 Responder 来进一步简化代码。例如下面是 create操作全部所需的几行代码:
      class PostsController
        respond_to :html, :xml
    
        def create
          @post = Post.create(params[:post])
          respond_with(@post)
        end
      end
    在该代码片段中,如果 @post 保存成功,respond_with(@post) 将发送到 show 以显示新的记录,而假设对象的验证失败,则发送到 new

这仅仅是一个小样本。您可以在下一章节找到这些新功能的示例以及更多内容,例如从头构建 Rails 3 应用程序。

 

首次构建 Rails 3 应用程序

要运行 Rails 3,您的系统必须安装有 Ruby 1.8.7 版或 Ruby 1.9.2 版,或者该编程语言的较新版本及其附加库和解释程序。您的机器上最好同时安装有 Git 软件版本控制系统,因为 Rails 3 和许多其他重要的 Rails 项目都是在 Git 中进行维护的。您的系统还需要数据库引擎,例如 SQLite(版本 3)、MySQL 或者 PostgreSQL。开发 Rails 应用程序时,Web 服务器不是必须的,但它通常是生产部署的一部分。

要创建 Rails 3 应用程序,您必须拥有 Rails 3 预发布 gem 和所有其相关产品。这时,您只需通过运行几条命令(请参阅 清单 1)即可安装所需的组件。(在您继续进行之前请查看 Rails 3 文档,因为根据版本的不同具体的操作会有所不同。) 清单 1. Rails 3 预发布 gem 和相关产品

               
$ gem install rails3b
Due to a rubygems bug, you must uninstall all older versions of bundler for 0.9 to work
Successfully installed mime-types-1.16
Successfully installed mail-2.1.2
Successfully installed text-hyphen-1.0.0
Successfully installed text-format-1.0.0
Successfully installed memcache-client-1.7.8
Successfully installed rack-1.1.0
Successfully installed rack-mount-0.4.7
Successfully installed abstract-1.0.0
Successfully installed erubis-2.6.5
Successfully installed i18n-0.3.3
Successfully installed tzinfo-0.3.16
Successfully installed bundler-0.9.5
Successfully installed thor-0.13.1
Successfully installed rails3b-3.0.1
14 gems installed

$ gem install arel --pre
Successfully installed activesupport-3.0.0.beta
Successfully installed arel-0.2.pre
2 gems installed

$ gem install rails --pre
Successfully installed activemodel-3.0.0.beta
Successfully installed actionpack-3.0.0.beta
Successfully installed activerecord-3.0.0.beta
Successfully installed activeresource-3.0.0.beta
Successfully installed actionmailer-3.0.0.beta
Successfully installed railties-3.0.0.beta
Successfully installed rails-3.0.0.beta
7 gems installed

 

下一步是生成应用程序 — 在 清单 2 中显示了一个小 wiki。该应用程序创建并管理文章。每一篇文章都有一个标题和一些散文,通过从现有页面的正文创建一个指向新文章的引用,您即可创建一篇新的文章。引用可以是任一驼峰式大小写单词,例如 TheSolarSystem 或者 TheOscars

注意:可通过下面的 下载 表格获取该 wiki 应用程序的源代码。 清单 2. Wiki Rails 应用程序

               
$ rails wiki

 

如果您运行了 ls -lR 来查看应用程序的内容,将会显示一些新文件:

  • Gemfile,即前面曾提到的 gem 清单。该文件必须至少包含两行:一行指向 Rails 3 beta gem 的源,另一行则绑定 Rails 3 beta gem 本身。您或许还需要第三行(至少)以连接数据库:
    source 'http://gemcutter.org'
    gem "rails", "3.0.0.beta"
    gem "sqlite3-ruby", :require => "sqlite3"
  • config/application.rb,它包含 config/environment.rb 中以前提供的很多选项。虽然后者仍然保留,但很大程度上已不再使用该文件。config/application.rb 的一个显著的附加功能是 generators block
    config.generators do |g|
      g.orm             :active_record
      g.template_engine :erb
      g.test_framework  :test_unit, :fixture => true
    end
    您的 Rails 3 应用程序可以使用一些兼容的对象关系映射器 (ORM)、模板引擎和测试框架。生成器块会指定应用程序的首选项,并根据您的模型、视图等调用适当的生成器。
  • db/seeds.rb,该文件对于 Rails 3 来说并不是新增的,但却有必要着重介绍一下,因为它是最近不久刚增加的功能(在 Rails 2.3.4 版引入的)。如果您的应用程序需要初始数据以正常运行,例如一个管理用户、价格代码或静态页面,那么您可以在 db/seeds.rb 中创建这些数据并运行任务 rake db:seed。在 Seed 文件之前,不存在初始化的惯例,许多开发人员把代码放入迁移中,这样容易混淆创建数据库和填充数据库之间的不同之处。
  • config.ru,存在于每个 Rails 3 应用程序的根目录下,即所谓的 rackup 文件,也就是基于 Rack 的应用程序的配置文件。Rails 3 是一个 Rack 应用程序,并且与任一支持 Rack 的 Web 服务器相兼容。总的来说,除非您想要添加其他 Rack 组件,否则请不要更改 config.ru 文件。

还有一些其他新文件;不过大多数看上去与 Rails 版本 2.3 相似。config/routes.rb 文件的功能与以往相同,只不过更加简化、更具有 Ruby 的特色。您将很快会看到一个示例。

生成应用程序并编辑 Gemfile 以声明依赖性之后,下一步就是收集应用程序所需的 gem。这是由新的实用程序 bundle(请参阅 清单 3)来完成的工作。 清单 3. 收集所需的 gem

               
$ bundle
installFetching source index from http://gemcutter.org
Resolving dependencies
Installing abstract (1.0.0) from system gems
Installing actionmailer (3.0.0.beta) from system gems
Installing actionpack (3.0.0.beta) from system gems
Installing activemodel (3.0.0.beta) from system gems
Installing activerecord (3.0.0.beta) from system gems
Installing activeresource (3.0.0.beta) from system gems
Installing activesupport (3.0.0.beta) from system gems
Installing arel (0.2.1) from rubygems repository at http://gemcutter.org
Installing builder (2.1.2) from system gems
Installing bundler (0.9.7) from rubygems repository at http://gemcutter.org
Installing erubis (2.6.5) from system gems
Installing i18n (0.3.3) from system gems
Installing mail (2.1.2) from system gems
Installing memcache-client (1.7.8) from system gems
Installing mime-types (1.16) from system gems
Installing rack (1.1.0) from system gems
Installing rack-mount (0.4.7) from system gems
Installing rack-test (0.5.3) from system gems
Installing rails (3.0.0.beta) from system gems
Installing railties (3.0.0.beta) from system gems
Installing rake (0.8.7) from system gems
Installing sqlite3-ruby (1.2.5) from rubygems repository at
    http://gemcutter.org with native extensions
Installing text-format (1.0.0) from system gems
Installing text-hyphen (1.0.0) from system gems
Installing thor (0.13.3) from rubygems repository at http://gemcutter.org
Installing tzinfo (0.3.16) from system gems
Your bundle is complete!

 

bundle 实用程序,简称 Bundler,可用于下载和安装所有在 Gemfile 中指定的 gem 以及任何这些 gems 的依赖项(请参阅 清单 4)。该 bundle 实用程序还可以将所有依赖项复制到您的应用程序中,使得您的代码库自给自足。具体来说,如果您运行 bundle pack,Bundler 会将所有 gem 的资料复制到 vendor/cache。 清单 4. 运行 bundle 实用程序

               
$ bundle pack
Copying .gem files into vendor/cache
  * bundler-0.9.7.gem
  * thor-0.13.3.gem
  * abstract-1.0.0.gem
  * mime-types-1.16.gem
  * text-hyphen-1.0.0.gem
  * rack-mount-0.4.7.gem
  * rake-0.8.7.gem
  * text-format-1.0.0.gem
  * tzinfo-0.3.16.gem
  * rack-test-0.5.3.gem
  * builder-2.1.2.gem
  * erubis-2.6.5.gem
  * memcache-client-1.7.8.gem
  * rack-1.1.0.gem
  * sqlite3-ruby-1.2.5.gem
  * i18n-0.3.3.gem
  * activesupport-3.0.0.beta.gem
  * arel-0.2.1.gem
  * mail-2.1.2.gem
  * activemodel-3.0.0.beta.gem
  * activerecord-3.0.0.beta.gem
  * actionpack-3.0.0.beta.gem
  * railties-3.0.0.beta.gem
  * actionmailer-3.0.0.beta.gem
  * activeresource-3.0.0.beta.gem
  * rails-3.0.0.beta.gem

$ ls vendor/cache
abstract-1.0.0.gem      memcache-client-1.7.8.gem
actionmailer-3.0.0.beta.gem mime-types-1.16.gem
actionpack-3.0.0.beta.gem   rack-1.1.0.gem
activemodel-3.0.0.beta.gem  rack-mount-0.4.7.gem
activerecord-3.0.0.beta.gem rack-test-0.5.3.gem
activeresource-3.0.0.beta.gem   rails-3.0.0.beta.gem
activesupport-3.0.0.beta.gem    railties-3.0.0.beta.gem
arel-0.2.1.gem          rake-0.8.7.gem
builder-2.1.2.gem       sqlite3-ruby-1.2.5.gem
bundler-0.9.7.gem       text-format-1.0.0.gem
erubis-2.6.5.gem        text-hyphen-1.0.0.gem
i18n-0.3.3.gem          thor-0.13.3.gem
mail-2.1.2.gem          tzinfo-0.3.16.gem

 

将 vendor/cache 视为应用程序自己的 gem 存储库。您可以将代码库移动到任何地方,并可以获得您所需的 gem 软件和版本 — 无需远程存储器即可实现。例如,如果您在 bundle pack 之后运行 bundle install,gem 会从您的应用程序存储库安装到您的系统中(请参阅 清单 5)。 清单 5. 安装 gem

               
Fetching source index from http://gemcutter.org
Resolving dependencies
Installing abstract (1.0.0) from .gem files at
  /Users/strike/projects/rails3/wiki/vendor/cache
Installing actionmailer (3.0.0.beta) from .gem files at
  /Users/strike/projects/rails3/wiki/vendor/cache
Installing actionpack (3.0.0.beta) from .gem files at
  /Users/strike/projects/rails3/wiki/vendor/cache
...
Installing thor (0.13.3) from .gem files at
  /Users/strike/projects/rails3/wiki/vendor/cache
Installing tzinfo (0.3.16) from .gem files at
  /Users/strike/projects/rails3/wiki/vendor/cache
Your bundle is complete!

 

 

使用 wiki

要创建应用程序,则需要为页面生成一个工作框架(scaffold)、创建数据库、将初始页面放到数据库并且设定所需的路径(请参阅 清单 6)。 为了简单化,仅限在某些字段使用 wiki 页面记录:标题、标头(标题的缩略语)、正文和时间截(以用于记录页面的创建时间和最新修改时间)。标题和标头是字符串字段;散文是文本字段;时间截是日 期和时间字段。(当然,一个真正的 wiki 还会有其他字段,如最近的作者以及页面的修订历史记录。为了尽量简洁,该例子还省略了用户和会话、格式以及各种身份验证和授权。)您可以使用 rails generate scaffold 命令生成一个初始模型、一系列视图以及一个控制器。 清单 6. 完整的 wiki 应用程序

               
$ rails generate scaffold page title:string slug:string body:text --timestamps
      invoke  active_record
      create    db/migrate/20100221115613_create_pages.rb
      create    app/models/page.rb
      invoke    test_unit
      create      test/unit/page_test.rb
      create      test/fixtures/pages.yml
       route  resources :pages
      invoke  scaffold_controller
      create    app/controllers/pages_controller.rb
      invoke    erb
      create      app/views/pages
      create      app/views/pages/index.html.erb
      create      app/views/pages/edit.html.erb
      create      app/views/pages/show.html.erb
      create      app/views/pages/new.html.erb
      create      app/views/pages/_form.html.erb
      create      app/views/layouts/pages.html.erb
      invoke    test_unit
      create      test/functional/pages_controller_test.rb
      invoke    helper
      create      app/helpers/pages_helper.rb
      invoke      test_unit
      create        test/unit/helpers/pages_helper_test.rb
      invoke  stylesheets
      create    public/stylesheets/scaffold.css

 

如果您想知道 ./script/generate 命令有何变化,回忆一下,该命令已经被全能的 rails 命令包含了。

运行 rake db:create db:migrate 以创建数据库:

$ rake db:create db:migrate
==  CreatePages: migrating ====================================================
-- create_table(:pages)
   -> 0.0010s
==  CreatePages: migrated (0.0011s) ===========================================

 

该 Wiki 现已存在,但却是空的。添加一个初始页面作为所有其他页面的基准。编辑文件 db/seeds.rb,并编写代码以创建一个新的页面,如 清单 7 中所示。 清单 7. wiki 基准页面

               
Page.create(
  :title    => 'The Marx Brothers Wiki',
  :slug     => 'Home',
  :body     => 'An encyclopedic guide to the Marx Brothers.')

 

运行 rake db:seed 以执行代码。您可以通过使用 rails console 快速浏览以验证页面,如 清单 8 中所示。 清单 8. 验证基准页面

               
$ rake db:seed
(in /Users/strike/projects/rails3/wiki)

$ rails console
Loading development environment (Rails 3.0.0.beta)
irb(main):001:0> Page.all
=> [#<Page id: 1, title: "The Marx Brothers Wiki", slug: "Home",
    body: "An encyclopedic guide to the Marx Brothers.",
    created_at: "2010-02-21 12:24:43", updated_at: "2010-02-21 12:24:43">]

 

在继续运行编码之前,请先设定路径。需要两条路径:一条默认的路径用来查找主页面,而另外一条路径则通过标头来查找页面。清单 9 显示了最终版的 config/routes.rb 文件。 清单 9. config/routes.rb(最终版)

               
Wiki::Application.routes.draw do |map|
  resources :pages
  root :to => "pages#show"
end

 

清单 6 中,rails generate scaffold page 这一行命令可自动在第二行创建路径,这是 REST 式的。您必须在第三行手动添加路径。用于指定站点路径的默认 “根目录” 的语法是 Rails 3 中的新增功能。第三行定义的是,“将路径 ‘/’ 映射到页面控制器的 ‘show’ 方法”。show 方法的代码将在数据库中查找主页面并显示出来。

添加新的根目录路径后,需要删除 public/index.html 文件以避免产生冲突:

$ rm public/index.html

 

现在,让我们来关注页面控制器。Rails 3 中的控制器代码可以极其简单。清单 10 通过单一的 show 方法,显示了控制器的初始实现。 清单 10. Rails 3 控制器

               
class PagesController < ApplicationController
  respond_to :html

  def show
    @page = Page.where( :slug => ( params[:id] || 'Home' ) ).first
    respond_with( @page )
  end
end

 

正如您所看到的,通常在 Rails 2 控制器中提供的所有模板都不见了。respond_to 列出了控制器所支持的格式;此处,它仅会对 HTML 的请求做出反应。respond_with 是逻辑快捷方式,用于决定控制器应如何继续处理。

查询的语法也是大有不同。查询是 Rails 3 关系代数的一个示例。您可能会想知道为什么需要有 first 后缀。where 和其他表达查询的操作数并不会真正引起查询语句被执行。相反地,查询站点一直闲置,直到真正需要数据时才启动。这就是延迟加载,即尽可能长的延迟查询语句的执行。first 命令将触发数据库中的实际查询。

如果您现在运行应用程序,您会看到与 图 1 相似的情况。 图 1. Rails 3 wiki 应用程序 该屏幕截图显示带有以下文本的页面:“标题:”Marx Brothers wiki;“标头:”主页;“正文:” Marx Brothers 的百科全书指南。底部显示有 “编辑” 和 “后一页” 的按钮。

现在,您可以向控制器中添加更多的代码。清单 11 显示了完整的控制器。 清单 11. 完整的 Rails 3 控制器

               
class PagesController < ApplicationController
  respond_to :html
  before_filter :get_page, :except => [ :create ]

  def create
    respond_with( @page = Page.create( params[ :page ] ) )
  end

  def edit
  end

  def index
    render :action => :show
  end

  def show
    @page ||= Page.new( :slug => params[ :id ] )

    if @page.new_record?
      render :action => :new
    else
      respond_with( @page )
    end
  end

  def update
    @page.update_attributes( params[ :page ] )
    respond_with( @page )
  end

  private

    def get_page
      @page = Page.where( :slug => ( params[:id] || 'Home' ) ).first ||
        Page.where( :id => params[:id] ).first
    end
end

 

在该控制器中,index 方法仅仅反映没有页面标示符的 show 操作,从而呈现主页面。show 会显示一个页面,并提供一个 ID 或标头(所有操作的查询都集中在 get_page 中,从而进一步减少了代码的数量);如果某个页面不存在,则会准备一个新的页面以供进行编辑。

Page 模型仅仅可以验证所有显示的字段:

class Page > ActiveRecord::Base
  validates_presence_of :body, :slug, :title
end

 

将驼峰式大小写引用转换为指向其他页面的链接,这一工作是在 Page 模型的视图中进行的。由 app/helpers/pages_helper.rb 中的 helper 函数来完成这一工作,从而保持视图的最小化(请参阅 清单 12)。 清单 12. 驼峰式大小写转换 helper 函数

               
module PagesHelper
  def wikify( page )
    return '' if page.body.blank?
    page.body.gsub( /^([A-Z][[:alnum:]]*([A-Z][[:alnum:]]*)+)/ ) do |match|
      link_to( $1, :action => :show, :id => $1 )
    end
  end
end

 

该视图是典型的视图,如 清单 13 中所示。 清单 13. 典型视图

               
<p>
  <b>Title:</b>
  <%= @page.title %>
</p>

<p>
  <b>Body:</b>
  <%= raw wikify( @page ) %>
</p>

<%= link_to 'Edit', edit_page_path(@page) %> |
<%= link_to 'Back', pages_path %>

 

raw 操作数是 Rails 3 中新增的功能。与以前版本的 Rails 不同,默认情况下所有的字符串都可以(去掉了 HTML)安全发送。如果要通过 HTML 发送一个字符串,则必须使用 raw

 

切换 Rails

除了此处所介绍的功能改进和便捷性,Rails 3 还提供了比以前版本更佳的性能,尤其是在呈现部件方面。您还可以创建您专有的验证器类,并充分利用更为流畅的标准验证。例如,由 Jeremy McAnally 编写以下验证,一次需要四行单独的代码:

validates :login, :presence => true, :length => {:minimum => 4},
  :uniqueness => true, :format => { :with => /[A-Za-z0-9]+/ }

 

Rails 的官方教程 “Rails 指南” 目前正在更新为 Rails 3 版。您也可以在 Jeremy McAnally、Yehuda Katz、Gregg Pollack 和其他社区领导者们的博客中找到更详细的说明以及更便捷的解决方案(请参阅 参考资料)。一些受大众欢迎的书籍也正在进行修订,其中包括十分创新的《应用 Rails 进行敏捷 Web 开发》(请参阅 参考资料)。