1 一般建议
计划升级现有项目之前,应该确定有升级的必要。你要考虑几个因素:对新功能的需求,难于支持旧代码,以及你的时间和技能,等等。
1.1 测试覆盖度
为了确保升级后应用依然能正常运行,最好的方式是具有足够的测试覆盖度。如果没有自动化测试保障应用,你就要自己花时间检查有变化的部分。对升级 Rails 来说,你要检查应用的每个功能。不要给自己找麻烦,在升级之前一定要保障有足够的测试覆盖度。
1.2 升级过程
升级 Rails 版本时,最好放慢脚步,一次升级一个小版本,充分利用弃用提醒。Rails 版本号的格式是“大版本.小版本.补丁版本”。大版本和小版本允许修改公开 API,因此可能导致你的应用出错。补丁版本只修正缺陷,不改变公开 API。
升级过程如下:
- 编写测试,确保能通过。
- 升级到当前版本的最新补丁版本。
- 修正测试和弃用的功能。
- 升级到下一个小版本的补丁版本。
重复上述过程,直到你所选的版本为止。每次升级版本都要修改 Gemfile
中的 Rails 版本号(以及其他需要升级的 gem),再运行 bundle update
。然后,运行下文所述的 update
任务,更新配置文件。最后运行测试。
Rails 的所有版本在这个页面中列出。
1.3 Ruby 版本
发布新版 Rails 时,一般会紧跟最新的 Ruby 版本:
- Rails 5 要求 Ruby 2.2.2 或以上版本
- Rails 4 建议使用 Ruby 2.0,要求 1.9.3 或以上版本
- Rails 3.2.x 是支持 Ruby 1.8.7 的最后一个版本
- Rails 3 及以上版本要求 Ruby 1.8.7 或以上版本。官方不再支持之前的 Ruby 版本,应该尽早升级。
Ruby 1.8.7 p248 和 p249 有一些缺陷,会导致 Rails 崩溃。 Ruby Enterprise Edition 1.8.7-2010.02 修正了这些缺陷。对 1.9 系列来说,1.9.1 完全不能用,因此如果你使用 1.9.x 的话,应该直接跳到 1.9.3。
1.4 update
任务
Rails 提供了 app:update
任务(4.2 及之前的版本是 rake rails:update
)。更新 Gemfile
中的 Rails 版本号之后,运行这个任务。这个任务在交互式会话中协助你创建新文件和修改旧文件。
$ rails app:update identical config/boot.rb exist config conflict config/routes.rb Overwrite /myapp/config/routes.rb? (enter "h" for help) [Ynaqdh] force config/routes.rb conflict config/application.rb Overwrite /myapp/config/application.rb? (enter "h" for help) [Ynaqdh] force config/application.rb conflict config/environment.rb ...
别忘了检查差异,以防有意料之外的改动。
2 从 Rails 5.0 升级到 5.1
Rails 5.1 的变动参见发布记。
2.1 温和弃用顶层 HashWithIndifferentAccess
类
如果你的应用使用顶层 HashWithIndifferentAccess
类,应该逐渐转用 ActiveSupport::HashWithIndifferentAccess
类。
这只是一项温和的弃用,目前代码不受影响,也不看看到提醒,但是以后会删除这个常量。
此外,如果 YAML 文档转储中包含这个类的对象,要重新加载并转储,以便引用正确的常量,防止以后无法加载。
2.2 application.secrets
全部使用符号键索引
如果你的应用在 config/secrets.yml
中存储嵌套的配置,现在所有键都通过符号加载,请勿再使用字符串加载。
请把
Rails.application.secrets[:smtp_settings]["address"]
改为:
Rails.application.secrets[:smtp_settings][:address]
3 从 Rails 4.2 升级到 5.0
Rails 5.0 的变动参见发布记。
3.1 要求 Ruby 2.2.2+
从 Ruby on Rails 5.0 开始,只支持 Ruby 2.2.2+。升级之前,确保你使用的是 Ruby 2.2.2 或以上版本。
3.2 现在 Active Record 模型默认继承自 ApplicationRecord
在 Rails 4.2 中,Active Record 模型继承自 ActiveRecord::Base
。在 Rails 5.0 中,所有模型继承自 ApplicationRecord
。
现在,ApplicationRecord
是应用中所有模型的超类,而不是 ActionController::Base
,这样结构就与 ApplicationController
一样了,因此可以在一个地方为应用中的所有模型配置行为。
从 Rails 4.2 升级到 5.0 时,要在 app/models/
目录中创建 application_record.rb
文件,写入下述内容:
class ApplicationRecord < ActiveRecord::Base self.abstract_class = true end
然后让所有模型继承它。
3.3 通过 throw(:abort)
停止回调链
在 Rails 4.2 中,如果 Active Record 和 Active Model 中的一个前置回调返回 false
,整个回调链停止。也就是说,后续前置回调不会执行,回调中的操作也不执行。
在 Rails 5.0 中,Active Record 和 Active Model 中的前置回调返回 false
时不再停止回调链。如果想停止,要调用 throw(:abort)
。
从 Rails 4.2 升级到 5.0 时,返回 false
的前置回调依然会停止回调链,但是你会收到一个弃用提醒,告诉你未来会像前文所述那样变化。
准备妥当之后,可以在 config/application.rb
文件中添加下述配置,启用新的行为(弃用消息不再显示):
ActiveSupport.halt_callback_chains_on_return_false = false
注意,这个选项不影响 Active Support 回调,因为不管返回什么值,这种回调链都不停止。
详情参见 #17227 工单。
3.4 现在 ActiveJob 默认继承自 ApplicationJob
在 Rails 4.2 中,Active Job 类继承自 ActiveJob::Base
。在 Rails 5.0 中,这一行为变了,现在继承自 ApplicationJob
。
从 Rails 4.2 升级到 5.0 时,要在 app/jobs/
目录中创建 application_job.rb
文件,写入下述内容:
class ApplicationJob < ActiveJob::Base end
然后让所有作业类继承它。
详情参见 #19034 工单。
3.5 Rails 控制器测试
3.5.1 某些辅助方法提取到 rails-controller-testing
中了
assigns
和 assert_template
提取到 rails-controller-testing
gem 中了。如果想继续在控制器测试中使用这两个方法,把 gem 'rails-controller-testing'
添加到 Gemfile
中。
如果使用 RSpec 做测试,还要做些配置,详情参见这个 gem 的文档。
3.5.2 上传文件的新行为
如果在测试中使用 ActionDispatch::Http::UploadedFile
上传文件,要换成类似的 Rack::Test::UploadedFile
类。
详情参见 #26404 工单。
3.6 在生产环境启动后不再自动加载
现在,在生产环境启动后默认不再自动加载。
及早加载发生在应用的启动过程中,因此顶层常量不受影响,依然能自动加载,无需引入相应的文件。
层级较深的常量与常规的代码定义体一样,只在运行时执行,因此也不受影响,因为定义它们的文件在启动过程中及早加载了。
针对这一变化,大多数应用都无需改动。在少有的情况下,如果生产环境需要自动加载,把 Rails.application.config.enable_dependency_loading
设为 true
。
3.7 XML 序列化
ActiveModel::Serializers::Xml
从 Rails 中提取出来,变成 activemodel-serializers-xml
gem 了。如果想继续在应用中使用 XML 序列化,把 gem 'activemodel-serializers-xml'
添加到 Gemfile
中。
3.8 不再支持旧的 mysql
数据库适配器
Rails 5 不再支持旧的 mysql
数据库适配器。多数用户应该换用 mysql2
。找到维护人员之后,会作为一个单独的 gem 发布。
3.9 不再支持 debugger
Rails 5 要求的 Ruby 2.2 不支持 debugger
。换用 byebug
。
3.10 使用 bin/rails 运行任务和测试
Rails 5 支持使用 bin/rails
运行任务和测试。一般来说,还有相应的 rake 任务,但有些完全移过来了。
新的测试运行程序使用 bin/rails test
运行。
rake dev:cache
现在变成了 rails dev:cache
。
执行 bin/rails
命令查看所有可用的命令。
3.11 ActionController::Parameters
不再继承自 HashWithIndifferentAccess
现在,应用中的 params
不再返回散列。如果已经在参数上调用了 permit
,无需做任何修改。如果使用 slice
及其他需要读取散列的方法,而不管是否调用了 permitted?
,需要更新应用,首先调用 permit
,然后转换成散列。
params.permit([:proceed_to, :return_to]).to_h
3.12 protect_from_forgery
的选项现在默认为 prepend: false
protect_from_forgery
的选项现在默认为 prepend: false
,这意味着,在应用中调用 protect_from_forgery
时,会插入回调链。如果始终想让 protect_from_forgery
先运行,应该修改应用,使用 protect_from_forgery prepend: true
。
3.13 默认的模板处理程序现在是 raw
文件扩展名中没有模板处理程序的,现在使用 raw 处理程序。以前,Rails 使用 ERB 模板处理程序渲染这种文件。
如果不想让 raw 处理程序处理文件,应该添加文件扩展名,让相应的模板处理程序解析。
3.14 为模板依赖添加通配符匹配
现在可以使用通配符匹配模板依赖。例如,如果像下面这样定义模板:
<% # Template Dependency: recordings/threads/events/subscribers_changed %> <% # Template Dependency: recordings/threads/events/completed %> <% # Template Dependency: recordings/threads/events/uncompleted %>
现在可以使用通配符一次调用所有依赖:
<% # Template Dependency: recordings/threads/events/* %>
3.15 不再支持 protected_attributes
gem
Rails 5 不再支持 protected_attributes
gem。
3.16 不再支持 activerecord-deprecated_finders
gem
Rails 5 不再支持 activerecord-deprecated_finders
gem。
3.17 ActiveSupport::TestCase
现在默认随机运行测试
应用中的测试现在默认的运行顺序是 :random
,不再是 :sorted
。如果想改回 :sorted
,使用下述配置选项:
# config/environments/test.rb Rails.application.configure do config.active_support.test_order = :sorted end
3.18 ActionController::Live
变为一个 Concern
如果在引入控制器的模块中引入了 ActionController::Live
,还应该使用 ActiveSupport::Concern
扩展模块。或者,也可以使用 self.included
钩子在引入 StreamingSupport
之后直接把 ActionController::Live
引入控制器。
这意味着,如果应用有自己的流模块,下述代码在生产环境不可用:
# This is a work-around for streamed controllers performing authentication with Warden/Devise. # See https://github.com/plataformatec/devise/issues/2332 # Authenticating in the router is another solution as suggested in that issue class StreamingSupport include ActionController::Live # this won't work in production for Rails 5 # extend ActiveSupport::Concern # unless you uncomment this line. def process(name) super(name) rescue ArgumentError => e if e.message == 'uncaught throw :warden' throw :warden else raise e end end end
3.19 框架的新默认值
3.19.1 Active Record belongs_to_required_by_default
选项
如果关联不存在,belongs_to
现在默认触发验证错误。
这一行为可在具体的关联中使用 optional: true
选项禁用。
新应用默认自动配置这一行为。如果现有项目想使用这一特性,可以在初始化脚本中启用:
config.active_record.belongs_to_required_by_default = true
3.19.2 每个表单都有自己的 CSRF 令牌
现在,Rails 5 支持每个表单有自己的 CSRF 令牌,从而降低 JavaScript 创建的表单遭受代码注入攻击的风险。启用这个选项后,应用中的表单都有自己的 CSRF 令牌,专门针对那个表单的动作和方法。
config.action_controller.per_form_csrf_tokens = true
3.19.3 伪造保护检查源
现在,可以配置应用检查 HTTP Origin
首部和网站的源,增加一道 CSRF 防线。把下述配置选项设为 true
:
config.action_controller.forgery_protection_origin_check = true
3.19.4 允许配置 Action Mailer 队列的名称
默认的邮件程序队列名为 mailers
。这个配置选项允许你全局修改队列名称。在配置文件中添加下述内容:
config.action_mailer.deliver_later_queue_name = :new_queue_name
3.19.5 Action Mailer 视图支持片段缓存
在配置文件中设定 config.action_mailer.perform_caching
选项,决定是否让 Action Mailer 视图支持缓存。
config.action_mailer.perform_caching = true
3.19.6 配置 db:structure:dump
的输出
如果使用 schema_search_path
或者其他 PostgreSQL 扩展,可以控制如何转储数据库模式。设为 :all
生成全部转储,设为 :schema_search_path
从模式搜索路径中生成转储。
config.active_record.dump_schemas = :all
3.19.7 配置 SSL 选项为子域名启用 HSTS
在配置文件中设定下述选项,为子域名启用 HSTS:
config.ssl_options = { hsts: { subdomains: true } }
3.19.8 保留接收者的时区
使用 Ruby 2.4 时,调用 to_time
时可以保留接收者的时区:
ActiveSupport.to_time_preserves_timezone = false
4 从 Rails 4.1 升级到 4.2
4.1 Web Console
首先,把 gem 'web-console', '~> 2.0'
添加到 Gemfile
的 :development
组里(升级时不含这个 gem),然后执行 bundle install
命令。安装好之后,可以在任何想使用 Web Console 的视图里调用辅助方法 <%= console %>
。开发环境的错误页面中也有 Web Console。
4.2 responders
gem
respond_with
实例方法和 respond_to
类方法已经提取到 responders
gem 中。如果想使用这两个方法,只需把 gem 'responders', '~> 2.0'
添加到 Gemfile
中。如果依赖中没有 responders
gem,无法调用二者。
# app/controllers/users_controller.rb class UsersController < ApplicationController respond_to :html, :json def show @user = User.find(params[:id]) respond_with @user end end
respond_to
实例方法不受影响,无需添加额外的 gem:
# app/controllers/users_controller.rb class UsersController < ApplicationController def show @user = User.find(params[:id]) respond_to do |format| format.html format.json { render json: @user } end end end
详情参见 #16526 工单。
4.3 事务回调中的错误处理
目前,Active Record 压制 after_rollback
或 after_commit
回调抛出的错误,只将其输出到日志里。在下一版中,这些错误不再得到压制,而像其他 Active Record 回调一样正常冒泡。
你定义的 after_rollback
或 after_commit
回调会收到一个弃用提醒,说明这一变化。如果你做好了迎接新行为的准备,可以在 config/application.rb
文件中添加下述配置,不再发出弃用提醒:
config.active_record.raise_in_transactional_callbacks = true
4.4 测试用例的运行顺序
在 Rails 5.0 中,测试用例将默认以随机顺序运行。为了抢先使用这一个改变,Rails 4.2 引入了一个新配置选项,即 active_support.test_order
,用于指定测试的运行顺序。你可以将其设为 :sorted
,继续使用目前的行为,或者设为 :random
,使用未来的行为。
如果不为这个选项设定一个值,Rails 会发出弃用提醒。如果不想看到弃用提醒,在测试环境的配置文件中添加下面这行:
# config/environments/test.rb Rails.application.configure do config.active_support.test_order = :sorted # 如果愿意,也可以设为 `:random` end
4.5 序列化的属性
使用定制的编码器时(如 serialize :metadata, JSON
),如果把 nil
赋值给序列化的属性,存入数据库中的值是 NULL
,而不是通过编码器传递的 nil
值(例如,使用 JSON
编码器时的 "null"
)。
4.6 生产环境的日志等级
Rails 5 将把生产环境的默认日志等级改为 :debug
(以前是 :info
)。若想继续使用目前的默认值,在 production.rb
文件中添加下面这行:
# Set to `:info` to match the current default, or set to `:debug` to opt-into # the future default. config.log_level = :info
4.7 在 Rails 模板中使用 after_bundle
如果你的 Rails 模板把所有文件纳入版本控制,无法添加生成的 binstubs,因为模板在 Bundler 之前执行:
# template.rb generate(:scaffold, "person name:string") route "root to: 'people#index'" rake("db:migrate") git :init git add: "." git commit: %Q{ -m 'Initial commit' }
现在,你可以把 git
调用放在 after_bundle
块中,在生成 binstubs 之后执行:
# template.rb generate(:scaffold, "person name:string") route "root to: 'people#index'" rake("db:migrate") after_bundle do git :init git add: "." git commit: %Q{ -m 'Initial commit' } end
4.8 rails-html-sanitizer
现在,净化应用中的 HTML 片段有了新的选择。古老的 html-scanner 方式正式弃用,换成了 rails-html-sanitizer。
因此,sanitize
、sanitize_css
、strip_tags
和 strip_links
等方法现在有了新的实现方式。
新的净化程序内部使用 Loofah,而它使用 Nokogiri。Nokogiri 包装了使用 C 和 Java 编写的 XML 解析器,因此不管使用哪个 Ruby 版本,净化的过程应该都很快。
新版本更新了 sanitize
,它接受一个 Loofah::Scrubber
对象,提供强有力的清洗功能。清洗程序的示例参见这里。
此外,还添加了两个新清洗程序:PermitScrubber
和 TargetScrubber
。详情参阅 rails-html-sanitizer
gem 的自述文件。
PermitScrubber
和 TargetScrubber
的文档说明了如何完全控制何时以及如何剔除元素。
如果应用想使用旧的净化程序,把 rails-deprecated_sanitizer
添加到 Gemfile
中:
gem 'rails-deprecated_sanitizer'
4.9 Rails DOM 测试
TagAssertions
模块(包含 assert_tag
等方法)已经弃用,换成了 SelectorAssertions
模块的 assert_select
方法。新的方法提取到 rails-dom-testing
gem 中了。
4.10 遮蔽真伪令牌
为了防范 SSL 攻击,form_authenticity_token
现在做了遮蔽,每次请求都不同。因此,验证令牌时先解除遮蔽,然后再解密。所以,验证非 Rails 表单发送的,而且依赖静态会话 CSRF 令牌的请求时,要考虑这一点。
4.11 Action Mailer
以前,在邮件程序类上调用邮件程序方法会直接执行相应的实例方法。引入 Active Job 和 #deliver_later
之后,情况变了。在 Rails 4.2 中,实例方法延后到调用 deliver_now
或 deliver_later
时才执行。例如:
class Notifier < ActionMailer::Base def notify(user, ...) puts "Called" mail(to: user.email, ...) end end mail = Notifier.notify(user, ...) # 此时 Notifier#notify 还未执行 mail = mail.deliver_now # 打印“Called”
对大多数应用来说,这不会导致明显的差别。然而,如果非邮件程序方法要同步执行,而以前依靠同步代理行为的话,应该将其定义为邮件程序类的类方法:
class Notifier < ActionMailer::Base def self.broadcast_notifications(users, ...) users.each { |user| Notifier.notify(user, ...) } end end
4.12 支持外键
迁移 DSL 做了扩充,支持定义外键。如果你以前使用 foreigner gem,可以考虑把它删掉了。注意,Rails 对外键的支持没有 foreigner 全面。这意味着,不是每一个 foreigner 定义都可以完全替换成 Rails 中相应的迁移 DSL。
替换的过程如下:
- 从
Gemfile
中删除gem "foreigner"
。 - 执行
bundle install
命令。 - 执行
bin/rake db:schema:dump
命令。 - 确保
db/schema.rb
文件中包含每一个外键定义,而且有所需的选项。
5 从 Rails 4.0 升级到 4.1
5.1 保护远程 <script>
标签免受 CSRF 攻击
或者“我的测试为什么失败了!?”“我的 <script>
小部件不能用了!!!”
现在,跨站请求伪造(Cross-site request forgery,CSRF)涵盖获取 JavaScript 响应的 GET 请求。这样能防止第三方网站通过 <script>
标签引用你的 JavaScript,获取敏感数据。
因此,使用下述代码的功能测试和集成测试现在会触发 CSRF 保护:
get :index, format: :js
换成下述代码,明确测试 XmlHttpRequest
:
xhr :get, :index, format: :js
注意,站内的 <script>
标签也认为是跨源的,因此默认被阻拦。如果确实想使用 <script>
加载 JavaScript,必须在动作中明确指明跳过 CSRF 保护。
5.2 Spring
如果想使用 Spring 预加载应用,要这么做:
- 把
gem 'spring', group: :development
添加到Gemfile
中。 - 执行
bundle install
命令,安装 Spring。 - 执行
bundle exec spring binstub --all
,用 Spring 运行 binstub。
用户定义的 Rake 任务默认在开发环境中运行。如果想在其他环境中运行,查阅 Spring 的自述文件。
5.3 config/secrets.yml
若想使用新增的 secrets.yml
文件存储应用的机密信息,要这么做:
-
在
config
文件夹中创建secrets.yml
文件,写入下述内容:development: secret_key_base: test: secret_key_base: production: secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
使用
secret_token.rb
初始化脚本中的secret_key_base
设定SECRET_KEY_BASE
环境变量,供生产环境中的用户使用。此外,还可以直接复制secret_key_base
的值,把<%= ENV["SECRET_KEY_BASE"] %>
替换掉。删除
secret_token.rb
初始化脚本。运行
rake secret
任务,为开发环境和测试环境生成密钥。重启服务器。
5.4 测试辅助方法的变化
如果测试辅助方法中有调用 ActiveRecord::Migration.check_pending!
,可以将其删除了。现在,引入 rails/test_help
文件时会自动做此项检查,不过留着那一行代码也没什么危害。
5.5 cookies 序列化程序
使用 Rails 4.1 之前的版本创建的应用使用 Marshal
序列化签名和加密的 cookie 值。若想使用新的基于 JSON 的格式,创建一个初始化脚本,写入下述内容:
Rails.application.config.action_dispatch.cookies_serializer = :hybrid
这样便能平顺地从现在的 Marshal
序列化形式改成基于 JSON 的格式。
使用 :json
或 :hybrid
序列化程序时要注意,不是所有 Ruby 对象都能序列化成 JSON。例如,Date
和 Time
对象序列化成字符串,散列的键序列化成字符串。
class CookiesController < ApplicationController def set_cookie cookies.encrypted[:expiration_date] = Date.tomorrow # => Thu, 20 Mar 2014 redirect_to action: 'read_cookie' end def read_cookie cookies.encrypted[:expiration_date] # => "2014-03-20" end end
建议只在 cookie 中存储简单的数据(字符串和数字)。如果存储复杂的对象,在后续请求中读取 cookie 时要自己动手转换。
如果使用 cookie 会话存储器,session
和 flash
散列也是如此。
5.6 闪现消息结构的变化
闪现消息的键会整形成字符串,不过依然可以使用符号或字符串访问。迭代闪现消息时始终使用字符串键:
flash["string"] = "a string" flash[:symbol] = "a symbol" # Rails < 4.1 flash.keys # => ["string", :symbol] # Rails >= 4.1 flash.keys # => ["string", "symbol"]
一定要使用字符串比较闪现消息的键。
5.7 JSON 处理方式的变化
Rails 4.1 对 JSON 的处理方式做了几项修改。
5.7.1 删除 MultiJSON
MultiJSON 结束历史使命,Rails 把它删除了。
如果你的应用现在直接依赖 MultiJSON,有几种解决方法:
- 把
multi_json
gem 添加到Gemfile
中。注意,未来这种方法可能失效。 - 摒除 MultiJSON,换用
obj.to_json
和JSON.parse(str)
。
不要直接把 MultiJson.dump
和 MultiJson.load
换成 JSON.dump
和 JSON.load
。这两个 JSON gem API 的作用是序列化和反序列化任意的 Ruby 对象,一般不安全。
5.7.2 JSON gem 的兼容性
由于历史原因,Rails 有些 JSON gem 的兼容性问题。在 Rails 应用中使用 JSON.generate
和 JSON.dump
可能导致意料之外的错误。
Rails 4.1 修正了这些问题:在 JSON gem 之外提供了单独的编码器。JSON gem 的 API 现在能正常使用了,但是不能访问任何 Rails 专用的功能。例如:
class FooBar def as_json(options = nil) { foo: 'bar' } end end >> FooBar.new.to_json # => "{\"foo\":\"bar\"}" >> JSON.generate(FooBar.new, quirks_mode: true) # => "\"#<FooBar:0x007fa80a481610>\""
5.7.3 新的 JSON 编码器
Rails 4.1 重写了 JSON 编码器,充分利用了 JSON gem。对多数应用来说,这一变化没有显著影响。然而,在重写的过程中从编码器中移除了下述功能:
- 环形数据结构检测
- 对
encode_json
钩子的支持 - 把
BigDecimal
对象编码成数字而不是字符串的选项
如果你的应用依赖这些功能,可以把 activesupport-json_encoder
gem 添加到 Gemfile
中。
5.7.4 时间对象的 JSON 表述
在包含时间组件的对象(Time
、DateTime
、ActiveSupport::TimeWithZone
)上调用 #as_json
,现在返回值的默认精度是毫秒。如果想继续使用旧的行为,不含毫秒,在一个初始化脚本中设定下述选项:
ActiveSupport::JSON::Encoding.time_precision = 0
5.8 行内回调块中 return
的用法
以前,Rails 允许在行内回调块中像下面这样使用 return
:
class ReadOnlyModel < ActiveRecord::Base before_save { return false } # BAD end
这种行为一直没得到广泛支持。由于 ActiveSupport::Callbacks
内部的变化,Rails 4.1 不再允许这么做。如果在行内回调块中使用 return
,执行回调时会抛出 LocalJumpError
异常。
使用 return
的行内回调块可以重构成求取返回值:
class ReadOnlyModel < ActiveRecord::Base before_save { false } # GOOD end
如果想使用 return
,建议定义为方法:
class ReadOnlyModel < ActiveRecord::Base before_save :before_save_callback # GOOD private def before_save_callback return false end end
这一变化影响使用回调的多数地方,包括 Active Record 和 Active Model 回调,以及 Action Controller 的过滤器(如 before_action
)。
详情参见这个拉取请求。
5.9 Active Record 固件中定义的方法
Rails 4.1 在各自的上下文中处理各个固件中的 ERB,因此一个附件中定义的辅助方法,无法在另一个固件中使用。
在多个固件中使用的辅助方法应该在 test_helper.rb
文件的一个模块中定义,然后使用新的 ActiveRecord::FixtureSet.context_class
引入。
module FixtureFileHelpers def file_sha(path) Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path))) end end ActiveRecord::FixtureSet.context_class.include FixtureFileHelpers
5.10 i18n 强制检查可用的本地化
现在,Rails 4.1 默认把 i18n 的 enforce_available_locales
选项设为 true
。这意味着,传给它的所有本地化都必须在 available_locales
列表中声明。
如果想禁用这一行为(让 i18n 接受任何本地化选项),在应用的配置文件中添加下述选项:
config.i18n.enforce_available_locales = false
注意,这个选项是一项安全措施,为的是确保不把用户的输入作为本地化信息,除非这个信息之前是已知的。因此,除非有十足的原因,否则不建议禁用这个选项。
5.11 在 Relation 上调用的可变方法
Relation
不再提供可变方法,如 #map!
和 #delete_if
。如果想使用这些方法,调用 #to_a
把它转换成数组。
这样改的目的是避免奇怪的缺陷,以及防止代码意图不明。
# 现在不能这么写 Author.where(name: 'Hank Moody').compact! # 要这么写 authors = Author.where(name: 'Hank Moody').to_a authors.compact!
5.12 默认作用域的变化
默认作用域不再能够使用链式条件覆盖。
在之前的版本中,模型中的 default_scope
会被同一字段的链式条件覆盖。现在,与其他作用域一样,变成了合并。
以前:
class User < ActiveRecord::Base default_scope { where state: 'pending' } scope :active, -> { where state: 'active' } scope :inactive, -> { where state: 'inactive' } end User.all # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' User.active # SELECT "users".* FROM "users" WHERE "users"."state" = 'active' User.where(state: 'inactive') # SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
现在:
class User < ActiveRecord::Base default_scope { where state: 'pending' } scope :active, -> { where state: 'active' } scope :inactive, -> { where state: 'inactive' } end User.all # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' User.active # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'active' User.where(state: 'inactive') # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'inactive'
如果想使用以前的行为,要使用 unscoped
、unscope
、rewhere
或 except
把 default_scope
定义的条件移除。
class User < ActiveRecord::Base default_scope { where state: 'pending' } scope :active, -> { unscope(where: :state).where(state: 'active') } scope :inactive, -> { rewhere state: 'inactive' } end User.all # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' User.active # SELECT "users".* FROM "users" WHERE "users"."state" = 'active' User.inactive # SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
5.13 使用字符串渲染内容
Rails 4.1 为 render
引入了 :plain
、:html
和 :body
选项。现在,建议使用这三个选项渲染字符串内容,因为这样可以指定响应的内容类型。
-
render :plain
把内容类型设为text/plain
-
render :html
把内容类型设为text/html
-
render :body
不设定内容类型首部
从安全角度来看,如果响应主体中没有任何标记,应该使用 render :plain
,因为多数浏览器会转义响应中不安全的内容。
未来的版本会弃用 render :text
。所以,请开始使用更精准的 :plain
、:html
和 :body
选项。使用 render :text
可能有安全风险,因为发送的内容类型是 text/html
。
5.14 PostgreSQL 的 json 和 hstore 数据类型
Rails 4.1 把 json
和 hstore
列映射成键为字符串的 Ruby 散列。之前的版本使用 HashWithIndifferentAccess
。这意味着,不再支持使用符号访问。建立在 json
或 hstore
列之上的 store_accessors
也是如此。确保要始终使用字符串键。
5.15 ActiveSupport::Callbacks
明确要求使用块
现在,Rails 4.1 明确要求调用 ActiveSupport::Callbacks.set_callback
时传入一个块。之所以这样要求,是因为 4.1 版大范围重写了 ActiveSupport::Callbacks
。
# Rails 4.0 set_callback :save, :around, ->(r, &block) { stuff; result = block.call; stuff } # Rails 4.1 set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }
6 从 Rails 3.2 升级到 4.0
如果你的应用目前使用的版本低于 3.2.x,应该先升级到 3.2,再升级到 4.0。
下述说明针对升级到 Rails 4.0。
6.1 HTTP PATCH
现在,Rails 4.0 使用 PATCH
作为更新 REST 式资源(在 config/routes.rb
中声明)的主要 HTTP 动词。update
动作仍然在用,而且 PUT
请求继续交给 update
动作处理。因此,如果你只使用 REST 式路由,无需做任何修改。
resources :users
<%= form_for @user do |f| %>
class UsersController < ApplicationController def update # 无需修改,首选 PATCH,但是 PUT 依然能用 end end
然而,如果使用 form_for
更新资源,而且用的是使用 PUT
HTTP 方法的自定义路由,要做修改:
resources :users, do put :update_name, on: :member end
<%= form_for [ :update_name, @user ] do |f| %>
class UsersController < ApplicationController def update_name # 需要修改,因为 form_for 会尝试使用不存在的 PATCH 路由 end end
如果动作不在公开的 API 中,可以直接修改 HTTP 方法,把 put
路由改用 patch
。
在 Rails 4 中,针对 /users/:id
的 PUT
请求交给 update
动作处理。因此,如果 API 使用 PUT 请求,依然能用。路由器也会把针对 /users/:id
的 PATCH
请求交给 update
动作处理。
resources :users do patch :update_name, on: :member end
如果动作在公开的 API 中,不能修改所用的 HTTP 方法,此时可以修改表单,让它使用 PUT
方法:
<%= form_for [ :update_name, @user ], method: :put do |f| %>
关于 PATCH
请求,以及为什么这样改,请阅读 Rails 博客中的这篇文章。
6.1.1 关于媒体类型
PATCH
动词规范的勘误指出,PATCH
请求应该使用“diff”媒体类型。JSON Patch 就是这样的格式。虽然 Rails 原生不支持 JSON Patch,不过添加这一支持也不难:
# 在控制器中 def update respond_to do |format| format.json do # 执行局部更新 @article.update params[:article] end format.json_patch do # 执行复杂的更新 end end end # 在 config/initializers/json_patch.rb 文件中 Mime::Type.register 'application/json-patch+json', :json_patch
JSON Patch 最近才收录到 RFC 中,因此还没有多少好的 Ruby 库。Aaron Patterson 开发的 hana 是一个,但是没有支持规范最近的几项修改。
6.2 Gemfile
Rails 4.0 删除了 Gemfile
的 assets
分组。升级时,要把那一行删除。此外,还要更新应用配置(config/application.rb
):
# Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups)
6.3 vendor/plugins
Rails 4.0 不再支持从 vendor/plugins
目录中加载插件。插件应该制成 gem,添加到 Gemfile
中。如果不想制成 gem,可以移到其他位置,例如 lib/my_plugin/*
,然后添加相应的初始化脚本 config/initializers/my_plugin.rb
。
6.4 Active Record
- Rails 4.0 从 Active Record 中删除了标识映射(identity map),因为与关联有些不一致。如果你启动了这个功能,要把这个没有作用的配置删除:
config.active_record.identity_map
。 - 关联集合的
delete
方法的参数现在除了记录之外还可以使用Integer
或String
,基本与destroy
方法一样。以前,传入这样的参数时会抛出ActiveRecord::AssociationTypeMismatch
异常。从 Rails 4.0 开始,delete
在删除记录之前会自动查找指定 ID 对应的记录。 - 在 Rails 4.0 中,如果修改了列或表的名称,相关的索引也会重命名。现在无需编写迁移重命名索引了。
- Rails 4.0 把
serialized_attributes
和attr_readonly
改成只有类方法版本了。别再使用实例方法版本了,因为已经弃用。应该把实例方法版本改成类方法版本,例如把self.serialized_attributes
改成self.class.serialized_attributes
。 - 使用默认的编码器时,把
nil
赋值给序列化的属性在数据库中保存的是NULL
,而不是通过YAML ("--- \n…​\n")
传递nil
值。 - Rails 4.0 删除了
attr_accessible
和attr_protected
,换成了健壮参数(strong parameter)。平滑升级可以使用protected_attributes
gem。 - 如果不使用
protected_attributes
gem,可以把与它有关的选项都删除,例如whitelist_attributes
或mass_assignment_sanitizer
。 -
Rails 4.0 要求作用域使用可调用的对象,如 Proc 或 lambda:
scope :active, where(active: true) # 变成 scope :active, -> { where active: true }
Rails 4.0 弃用了
ActiveRecord::Fixtures
,改成了ActiveRecord::FixtureSet
。Rails 4.0 弃用了
ActiveRecord::TestCase
,改成了ActiveSupport::TestCase
。Rails 4.0 弃用了以前基于散列的查找方法 API。这意味着,不能再给查找方法传入选项了。例如,
Book.find(:all, conditions: { name: '1984' })
已经弃用,改成了Book.where(name: '1984')
。-
除了
find_by_…​
和find_by_…​!
,其他动态查找方法都弃用了。新旧变化如下:-
find_all_by_…​
变成where(…​)
-
find_last_by_…​
变成where(…​).last
-
scoped_by_…​
变成where(…​)
-
find_or_initialize_by_…​
变成find_or_initialize_by(…​)
-
find_or_create_by_…​
变成find_or_create_by(…​)
-
注意,
where(…​)
返回一个关系,而不像旧的查找方法那样返回一个数组。如果需要使用数组,调用where(…​).to_a
。等价的方法所执行的 SQL 语句可能与以前的实现不同。
如果想使用旧的查找方法,可以使用
activerecord-deprecated_finders
gem。-
Rails 4.0 修改了
has_and_belongs_to_many
关联默认的联结表名,把第二个表名中的相同前缀去掉。现有的has_and_belongs_to_many
关联,如果表名中有共用的前缀,要使用join_table
选项指定。例如:CatalogCategory < ActiveRecord::Base has_and_belongs_to_many :catalog_products, join_table: 'catalog_categories_catalog_products' end CatalogProduct < ActiveRecord::Base has_and_belongs_to_many :catalog_categories, join_table: 'catalog_categories_catalog_products' end
注意,前缀含命名空间,因此
Catalog::Category
和Catalog::Product
,或者Catalog::Category
和CatalogProduct
之间的关联也要以同样的方式修改。
6.5 Active Resource
Rails 4.0 把 Active Resource 提取出来,制成了单独的 gem。如果想继续使用这个功能,把 activeresource
gem 添加到 Gemfile
中。
6.6 Active Model
- Rails 4.0 修改了
ActiveModel::Validations::ConfirmationValidator
错误的依附方式。现在,如果二次确认验证失败,错误依附到:#{attribute}_confirmation
上,而不是attribute
。 -
Rails 4.0 把
ActiveModel::Serializers::JSON.include_root_in_json
的默认值改成false
了。现在 Active Model 序列化程序和 Active Record 对象具有相同的默认行为。这意味着,可以把config/initializers/wrap_parameters.rb
文件中的下述选项注释掉或删除:# Disable root element in JSON by default. # ActiveSupport.on_load(:active_record) do # self.include_root_in_json = false # end
6.7 Action Pack
-
Rails 4.0 引入了
ActiveSupport::KeyGenerator
,使用它生成和验证签名 cookie 等。Rails 3.x 生成的现有签名 cookie,如果有secret_token
,并且添加了secret_key_base
,会自动升级。# config/initializers/secret_token.rb Myapp::Application.config.secret_token = 'existing secret token' Myapp::Application.config.secret_key_base = 'new secret key base'
注意,完全升级到 Rails 4.x,而且确定不再降级到 Rails 3.x之后再设定
secret_key_base
。这是因为使用 Rails 4.x 中的新secret_key_base
签名的 cookie 与 Rails 3.x 不兼容。你可以留着secret_token
,不设定新的secret_key_base
,把弃用消息忽略,等到完全升级好了再改。如果使用外部应用或 JavaScript 读取 Rails 应用的签名会话 cookie(或一般的签名 cookie),解耦之后才应该设定
secret_key_base
。 -
如果设定了
secret_key_base
,Rails 4.0 会加密基于 cookie 的会话内容。Rails 3.x 签名基于 cookie 的会话,但是不加密。签名的 cookie 是“安全的”,因为会确认是不是由应用生成的,无法篡改。然而,终端用户能看到内容,而加密后则无法查看,而且性能没有重大损失。改成加密会话 cookie 的详情参见 #9978 拉取请求。
Rails 4.0 删除了
ActionController::Base.asset_path
选项,改用 Asset Pipeline 功能。Rails 4.0 弃用了
ActionController::Base.page_cache_extension
选项,换成ActionController::Base.default_static_extension
。Rails 4.0 从 Action Pack 中删除了动作和页面缓存。如果想在控制器中使用
caches_action
,要添加actionpack-action_caching
gem,想使用caches_page
,要添加actionpack-page_caching
gem。Rails 4.0 删除了 XML 参数解析器。若想使用,要添加
actionpack-xml_parser
gem。Rails 4.0 修改了默认的
layout
查找集,使用返回nil
的符号或 proc。如果不想使用布局,返回false
。Rails 4.0 把默认的 memcached 客户端由
memcache-client
改成了dalli
。若想升级,只需把gem 'dalli'
添加到Gemfile
中。Rails 4.0 弃用了控制器中的
dom_id
和dom_class
方法(在视图中可以继续使用)。若想使用,要引入ActionView::RecordIdentifier
模块。Rails 4.0 弃用了
link_to
辅助方法的:confirm
选项。现在应该使用data
属性(如data: { confirm: 'Are you sure?' }
)。基于这个辅助方法的辅助方法(如link_to_if
或link_to_unless
)也受影响。Rails 4.0 改变了
assert_generates
、assert_recognizes
和assert_routing
的工作方式。现在,这三个断言抛出Assertion
,而不是ActionController::RoutingError
。-
如果具名路由的名称有冲突,Rails 4.0 抛出
ArgumentError
。自己定义具名路由,或者由resources
生成都可能触发这一错误。下面两例中的example_path
路由有冲突:get 'one' => 'test#example', as: :example get 'two' => 'test#example', as: :example resources :examples get 'clashing/:id' => 'test#example', as: :example
在第一例中,可以为两个路由起不同的名称。在第二例中,可以使用
resources
方法提供的only
或except
选项,限制生成的路由。详情参见限制所创建的路由。 -
Rails 4.0 还改变了含有 Unicode 字符的路由的处理方式。现在,可以直接在路由中使用 Unicode 字符。如果以前这样做过,要做修改。例如:
get Rack::Utils.escape('こんにちは'), controller: 'welcome', action: 'index'
要改成:
get 'こんにちは', controller: 'welcome', action: 'index'
-
Rails 4.0 要求使用
match
定义的路由必须指定请求方法。例如:# Rails 3.x match '/' => 'root#index' # 改成 match '/' => 'root#index', via: :get # 或 get '/' => 'root#index'
-
Rails 4.0 删除了
ActionDispatch::BestStandardsSupport
中间件。根据这篇文章,<!DOCTYPE html>
就能触发标准模式。此外,ChromeFrame 首部移到config.action_dispatch.default_headers
中了。注意,还必须把对这个中间件的引用从应用的代码中删除,例如:
# 抛出异常 config.middleware.insert_before(Rack::Lock, ActionDispatch::BestStandardsSupport)
此外,还要把环境配置中的
config.action_dispatch.best_standards_support
选项删除(如果有的话)。 在 Rails 4.0 中,预先编译好的静态资源不再自动从
vendor/assets
和lib/assets
中复制 JS 和 CSS 之外的静态文件。Rails 应用和引擎开发者应该把静态资源文件放在app/assets
目录中,或者配置config.assets.precompile
选项。在 Rails 4.0 中,如果动作无法处理请求的格式,抛出
ActionController::UnknownFormat
异常。默认情况下,这个异常的处理方式是返回“406 Not Acceptable”响应,不过现在可以覆盖。在 Rails 3 中始终返回“406 Not Acceptable”响应,不可覆盖。在 Rails 4.0 中,如果
ParamsParser
无法解析请求参数,抛出ActionDispatch::ParamsParser::ParseError
异常。你应该捕获这个异常,而不是具体的异常,如MultiJson::DecodeError
。在 Rails 4.0 中,如果挂载引擎的 URL 有前缀,
SCRIPT_NAME
能正确嵌套。现在不用设定default_url_options[:script_name]
选项覆盖 URL 前缀了。Rails 4.0 弃用了
ActionController::Integration
,改成了ActionDispatch::Integration
。Rails 4.0 弃用了
ActionController::IntegrationTest
,改成了ActionDispatch::IntegrationTest
。Rails 4.0 弃用了
ActionController::PerformanceTest
,改成了ActionDispatch::PerformanceTest
。Rails 4.0 弃用了
ActionController::AbstractRequest
,改成了ActionDispatch::Request
。Rails 4.0 弃用了
ActionController::Request
,改成了ActionDispatch::Request
。Rails 4.0 弃用了
ActionController::AbstractResponse
,改成了ActionDispatch::Response
。Rails 4.0 弃用了
ActionController::Response
,改成了ActionDispatch::Response
。Rails 4.0 弃用了
ActionController::Routing
,改成了ActionDispatch::Routing
。
6.8 Active Support
Rails 4.0 删除了 ERB::Util#json_escape
的别名 j
,因为已经把它用作 ActionView::Helpers::JavaScriptHelper#escape_javascript
的别名。
6.9 辅助方法的加载顺序
Rails 4.0 改变了从不同目录中加载辅助方法的顺序。以前,先找到所有目录,然后按字母表顺序排序。升级到 Rails 4.0 之后,辅助方法的目录顺序依旧,只在各自的目录中按字母表顺序加载。如果没有使用 helpers_path
参数,这一变化只影响从引擎中加载辅助方法的方式。如果看重顺序,升级后应该检查辅助方法是否可用。如果想修改加载引擎的顺序,可以使用 config.railties_order=
方法。
6.10 Active Record 观测器和 Action Controller 清洁器
ActiveRecord::Observer
和 ActionController::Caching::Sweeper
提取到 rails-observers
gem 中了。如果要使用它们,要添加 rails-observers
gem。
6.11 sprockets-rails
-
assets:precompile:primary
和assets:precompile:all
删除了。改用assets:precompile
。 -
config.assets.compress
选项要改成config.assets.js_compressor
,例如:config.assets.js_compressor = :uglifier
6.12 sass-rails
-
asset-url
不再接受两个参数。例如,asset-url("rails.png", image)
变成了asset-url("rails.png")
。
英语原文还有从 Rails 3.0 升级到 3.1 及从 3.1 升级到 3.2 的说明,由于版本太旧,不再翻译,敬请谅解。——译者注
反馈
我们鼓励您帮助提高本指南的质量。
如果看到如何错字或错误,请反馈给我们。 您可以阅读我们的文档贡献指南。
您还可能会发现内容不完整或不是最新版本。 请添加缺失文档到 master 分支。请先确认 Edge Guides 是否已经修复。 关于用语约定,请查看Ruby on Rails 指南指导。
无论什么原因,如果你发现了问题但无法修补它,请创建 issue。
最后,欢迎到 rubyonrails-docs 邮件列表参与任何有关 Ruby on Rails 文档的讨论。