首页
/ Rails 3.1 发行说明精读:四大核心特性、升级配置全清单与这些特性在现代 Rails 源码中的存续

Rails 3.1 发行说明精读:四大核心特性、升级配置全清单与这些特性在现代 Rails 源码中的存续

2026-09-06 19:14:58作者:傅爽业Veleda

本篇基于 Rails 官方仓库中的 3.1 发行说明 展开精读。读完你不仅能完整掌握 Rails 3.1 的四大新特性(HTTP Streaming、可逆迁移、Asset Pipeline、jQuery 默认化)和逐文件的升级操作清单,还能通过当前仓库(版本 8.2.0.alpha)的源码路径,确认这些 2011 年落地的机制如今以什么形式存活在框架中。

需要说明前提:这份发行说明是历史文档,覆盖 Rails 3.1 时代(Ruby 1.8.7/1.9.2 兼容期)的 API 与配置写法;文中代码示例保留当年的 Ruby 1.8 风格 hash 语法(:key => value),用于忠实呈现 3.1 的原始形态。原文档只涵盖主要变更,具体 bug 修复请参考各框架的 CHANGELOG。

Rails 3.1 的四大亮点

发行说明开宗明义列出了 3.1 的四个主题:

  • Streaming(HTTP 流式响应)
  • Reversible Migrations(可逆迁移)
  • Assets Pipeline(资产管道)
  • jQuery 成为默认 JavaScript 库

以下各章按发行说明的结构逐一展开,并在关键处结合当前仓库源码做纵深印证。

升级现有应用到 Rails 3.1

发行说明建议:升级前先保证良好的测试覆盖率;如果还没升级到 Rails 3.0,先升到 3.0 并确认应用行为正常,再尝试 3.1。

Ruby 版本要求

Rails 3.1 要求 Ruby 1.8.7 或更高,官方弃用更早的 Ruby 版本;同时兼容 Ruby 1.9.2。发行说明特别警告了当时的坑:

  • Ruby 1.8.7 p248/p249 存在 marshal bug 会导致 Rails 崩溃(Ruby Enterprise Edition 自 1.8.7-2010.02 起已修复);
  • Ruby 1.9.1 会直接段错误,不可用;想用 1.9.x 应直接上 1.9.2。

注:当前仓库中的 Rails 已完全不再支持 Ruby 1.8/1.9 时代环境,本节仅作为 3.1 升级场景的历史事实记录。

Gemfile 修改

升级到 3.1.x(以 3.1.3 为例)时,发行说明给出的 Gemfile 改动如下:

gem "rails", "= 3.1.3"
gem "mysql2"

# 新的资产管道所需
group :assets do
  gem "sass-rails",   "~> 3.1.5"
  gem "coffee-rails", "~> 3.1.1"
  gem "uglifier",     ">= 1.0.3"
end

# Rails 3.1 的默认 JavaScript 库是 jQuery
gem "jquery-rails"

要点:sass-railscoffee-railsuglifier 三者构成资产管道的最小依赖组合,被统一收进 :assets 组;jquery-rails 是 3.1 默认 JS 库的来源。

config/application.rb

资产管道要求补充:

config.assets.enabled = true
config.assets.version = '1.0'

如果你的应用恰好把 /assets 路由给了某个业务资源,可以改前缀避免冲突:

# 默认值是 '/assets'
config.assets.prefix = '/asset-files'

config/environments/development.rb

  • 删除 RJS 遗留配置 config.action_view.debug_rjs = true(RJS 自此退出主流,由 rails-ujs/jQuery 取代);
  • 若启用资产管道,追加:
# 开发环境不压缩资产
config.assets.compress = false

# 展开加载资产的路径列表(便于调试)
config.assets.debug = true

config/environments/production.rb

生产环境的改动绝大多数服务于资产管道,更完整的说明见 Asset Pipeline 指南

# 压缩 JavaScript 与 CSS
config.assets.compress = true

# 预编译资产缺失时不回退到动态编译
config.assets.compile = false

# 为资产 URL 生成摘要(digest)
config.assets.digest = true

# 默认指向 Rails.root.join("public/assets")
# config.assets.manifest = YOUR_PATH

# 预编译额外资产(application.js、application.css 及所有非 JS/CSS 文件已自动包含)
# config.assets.precompile `= %w( admin.js admin.css )

# 强制全部请求走 SSL、启用 Strict-Transport-Security 并使用安全 Cookie
# config.force_ssl = true

config.assets.compile = falseconfig.assets.digest = true 组合,正是 3.1 之后生产部署的标准姿势:资产必须走预编译,缺失即报错而非现场编译。

config/environments/test.rb

# 为测试配置静态资产服务器,用 Cache-Control 提升性能
config.serve_static_assets = true
config.static_cache_control = "public, max-age=3600"

config/initializers/wrap_parameters.rb

若希望把参数包裹进嵌套 hash(新应用默认开启),添加该文件:

# 修改本文件后记得重启服务器。
# 本文件包含 ActionController::ParamsWrapper 的配置,默认启用。

# 为 JSON 请求启用参数包裹。可通过把 :format 设为空数组来关闭。
ActiveSupport.on_load(:action_controller) do
  wrap_parameters :format => [:json]
end

# 默认不在 JSON 中生成根元素。
ActiveSupport.on_load(:active_record) do
  self.include_root_in_json = false
end

这段配置背后的机制是 Action Controller 新增的 ActionController::ParamsWrapper(详见下文 Action Pack 章节)。

移除资产辅助方法中的 :cache 与 :concat 选项

资产管道接管后,视图里 :cache:concat 选项不再被使用,应从视图中删除。

创建 Rails 3.1 应用

# 前提:已安装 'rails' RubyGem
$ rails new myapp
$ cd myapp

Gem 的 vendoring

Rails 应用根目录的 Gemfile 决定启动所需的全部 gem,由 Bundler 处理:它安装全部依赖,甚至可以把依赖整体安装到应用本地,使应用不再依赖系统 gem。

Living on the Edge

Bundler + Gemfile 让“冻结”应用依赖变得容易。想直接从 Git 仓库捆绑 Rails,可传 --edge 标志:

$ rails new myapp --edge

如果本地已有 Rails 仓库的检出,可用 --dev 标志基于本地代码生成应用:

$ ruby /path/to/rails/railties/bin/rails new myapp --dev

--edge--dev 分别是“跟 git 主干”和“跟本地检出”两种开发工作流,也是今天 Rails 团队贡献者日常调试框架的方式原型。

架构层面的变化

Asset Pipeline

3.1 最大的架构变化是资产管道:它让 CSS 与 JavaScript 成为一等公民代码,支持规范的目录组织,也支持在插件与引擎中使用。管道由 Sprockets 驱动,完整用法见 Asset Pipeline 指南

HTTP Streaming

3.1 的另一项架构级新特性是 HTTP Streaming:允许浏览器在服务器还在生成响应的同时就开始下载样式表与 JavaScript。它要求 Ruby 1.9.2,属于显式开启(opt-in)特性,且需要 Web 服务器配合;发行说明指出当时流行的 NGINX + Unicorn 组合已具备利用条件。

在当前仓库的源码中可以印证这条技术线的延续:ActionController::Streaming 模块至今仍是框架的一部分,定义在 streaming.rb,3.1 引入的流式响应能力一直保留在 Action Controller 中。

默认 JS 库换为 jQuery

jQuery 是 Rails 3.1 随附的默认 JavaScript 库;如果你仍想用 Prototype,切换也很简单:

$ rails new myapp -j prototype

Identity Map

Active Record 在 3.1 引入了 Identity Map(恒等映射):它保存已被实例化的记录,再次访问同一行时直接返回已关联的对象。恒等映射按请求粒度创建,请求结束时清空。注意:Rails 3.1 默认关闭恒等映射

Railties 变更清单

  • jQuery 成为新的默认 JavaScript 库。
  • jQuery 与 Prototype 不再随框架 vendoring,改由 jquery-railsprototype-rails gem 提供。
  • 应用生成器接受任意字符串的 -j 选项:传 "foo" 会把 gem "foo-rails" 加入 Gemfile,应用 JS 清单文件会 require "foo""foo_ujs"。当时仅 prototype-railsjquery-rails 存在,并通过资产管道提供这些文件。
  • 生成应用或插件时会自动执行 bundle install,除非指定 --skip-gemfile--skip-bundle
  • controller 与 resource 生成器会自动产出资产占位文件(可用 --skip-assets 关闭);若可用,这些 stub 会使用 CoffeeScript 与 Sass。
  • Scaffold 与应用生成器在 Ruby 1.9 下使用新风格 hash 字面量;传 --old-style-hash 可生成旧风格。
  • Scaffold controller 生成器为 JSON(而非 XML)创建 format 块。
  • Active Record 日志输出到 STDOUT,在控制台中内联显示。
  • 新增 config.force_ssl 配置:加载 Rack::SSL 中间件,强制所有请求使用 HTTPS 协议。
  • 新增 rails plugin new 命令,生成带 gemspec、测试与测试用 dummy 应用的 Rails 插件。
  • 默认中间件栈加入 Rack::EtagRack::ConditionalGet
  • 默认中间件栈加入 Rack::Cache
  • 引擎(Engines)迎来重大更新——可以挂载到任意路径、启用资产、运行生成器等。

Action Pack 变更

Action Controller

  • 无法验证 CSRF token 真实性时会给出警告。
  • 控制器可用 force_ssl 强制该控制器下的数据传输走 HTTPS;用 :only:except 限定到具体 action。
  • config.filter_parameters 中声明的敏感查询参数,现在会从日志中的请求路径里过滤掉。
  • to_param 返回 nil 的 URL 参数会从查询串中移除。
  • 新增 ActionController::ParamsWrapper,把参数包裹为嵌套 hash,新应用的 JSON 请求默认开启,可在 config/initializers/wrap_parameters.rb 中定制(文件内容见上文升级章节)。
  • 新增 config.action_controller.include_all_helpers:默认情况下 ActionController::Base 会执行 helper :all 包含全部 helper;设为 false 后只包含 application_helper 和与控制器同名的 helper(如 foo_controller 对应 foo_helper)。
  • url_for 与命名 URL helper 开始接受 :subdomain:domain 选项。
  • 新增 Base.http_basic_authenticate_with,一个类方法调用即可完成简单的 HTTP Basic 认证。当前仓库中该方法仍保留在 http_authentication.rb,且签名已演进为关键字参数形式(name:, password:, realm:, message:, content_type:)。

发行说明给出的改造前后对照:

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

等价的新写法:

class PostsController < ApplicationController
  http_basic_authenticate_with :name => "dhh", :password => "secret", :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
end
  • 新增 streaming 支持,开启方式:
class PostsController < ActionController::Base
  stream
end

可用 :only / :except 限定到部分 action(该模块的当前位置见上文 streaming.rb 路径)。

  • redirect 路由方法开始接受选项 hash(只改 URL 的相应部分),或接受任何响应 call 的对象,使 redirect 逻辑可复用。

Action Dispatch

  • config.action_dispatch.x_sendfile_header 默认值改为 nilconfig/environments/production.rb 也不再为它设特定值,允许服务器通过 X-Sendfile-Type 自行设定。
  • ActionDispatch::MiddlewareStack 改为组合优于继承,不再是数组。
  • 新增 ActionDispatch::Request.ignore_accept_header 以忽略 accept 头。
  • Rack::Cache 进入默认栈。
  • Etag 职责从 ActionDispatch::Response 移入中间件栈。
  • 依赖 Rack::Session 存储 API 以获得更好的 Ruby 生态兼容性。这是向后不兼容的:Rack::Session 要求 #get_session 接受四个参数,并且要求 #destroy_session 而非单纯的 #destroy
  • 模板查找开始沿继承链向上搜索。

Action View

  • form_tag 新增 :authenticity_token 选项,可自定义处理或传 :authenticity_token => false 省略 token。
  • 创建 ActionView::Renderer,并为 ActionView::Context 定义了 API。
  • 原地修改 SafeBuffer 在 3.1 中被禁止。
  • 新增 HTML5 button_tag helper。
  • file_field 自动为外层 form 加上 :multipart => true
  • 标签 helper 支持从 :data hash 生成 HTML5 data-* 属性:
tag("div", :data => {:name => 'Stephen', :city_state => %w(Chicago IL)})
# => <div data-name="Stephen" data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]" />

键会被 dasherize(连字符化);值会做 JSON 编码,字符串和 symbol 除外。

  • csrf_meta_tag 更名为 csrf_meta_tags,并保留旧名作为别名以向后兼容。
  • 旧模板处理器 API 弃用;新 API 只要求处理器响应 call
  • rhtml 与 rxml 终于被移除出模板处理器。
  • 恢复 config.action_view.cache_template_loading,用于决定模板是否缓存。
  • 表单 submit helper 不再生成 "object_name_id" 这样的 id。
  • FormHelper#form_for 允许直接用选项传 :method,而无需套在 :html hash 里:form_for(@post, remote: true, method: :delete) 取代 form_for(@post, remote: true, html: { method: :delete })
  • 提供 JavaScriptHelper#j() 作为 JavaScriptHelper#escape_javascript() 的别名,取代 JSON gem 在模板中经由 JavaScriptHelper 注入的 Object#j()
  • 日期时间选择器支持 AM/PM 格式。
  • auto_link 从 Rails 移除,抽取为 rails_autolink gem。

Active Record 变更

这一章是发行说明中最长、对日常开发影响最大的部分。

模型层

  • 新增类方法 pluralize_table_names,可针对单个模型单独设置表名单复数规则(此前只能通过 ActiveRecord::Base.pluralize_table_names 全局设置):
class User < ActiveRecord::Base
  self.pluralize_table_names = false
end
  • 单数关联支持带块地设置属性,块在实例初始化后被调用:
class User < ActiveRecord::Base
  has_one :account
end

user.build_account{ |a| a.credit_limit = 100.0 }
  • 新增 ActiveRecord::Base.attribute_names 返回属性名列表;模型是抽象类或表不存在时返回空数组。
  • CSV Fixtures 弃用,将在 Rails 3.2.0 移除支持。
  • ActiveRecord#newActiveRecord#createActiveRecord#update_attributes 开始接受第二个 hash 参数,用于指定“以什么角色做属性赋值”,建立在 Active Model 新增的批量赋值能力之上:
class Post < ActiveRecord::Base
  attr_accessible :title
  attr_accessible :title, :published_at, :as => :admin
end

Post.new(params[:post], :as => :admin)
  • default_scope 开始接受块、lambda 或任何响应 call 的对象,实现惰性求值。
  • 默认作用域改为在尽可能晚的时机求值,避免“作用域隐式包含默认作用域、之后又无法用 Model.unscoped 摆脱”的问题。
  • PostgreSQL 适配器只支持 PostgreSQL 8.2 及以上。
  • ConnectionManagement 中间件改为在 rack body flush 之后清理连接池。
  • 新增 update_column:更新对象的某个属性,跳过验证与回调。发行说明明确建议——除非你确定不想执行任何回调(包括不修改 updated_at 列),否则应使用 update_attributesupdate_attribute;且不应在新记录上调用。该方法在当前仓库的 persistence.rb 中依然是持久化 API 的一部分。
  • :through 关联的 through 或 source 关联可以是任意关联,包括其他 :through 关联与 has_and_belongs_to_many 关联。
  • 当前数据库连接的配置可通过 ActiveRecord::Base.connection_config 访问。
  • COUNT 查询不再携带 limit/offset,除非两者同时提供:
People.limit(1).count           # => 'SELECT COUNT(*) FROM people'
People.offset(1).count          # => 'SELECT COUNT(*) FROM people'
People.limit(1).offset(1).count # => 'SELECT COUNT(*) FROM people LIMIT 1 OFFSET 1'
  • ActiveRecord::Associations::AssociationProxy 被拆分:职责操作关联的 Association 类(及子类),加一个薄的 CollectionProxy 包装集合关联——避免命名空间污染、分离关注点,为后续重构铺路。
  • 单数关联(has_onebelongs_to)不再有代理,直接返回关联记录或 nil。因此不应再使用 bob.mother.create 这类未公开文档的方法——应使用 bob.create_mother

关联删除语义(重要且不兼容)

  • has_many :through 关联支持 :dependent 选项。由于历史与实际原因,association.delete(*records) 采用的默认删除策略是 :delete_all(而普通 has_many 的默认策略是 :nullify);且只有 source 反射是 belongs_to 时它才生效,其他情况应直接修改 through 关联。
  • has_and_belongs_to_manyhas_many :throughassociation.destroy 的行为改变:从此之后,对关联调用 'destroy' 或 'delete' 一律理解为“断开链接”,而不(必然)是“删掉关联记录本身”。
    • 此前 has_and_belongs_to_many.destroy(*records) 会销毁记录本身、且不删除中间表记录;现在它删除中间表记录。
    • 此前 has_many_through.destroy(*records) 会销毁记录本身和中间表记录(更早的版本只销毁记录本身);现在它只销毁中间表记录。
    • 发行说明坦承这一改动在一定程度上向后不兼容,且无法先做“弃用”再改;改动目的是统一各类关联中 destroy/delete 的语义。若要真正销毁记录本身,可写 records.association.each(&:destroy)

迁移

  • change_table 支持 :bulk => true 选项,把块内定义的所有 schema 变更合并为单条 ALTER 语句:
change_table(:users, :bulk => true) do |t|
  t.string :company_name
  t.change :birthdate, :datetime
end
  • 移除对 has_and_belongs_to_many 中间表访问属性值的支持,需要改用 has_many :through
  • has_onebelongs_to 关联新增 create_association! 方法。
  • 迁移从此可逆:Rails 会自动推导如何回滚。使用可逆迁移只需定义 change 方法:
class MyMigration < ActiveRecord::Migration
  def change
    create_table(:horses) do |t|
      t.column :content, :text
      t.column :remind_at, :datetime
    end
  end
end
  • 有些操作无法自动求逆。如果你知道怎么逆,就自己定义 updown;如果在 change 里定义了不可逆的东西,回滚时会抛出 IrreversibleMigration 异常。这一机制在当前仓库中依然有效,IrreversibleMigration 的抛出点集中在 command_recorder.rb,例如 remove_column is only reversible if given a typedrop_table is only reversible if given a single table name 等检查,与 3.1 说明的设计一脉相承。
  • 迁移开始使用实例方法而非类方法:
class FooMigration < ActiveRecord::Migration
  def up # 不是 self.up
    # ...
  end
end
  • 由模型生成器与构造性迁移生成器(例如 add_name_to_users)产出的迁移文件,默认使用可逆迁移的 change 方法,而非常规的 up/downchange 的用法如今仍写在 migration.rb 的类文档中。
  • 移除对关联上字符串 SQL 条件插值的支持,应改用 proc:
has_many :things, :conditions => 'foo = #{bar}'          # 之前
has_many :things, :conditions => proc { "foo = #{bar}" } # 之后

proc 内部 self 是关联的拥有者对象;除非正在预加载该关联,此时 self 是关联所在的类。proc 里也可以写“常规”条件:

has_many :things, :conditions => proc { ["foo = ?", bar] }
  • has_and_belongs_to_many:insert_sql:delete_sql 此前允许调用 'record' 取被插入/删除的记录;现在该记录作为参数传入 proc。

安全与密码

  • 新增 ActiveRecord::Base#has_secure_password(经由 ActiveModel::SecurePassword),用 BCrypt 加密加盐封装最简密码用法:
# Schema: User(name:string, password_digest:string, password_salt:string)
class User < ActiveRecord::Base
  has_secure_password
end

这条线在当前仓库中演化为 secure_password.rb,且已提供 bcrypt 与 argon2 两套实现(active_model/secure_password/bcrypt_password.rbactive_model/secure_password/argon2_password.rb),3.1 引入的“一行代码获得密码哈希”心智一直保留至今。

其他 Active Record 变更

  • 生成模型时,belongs_toreferences 列默认会加上 add_index
  • 设置 belongs_to 对象的 id 会同步更新对象引用。
  • ActiveRecord::Base#dup#clone 的语义向 Ruby 常规语义靠拢:
    • #clone:记录的浅拷贝,连冻结状态一起复制,不触发任何回调;
    • #dup:复制记录并触发 after initialize 钩子,不复制冻结状态,清空全部关联;dup 出来的记录 new_record?trueidnil、可保存。
  • 查询缓存(query cache)现在可以配合 prepared statements 工作,应用侧无需任何改动。

Active Model 变更

  • attr_accessible 接受 :as 选项指定角色(与上文 Active Record 的角色化批量赋值配套)。
  • InclusionValidatorExclusionValidatorFormatValidator 现在接受 proc、lambda 或任何响应 call 的对象作为选项;它会被传入当前记录调用,对 InclusionValidator/ExclusionValidator 返回一个响应 include? 的对象,对 FormatValidator 返回正则表达式对象。
  • 新增 ActiveModel::SecurePassword,用 BCrypt 加密加盐封装最简密码用法。
  • ActiveModel::AttributeMethods 支持按需定义属性。
  • 新增对观察者(observers)选择性启用/禁用的支持。
  • 不再支持备用 I18n 命名空间查找。

Active Resource 变更

  • 所有请求的默认格式改为 JSON。想继续用 XML 需在类中显式声明:
class User < ActiveResource::Base
  self.format = :xml
end

Active Support 变更

  • ActiveSupport::Dependenciesload_missing_constant 中若发现已存在同名常量,会抛出 NameError
  • 新增报告方法 Kernel#quietly,同时静默 STDOUTSTDERR
  • 新增 String#inquiry,把 String 便捷地转为 StringInquirer 对象。
  • 新增 Object#in?,测试一个对象是否包含在另一个对象中。
  • LocalCache 策略成为真正的中间件类,不再是匿名类。
  • 引入 ActiveSupport::Dependencies::ClassCache,用于持有可重载类的引用。
  • ActiveSupport::Dependencies::Reference 重构为直接利用新的 ClassCache
  • 在 Ruby 1.8 下将 Range#cover? 作为 Range#include? 的别名 backport。
  • Date/DateTime/Time 新增 weeks_agoprev_week
  • ActiveSupport::Dependencies.remove_unloadable_constants! 新增 before_remove_const 回调。

弃用:ActiveSupport::SecureRandom 被弃用,建议改用 Ruby 标准库的 SecureRandom

从 3.1 到当前仓库:这些特性的传承

以当前仓库的版本 8.2.0.alpha 为参照,可以确认 3.1 的多数核心机制至今仍是框架骨架的一部分:

3.1 引入的特性 当前仓库中的对应位置
HTTP Streaming actionpack/lib/action_controller/metal/streaming.rbActionController::Streaming 模块)
可逆迁移 / IrreversibleMigration activerecord/lib/active_record/migration/command_recorder.rb(命令录制与可逆性检查)
http_basic_authenticate_with actionpack/lib/action_controller/metal/http_authentication.rb
has_secure_password activemodel/lib/active_model/secure_password.rb(bcrypt/argon2 双实现)
update_column activerecord/lib/active_record/persistence.rb
change 式迁移 activerecord/lib/active_record/migration.rb 文档中的标准示例

从源码结构看,3.1 发行说明中的几条“不兼容变更”(Etag 移入中间件栈、单数关联去代理化、AssociationProxy 拆分、destroy/delete 语义统一)正是现代 Rails 关联与请求栈代码组织方式的起点。阅读这份历史文档时,建议按本文的路径对照当前源码,能直观看到十多年来框架在“保持 API 稳定”与“内部持续重构”之间取得的平衡。

附注

  • 本文内容全部取材自仓库内的 3.1 发行说明,原文由 Vijay Dev 整理;文中源码路径均指向当前仓库文件,供对照阅读。
  • 文中引用的 config.assets.*serve_static_assetsattr_accessible 等 API 属于 3.1 时代写法,仅作历史事实呈现;在当前 Rails 版本中,资产管道、参数包裹、批量赋值防护(strong parameters)等机制已有新的配置形态与命名,实操时请以当前版本的 Asset Pipeline 指南 等文档为准。
登录后查看全文
热门项目推荐
相关项目推荐