2007 年 12 月 7 日星期五

Rails 2.0: 完工!

作者 David

在经过大约一年的制作后,Rails 2.0 最终完成。这是一个奇妙的发行版,绝对塞满了伟大的新特性、大量的修复以及令人难以置信的打磨。我们甚至还去除了相当多的累赘,以便让整个程序包更加连贯精简。

这对于 Ruby on Rails 来说也是一个里程碑。我个人已经在这个框架上工作了大约四年半的时间,并且我们还有一些贡献者已经参与了几乎同样长的时间。看到我们在那段时间里取得了如此大的进步,这真是让人感到满足。我们证明了最初的宣传值得,我们能够坚持不懈,继续努力超越自我。

在详细介绍特性之前,我只想对帮助实现此版本的所有人表示深切的感谢。从 Rails 核心中的快乐的男人,到数百个已应用补丁的贡献者,以及在这一年里参与社区的每个人。这次发布是大规模开源开发的胜利,你们所有人对于自己在其中所扮演的角色都应该感到非常自豪。欢呼!

把这些煽情的话放在一边,让我们深入这个盛宴,看看有哪些新增内容

动作包:资源

这是 2.0 中进行大部分动作的地方。我们对 RESTful 生活方式进行了一系列改进。首先,我们放弃了分号,而不再使用常规斜杠来表示自定义方法。因此/people/1;edit 现在变为 /people/1/edit。我们还向资源路由添加了名称空间功能,这使得限制事物(如管理界面)变得非常容易

map.namespace(:admin) do |admin| admin.resources :products, :collection => { :inventory => :get }, :member => { :duplicate => :post }, :has_many => [ :tags, :images, :variants ] end

它会为你提供命名路由,比如 inventory_admin_products_url 和 admin_product_tags_url。为了跟踪这种命名路由的增殖,我们添加了“rake routes”任务,它会列出 routes.rb 所创建的所有命名路由。

我们还制定了一项新约定,即所有基于资源的控制器在默认情况下将为复数。这允许以多种方式映射单个资源,并且仍然引用相同的控制器。示例


  # /avatars/45 => AvatarsController#show
  map.resources :avatars
  
  # /people/5/avatar => AvatarsController#show 
  map.resources :people, :has_one => :avatar

动作包:多视图

除了对资源进行改进之外,我们还对多视图进行了改进。我们已经有了 #respond_to,但我们将其更进一步,使其深入到模板中。我们将模板的格式与其渲染引擎分离开来。因此,show.rhtml 现在变为 show.html.erb,这是一个模板,它响应 show 动作以其 respond_to 中声明的 format.html 为默认对其进行渲染。现在,你可以拥有 show.csv.erb 之类的东西,它以 text/csv 为目标,但同样使用默认 ERB 渲染器。

因此,用于模板的新格式为 action.format.renderer。一些示例

  • show.erb:针对所有格式使用相同的 show 模板
  • index.atom.builder:使用 Builder 格式,以前称为 rxml,为 application/atom+xml MIME 类型呈现索引动作
  • edit.iphone.haml:使用自定义 HAML 模板引擎(默认情况下不包含)来呈现针对自定义 Mime::IPHONE 格式的编辑动作

说到 iPhone,我们已经使声明“虚假”类型的过程变得更简单,这些类型仅用于内部路由。例如,当你仅为 iPhone 需要特殊的 HTML 接口时。你需要做的就是类似于以下操作


  # should go in config/initializers/mime_types.rb
  Mime.register_alias "text/html", :iphone

  class ApplicationController < ActionController::Base
    before_filter :adjust_format_for_iphone
  
    private
      def adjust_format_for_iphone
        if request.env["HTTP_USER_AGENT"] && request.env["HTTP_USER_AGENT"][/(iPhone|iPod)/]
          request.format = :iphone
        end
      end
  end
  
  class PostsController < ApplicationController
    def index
      respond_to do |format|
        format.html   # renders index.html.erb
        format.iphone # renders index.iphone.erb
      end
    end
  end

鼓励你在 config/initializers/mime_types.rb 文件中声明自己的 MIME 类型的别名。默认情况下,此文件将包含在所有新应用程序中。

Action Pack:记录标识

关于资源的新驱动是,为处理 URL 的控制器和视图方法进行了一些简化。我们已经添加了许多惯例,以便随时将模型类转换为资源路由。示例


  # person is a Person object, which by convention will 
  # be mapped to person_url for lookup
  redirect_to(person)
  link_to(person.name, person)
  form_for(person)

Action Pack:HTTP loving

正如你可能已经了解的那样,Rails 2.0 中的 Action Pack 旨在更加贴近 HTTP 及其所有优点。资源、多种表现形式,但还有更多内容。我们已经添加了一个新模块来使用 HTTP 基本身份验证,这被证明是在 SSL 上执行 API 身份验证的一种绝佳方式。它非常易于使用。以下是一个示例(ActionController::HttpAuthentication 中有更多内容)


  class PostsController < ApplicationController
    USER_NAME, PASSWORD = "dhh", "secret"

    before_filter :authenticate, :except => [ :index ]

    def index
      render :text => "Everyone can see me!"
    end

    def edit
      render :text => "I'm only accessible if you know the password"
    end

    private
      def authenticate
        authenticate_or_request_with_http_basic do |user_name, password| 
          user_name == USER_NAME && password == PASSWORD
        end
      end
  end

我们还让你可以不必受到请求大量文件而带来的 HTTP 开销的影响,即可更轻松地按逻辑单位设置 JavaScript 和样式表文件。使用 javascript_include_tag(:all, :cache => true) 将在生产中将 public/javascripts/.js 转换为一个 public/javascripts/all.js 文件,同时在开发中仍保留文件分离,以便你可以迭代工作,而无需清除缓存。

沿着相同的思路,我们已经添加了欺骗那些不喜欢自行对请求进行管道处理的浏览器的选项。如果你设置 ActionController::Base.asset_host = “assets%d.example.com”,我们将自动将你的资产调用(如 image_tag)分发到 assets1 到 assets4。这允许浏览器一次打开更多连接,并提升应用程序的 perceived 速度。

操作包:安全性

使即用型安全应用程序的构建变得更加容易始终是一件乐事,对于 Rails 2.0,我们正通过多种方式进行操作。最重要的是,我们现在提供了一种处理 CRSF 攻击的内置机制。通过在所有表单和 Ajax 请求中包含特殊令牌,您可以防止从应用程序外部提出的请求。所有这些在新 Rails 2.0 应用程序中都是默认开启的,您可以使用 ActionController::Base.protect_from_forgery 非常轻松地在现有应用程序中将其开启(有关详细信息,请参阅 ActionController::RequestForgery保护)。

我们还使处理 XSS 变得更加容易,同时仍然允许用户将 HTML 嵌入到您的页面中。旧的 TextHelper#sanitize 方法已经从黑名单(非常难以保持安全)方法转为白名单方法。如果您已经在使用 sanitize,则您会自动获得更好的保护。您还可以调整默认情况下允许的标签,具体请参阅 TextHelper#sanitize。.

最后,我们增加了对 HTTP 仅限 Cookie 的支持。尚未所有浏览器都支持这些 Cookie,但您可以在支持它们的浏览器上使用它们。

操作包:异常处理

许多常见异常最好在共享级别(而不是每个动作)进行捕获。通过覆盖 rescue_action_in_public 一直以来都可以做到这一点,但这样必须自己展开 case 语句并调用 super。够了。因此,我们现在有一个名为 rescue_from 的类级别宏,您可使用它声明性地将特定异常指向给定动作。示例


  class PostsController < ApplicationController
    rescue_from User::NotAuthorized, :with => :deny_access
    
    protected
      def deny_access
        ...
      end
  end

操作包:Cookie 存储会话

Rails 2.0 中的默认会话存储现在是基于 Cookie 的会话存储。这意味着会话不再存储在文件系统或数据库中,而是由客户端以不可伪造的哈希形式保存。这不仅使它比传统会话存储快很多,而且还使其无需维护。无需 cron 作业来清除会话,并且无论您忘记做什么,服务器都不会崩溃,因为您突然在 tmp/session 中有 500K 个文件。

如果您遵循最佳做法并将会话使用量减至最低,则此设置非常好,例如仅存储用户 ID 和闪存的常见情况。但是,如果您计划在会话中存储核武器的发射代码,则默认 cookie 存储是一个糟糕的选择。虽然无法伪造它们(is_admin = true 很好),但可以查看其内容。如果这是您应用程序中的一个问题,您可以随时切换回传统的会话存储之一(但首先将此要求研究为代码问题)。

操作包:新的请求分析器

找出您的瓶颈所在实际使用中可能会很困难,但我们刚刚使用全新的请求分析器使其变得更加简单,它可以遵循整个使用脚本并报告汇总结果。像这样使用它


  $ cat login_session.rb
  get_with_redirect '/'
  say "GET / => #{path}"
  post_with_redirect '/sessions', :username => 'john', :password => 'doe'
  say "POST /sessions => #{path}"
  $ ./script/performance/request -n 10 login_session.rb

您可以详细了解 HTML 和文本中如何使用时间,因此您可以清楚地知道从何处着手加速应用程序。

动作包:其他

同样值得注意的是 AtomFeedHelper,它使用增强的 Builder 语法创建 Atom 馈送变得更加简单了。简单示例


  # index.atom.builder:
  atom_feed do |feed|
    feed.title("My great blog!")
    feed.updated((@posts.first.created_at))
  
    for post in @posts
      feed.entry(post) do |entry|
        entry.title(post.title)
        entry.content(post.body, :type => 'html')
  
        entry.author do |author|
          author.name("DHH")
        end
      end
    end
  end

我们进行了一些性能改进,因此资产标签调用现已变得便宜得多,并且我们正在缓存简单命名的路由,使其变得更快。

最后,我们已将 in_place_editor 和 autocomplete_for 踢入到置于官方 Rails SVN 中的插件中。

活动记录:性能

活动记录已进行过无数修复和小调整,但是它缺少一些新的重要功能。我们添加的新功能是一个非常简单的查询缓存,它可以识别相同请求内的类似 SQL 调用并返回缓存结果。对于可能难以使用 :include 或其他机制来处理的 N+1 情况,这非常不错。我们还极大地提升了夹具性能,这使得基于正常夹具使用的多数测试套件速度提高了 50-100%。

活动记录:性感迁移

有一种新的备用格式,可以用稍微更有效率的格式来声明迁移。在编写前

create_table :people do |t| t.column, “account_id”, :integer t.column, “first_name”, :string, :null => false t.column, “last_name”, :string, :null => false t.column, “description”, :text t.column, “created_at”, :datetime t.column, “updated_at”, :datetime end

现在可以编写

create_table :people do |t| t.integer :account_id t.string :first_name, :last_name, :null => false t.text :description t.timestamps end

活动记录:性感夹具

活动记录中夹具最近招致了很多批评。这些批评的关键点之一是它与声明夹具之间的依赖关系的工作方式有关。不得不通过其主键的 ID 来关联夹具一点也不有趣。现在这个问题已得到解决,您可以像这样编写夹具


  # sellers.yml
  shopify:
    name: Shopify

  # products.yml
  pimp_cup:
    seller: shopify
    name: Pimp cup

如您所见,现在不需要声明夹具的 ID,并且不必再使用 seller_id 来指代该关系,只需使用 seller 和夹具名称。

活动记录:XML 输入,JSON 输出

一段时间以来,活动记录都支持序列化到 XML。在 2.0 中,我们也添加了反序列化,这样您可以说 Person.new.from_xml(“David”) 并得到您所期望的结果。我们还添加了序列化到 JSON,它支持与 XML 序列化相同的语法(包括嵌套关联)。只需使用 person.to_json,然后您就可以继续了。

活动记录:减小权重

为了使 ActiveRecord 更加精简,我们移除了 acts_as_XYZ 功能并将它们放入了 Rails SVN 代码库中的各个插件。所以如果你正在使用 acts_as_list,你只需执行 ./script/plugin install acts_as_list 命令,然后一切都将继续进行,就像从未发生过什么事一样。

我们还做了一件更为激进的事,我们将所有商用数据库适配器都推到它们自己的 gem 包中了。所以 Rails 现在只附带 MySQL、SQLite 和 PostgreSQL 的适配器。这些是我们容易获得且愿意用来测试的数据库。但这并不意味着商用数据库被排除了在外。而是它们现在可以自由地从 Rails 主分发中独立发布。这可能是一件好事,因为商用数据库往往需要更多的异常和定期解决问题才能正常工作。

商用数据库适配器现在位于 gem 包中,它们都遵循相同的命名约定:activerecord-XYZ-adapter。所以如果你执行 gem install activerecord-oracle-adapter 命令,你就可以立即在该机器上的所有 Rails 应用程序中将 Oracle 作为适配器选项使用。你不必更改应用程序中的任何一行代码就能使用它。

这也意味着,新的数据库适配器将更容易在 Rails 世界中获得吸引力。只要你根据发布的约定打包你的适配器,用户只需安装该 gem 包即可开始使用了。

Active Record:带有语法醋味的 with_scope

ActiveRecord::Base.with_scope 已成为受保护的,以阻止人们在控制器(尤其是在过滤器中)中错误地使用它。现在,我们建议你只在模型本身中使用它。这是其设计用途,也是它逻辑上仍然合适的用途。但当然,这一切都是关于鼓励和劝阻。如果你权衡了利弊,仍然想在模型之外使用 with_scope,你可以随时通过 .send(:with_scope) 调用它。

ActionWebService 退出,ActiveResource 进入

Rail 在 SOAPREST 之间的争论中已经选择了一方,这可能不足为奇。除非你绝对必须出于集成目的而使用 SOAP,我们强烈建议你不要这样做。作为该决定的自然延伸,我们已将 ActionWebService 从默认包中移除。它只需通过 gem install actionwebservice 命令即可安装,但这传达了一个非常重要的信息。

与此同时,我们已将新的 ActiveResource 框架从测试版本中提取出来并放入了默认包中。ActiveResource 类似于 ActiveRecord,但它是用于资源的。它遵循类似的 API,并且被配置为仅使用使用资源驱动方法的 Rails 应用程序。例如,ActiveResource 可以访问基础的脚手架。

ActiveSupport

ActiveSupport 中没有什么太多新内容。我们提供了大量的新方法,比如 Array#rand 用于从数组中获取随机元素,Hash#except 用于根据不需要的键过滤散列等,并对日期进行了许多扩展。我们通过 assert_difference 优化了一些测试。除此之外,还有一些修复和调整。

Action Mailer

Action Mailer 更新很小。除了修复了一些错误,我们还增加了注册备用模板引擎和 assert_emails 的选项,这些选项的作用如下

  1. 断言在一个块内发送的电子邮件数量
    assert_emails 1 do
    post :signup, :name => ‘Jonathan’
    end

Rails: 调试器已回归

为了将所有内容整合在一起,我们对 Rails 整体进行了一系列改进。我最喜欢的是以调试器形式回归的断点。它是一个真正的调试器,而不仅仅是 IRB 转储。您可以来回调试、列出当前位置等。这些都来自 ruby-debug gem 的友好通知。所以您必须安装该调试器才能运行这个调试器。

要使用调试器,您只需安装 gem,在应用程序中将“debugger”放在某个地方,然后使用 —debugger 或 -u 启动服务器。当代码执行 debugger 命令时,您可以在运行服务器的终端中直接使用它。无需使用 script/breakpointer 或其他任何内容。您也可以在测试中使用调试器。

Rails: 清理您的环境

在 Rails 2.0 之前,全部 config/environment.rb 文件中会塞满各种各样的临时配置详细信息。现在,您可以将那些元素收集到自包含文件中并将其放在 config/initializers 下,它们将自动加载。新的 Rails 2.0 应用程序附带两个示例,分别是 inflections.rb(用于您自己的复数规则)和 mime_types.rb(用于您自己的 MIME 类型)。这应该确保您只需保留 config/environment.rb 中的默认设置。

Rails: 更简单的插件顺序

现在我们已经从 Rails 中提取出很大一部分内容并将其放入插件中,您可能也有一些需要此功能的其他插件。这可能要求您在自己的 acts_as_extra_cool_list 插件加载之前加载 acts_as_list,以便后者可以扩展前者。

以前,这需要您在 config.plugins 中命名您所有插件。当您只想表示“我只在乎在加载任何其他东西之前加载 acts_as_list”。现在,您可以使用 config.plugins = [ :acts_as_list, :all ] 确切地做到这一点。

以及其他数百个改进

我在上面讨论的只是完整 2.0 软件包的一小部分。我们已经将数百个 bug 修复、调整和功能增强塞进了 Rails 2.0 中。所有这些都源自大量热心的贡献者不知疲倦地改善该框架的方式,虽然很小,但很重要。

我鼓励您仔细检查 CHANGELOG 并了解有关更改的所有信息。

那么我如何升级呢?

如果您想将您的应用程序转移到 Rails 2.0,您应首先将其转移到 Rails 1.2.6。该版本包括对我们在 2.0 中剔除的大多数内容的弃用警告。所以如果您的应用程序在 1.2.6 上正常运行,且无弃用警告,您很有可能可以直接将其运行于 2.0。当然,如果您正在使用,比如说,分页,您需要安装 classic_pagination 插件。如果您正在使用 Oracle,您需要安装 activerecord-oracle-adapter gem。其他所有提取项也都如此。

那么我如何进行安装?

要通过 gem 安装,请执行

gem install rails -y

…如果您遇到此问题(未找到 gem),暂时从我们自己的库中获取 gem

gem install rails -y —source http://gems.rubyonrails.org

要从 SVN 标签中进行尝试,请使用(您可能需要根据您的当前 Rails 版本运行此命令两次)

rake rails:freeze:edge TAG=rel_2-0-1

注意:版本号为 2.0.1,因为在我们推出 2.0.0 之后,我们发现了一个小问题。