上礼拜工作太无聊了,但是为了完成公司的一些Performance的Goal,所以就尝试把我们在维护的一个已经有相当年龄体态臃肿的大项目升级到Ruby 1.9.3,看看这样一次升级大约需要花费多少工作量。
首先升级Gemfile的时候,有三个Gem就不再支持Ruby 1.9,他们是rcovsystem_timerfastercsv。其中rcov在Ruby 1.9中的替代品是simplecovsystem_timer本来就是由于Fix 1.8中的一个问题,1.9中就不再需要了而fastercsv则因为Ruby 1.9自带csv库而主动退役了。升级到1.9之后,原先rcov和fastercsv的代码自然就要重写了。
接着执行
find . -name '*.rb' -exec ruby -c {} \; | grep -v 'Syntax OK'
命令,可以立即扫描出项目中不兼容Ruby 1.9语法的代码的位置,1.8和1.9主要有下列几个不兼容的部分:
1. case when在1.8中可以用冒号分割条件和值,例如`when condition: value`,而1.9中只能通过回车,分号或者then来分割,与此类似的还有while和if等。
2. 一部分原本在1.8下可以省略的括号在1.9不能省略,例如`[fn param1, param2]`这样的代码在1.9中须写成`[fn(param1, param2)]`
3. 编码问题,在1.8中我们可以直接在脚本文件中放入任何一个国家的语言而不必担心,因为Ruby 1.8完全不处理这些跨国字符,而在1.9中,如果代码中出现异国字符就必须在文件开头加入Magic Encoding信息,否则默认的ASCII编码将会发生错误。
 
当然,大部分不兼容的部分都不会在语法检查时就被发现,还必须通过更多的操作去发现:
1. YAML引擎变更,1.9后默认的YAML引擎是psych,我们项目中很多自动生成的YAML之前是用1.8默认引擎Syck,换了1.9之后无法解析,随后我就修改了config/boot.rb把YAML引擎重新改回了psych。这样还不够,由于psych版本上的不同,之前我们手动编写的config/database.yml文件(由于用了些比较高端的语法)也必须做些修改才能正常使用(我在牺牲掉了两个开发数据库所有数据之后才发现了这点)。很奇怪的是,想要完美替换YAML引擎并非这么容易,对psych的默认支持的代码存在于多个地方,最后我的代码是这个样子的:
begin
  require 'syck'
rescue LoadError
end
require 'yaml'
YAML::ENGINE.yamler = 'syck' if defined?(YAML::ENGINE)
ENV['TEST_SYCK'] = 'true'
可以看到,相当麻烦。
2. require不再支持当前目录,这个我相信大家都知道。最佳解决方案就是将路径换成绝对路径即可(如果还希望兼容1.8的话),我们项目中有数百个地方因此需要替换,我在Vim写了个正则表达式把他们全部替换了。
3. 在老版本中我们会用Symbol#to_i的方法来求得一个唯一的对应值,在1.9中这个方法不复存在,可以使用Symbol#object_id的方法,虽然计算得到的值并不相同,但效果类似。
4. 我们原来的代码中用到了1.8的ParseDate库,可以将一个日期字符串转换为一个数组。1.9中这个库不复存在(可能感觉功能过于薄弱且类似功能完全可以交给Date库完成),但是要恢复它并不困难,只要阅读1.8中ParseDate库的源代码,就可以发现其实ParseDate.parsedate方法其实只是简单调用了Date._parse这一内部方法而已,而这一内部方法依然存在,所以我们可以很容易地将它恢复回去。
5. ftools这个库没有了,它的方法被分散在了File和FileUtils这两个库中实现,直接替换即可。
6. Date#emit_sn这个方法没有了,其实这是个私有方法,被剔除完全没有任何问题。只是由于我们的项目中在扩展Date类功能的时候多次调用了这一方法,并且这个方法由于缺乏文档又没有给出任何替代品因此难以重写。解决方案仍然是阅读1.8版本的源代码,将其实现拷贝过来(其实这不是一个好办法,但是却可以保证完美的兼容)。
7. `Time.now`返回值的字符串版本的格式发生了变化,1.8中格式类似于"Tue Aug 27 22:14:05 +0800 2013"这样的美式写法,而1.9中则是"2013-08-27 22:14:52 +0800"这样的本地写法。
8. 在Ruby 1.8中,所有Object均拥有to_a方法,而在1.9中,to_a方法被从Object类中移除,解决方案是调用Array方法。
9. 同样关于Array方法,在1.8中,将String转换成数组时会将String按照回车分割成多个元素放入数组,而1.9中则始终创建只有一个元素的数组,这是因为在1.8中字符串默认返回的迭代器就是按行遍历数组的,而1.9中字符串不再拥有默认的迭代器,因此只能采用默认行为,将字符串包装成数组返回了。解决方案是调用数组的`each_line.to_a`方法,将得到一致的效果。这个区别导致Rails 2.3的Cookie没法正常工作,因为Cookie字符串在Rails中默认就是用回车分割的。
10. String#[]在1.8中返回一个整数,而在1.9中却返回一个字符。对此我们统一用chr方法将数字转换成字符(在1.9中如果已经是字符,chr就会直接返回)
11. String#length在1.8中相当于1.9的String#bytesize,而1.9的String#length将会返回正确的字符的数量
12. Array#choice更名为Array#sample
13. 在Ruby 1.8中Date有个new系列方法,即new0,new1,new2,new!,均与不同的new方法不同,其中new!和new0相同,并且我们代码里用到了这个方法。在1.9中统统没有了,连替代品都没有。我最后找到了https://developer.uservoice.com/blog/2012/03/04/how-to-upgrade-a-rails-2-3-app-to-ruby-1-9-3/这张页面,里面有关于new!的简易实现。
14. Date#exist?更名为Date#valid_date?,后者在1.8中其实也有。
15. Array#to_s在1.8中其行为几乎等同于不加参数的join,也就是元素直接拼合成字符串,而在1.9中则会用逗号分割并且用方括号包围。应对方法就是不再调用Array#to_s,这个问题还是挺难检查出来的。
16. 在1.8中你可以在遍历hash时为当前hash增加元素,而可能因为改为链表实现的缘故,1.9中不能再这样做了,否则将直接抛掷异常。不过数组还依然是可以的。
17. Rails为1.8增加了一个处理多字节字符的模块ActiveSupport::Multibyte,可以实现一些比较高级的功能。但Rails认为Ruby 1.9的字符串天然支持多字节字符功能,因此String#mb_chars在1.9中将返回字符串本身。在我们的项目中我们有些利用Multibyte实现的特殊功能,比如将带重音字节的a字符转换成普通的英语a字符。我后来通过强制创建ActiveSupport::Multibyte对象才恢复了这一功能。
18. bigdecimal/util库中Float#to_d方法的实现不同,1.9版本中可以设置精度,默认精度是16,这造成数据库中decimal类型的column经过ActiveRecord的类型转换后返回的数据在精度上有差异。
19. RDoc::Usage被移除出了Ruby标准库,现在已经不能通过对脚本做简单注释就实现--help的功能了。并且Ruby官方没有提供任何替代品,如果要说的话,可能就是optparser库了。但是在我们项目中已经有将近100个脚本,其中大部分均使用了RDoc::Usage的办法,重写几乎是不可能的。因此我故伎重施,将1.8源代码中的与RDoc::Usage相关部分的代码提取出来,也就是rdoc目录下的usage.rb文件和markup/和ri/目录而已。
20. 由于我按照https://developer.uservoice.com/blog/2012/03/04/how-to-upgrade-a-rails-2-3-app-to-ruby-1-9-3/里的建议将Ruby 1.9的默认读写的Encoding都设置为了UTF-8,因此如果要返回二进制文件作为数据返回的话,将遭遇到`invalid byte sequence`的错误,这是因为二进制数据在render的时候会调用一次String#blank?方法,而该方法的实现就是令其与/\S/做比较,如果字符串是二进制,这一比较将发生错误,因此我修改了这一方法,在遭遇到错误后将会将字符串强制编码为Encoding::BINARY,重试后即可解决。
21. `activesupport-2.3.18/lib/active_support/core_ext/string/output_safety.rb:22: warning: regexp match /.../n against to UTF-8 string`警告,这个警告其实并不重要,它的意思是/.../n的正则表达式只匹配二进制数据。可以用silence_warnings方法忽略到这个警告或者检测版本为1.9的话就移除这个n,或者简单的将ERb替换成Erubis
22. 对于多国语言的网站,参数中存在异国文字也有可能发生编码错误,直接采用https://developer.uservoice.com/blog/2012/03/04/how-to-upgrade-a-rails-2-3-app-to-ruby-1-9-3/中的脚本即可解决这个问题。
23. 最后这个是最重要的,在Ruby 1.8的lambda中,有个众所周知的Bug,当外部存在a变量时,调用block,并且block的参数中存在同名变量时,对这一变量的赋值将导致外部变量一起被修改。我很不能理解的是,这明显是一个必然要被修复的Bug,却有人利用这一特性为外部变量赋值,这种问题甚至还出现在一些著名的Gem里。而且这个错误非常难以发现,我就有找这个错误找到睡觉在梦里继续找这个错误的痛苦经历。
 
主要的差异就这么多吧,其实升级的过程并没有非常困难,无非就是 测试-发现问题-调试代码-版本比对-解决问题 这样循环往复的过程。关键就是如何通过测试尽量去发现所有的问题,只要问题能被定位就总能解决。目前,我也只能通过跑测试用例来发现不兼容的地方,但由于我们项目测试用例其实并不健全,所以究竟有多少可能的问题就不得而知了。
 

补充

我后来又在RubyTuesday 9/21号的活动上以这个主题做了一个演讲,演讲用的Slides可以点击这里观看,这个Slides用mdpress编写,源代码请这里下载

我发现写Rails,尤其是测试部分,需要reload的次数相当频繁。比方说

user = users(:xxxxx)
xhr :put, 'change_xxxxx', :id => user.id
user.reload # necessary!
assert user.xxx, xxx
assert user.yyy, yyy

由于这里创建的user对象和被测试的action里的user对象(在内存概念上)不是同一个对象,因此,被测试的action修改了user对象在数据库里的数据,会导致外围的user信息过时,因此必须reload,否则接下来的断言语句都无法通过。

这种情况同样也有可能存在与关联对象上,比如

assert child1.father, child2.father
child1.father.name = 'new name'
child1.save
child2.reload # necessary!
assert child2.father.name, 'new name'

总是写很多reload语句显然很不舒服,为此我今天在回家的地铁上想了一种解决方案,就是在后台全局的部分,以ActiveRecord类名和实例为key简单的缓存一部分数据,这部分数据与数据库里的数据在本次request返回内一致,request结束后自动清除以免内存泄露和影响下一个request。而前面的ActiveRecord对象,本身仅存储那些还未被保存的数据。

方案大致是这样的(以Rails 2.3为例):

require 'active_record'

module ActiveRecord
  class Base
    def request_cache
      self.class.request_cache[self]
    end

    def request_cache=(v)
      self.class.request_cache[self] = v
    end
  end

  class << Base
    def request_cache
      @request_cache ||= Hash.new {|h, k| h[k] = Hash.new {|h, k| h[k] = {}}}
      @request_cache[name]
    end
  end
end

ActionController::Dispatcher.middleware.use(Class.new do
  def initializer(app)
    @app = app
  end

  def call(env)
    @app.call(env)
  ensure
    ActiveRecord::Base.request_cache.clear
  end
end)

这里我把缓存就设计在ActiveRecord的class variable内(三级Hash,分别是类-实例对象-属性名),然后用Middleware的方式去清理缓存,之所以选择用Middleware的方式是因为ActiveRecord原本和HTTP Request是不能发生关联的,否则Console和Unit test里就无法执行了,设计成Middleware则能很好的解决这一问题。

由于选择的三级Hash的设计中没有直接和数据库关联的信息,因此其实也可以用于非ActiveRecord类(唯一要做的可能仅仅是自己实现一种标示方法),毕竟当前可以用于存储的服务早已不止数据库一种了。

至于对于ActiveRecord的修改我现在对ActiveRecord的实现还不够熟悉,无法完成,以后学习这一部分了再说吧。但是大致的思路是这样的:

  • 当从数据库读入数据的时候(包括reload),将缓存里的数据翻新
  • 当保存数据进数据库的时候,将数据同样保存进缓存
  • 当从一个ActiveRecord对象读取属性的时候,现在被当前对象修改过的属性中搜索,如果搜索不到再从缓存里搜索,如果还是搜索不到就采用默认方法

大致就是这些了,其实想法还是很简单的,希望早日自己能把它变成现实。

Rails 3.2 新特性简介

2012年4月08日 00:12

好久没有写Blog了,我依稀记得当初面试的时候面试官翻阅我Blog的场景,后来我也只写过一篇Blog而已。而今天,我已经成功的进入了这家企业,成为了真正的Ruby on Rails开发者。从去年暑假刚开始学习Rails,到今天,只有半年有余。不过Rails已经从最初学习时的3.0.8升级到了3.2.3。我依然记得第一次用3.1.0的时候还在疑惑怎么一些功能与书上已经开始不一致了,那会还不知道3.1.0加了很多新特性。不过那时幸亏有GitCafe团队的成员Rainux(@RainuxLuo)带领稀里糊涂的我用上了Rails 3.1,否则我可能至今都不了解Rails的版本计划和3.1的新特性。

本文简单介绍Rails 3.2的新特性,主要参考 Ruby on Rails Guide 3.2 Release Notes。不过作为新手,这Release notes我有很多是看不懂的,幸亏后来又看了Railscasts Upgrading to Rails 3.2视频,终于理解了主要更新,至于次要更新,如果我能理解的就会提及(不能保证理解完全正确,如果有错误请务必指出),如果不能理解的就不说了,一般也不会很重要(如果读者能在评论中略微指导下,我将不胜感激)。

1. Development环境下性能改进,由于集成了Active Reload插件,Rails在Development环境下只会重新加载哪些确实被改过的类文件,在大型项目中,这个新特性将明显改进效率。

2. 使用新的Journey引擎,路由识别的性能提升。

3. ActiveRecord::Relation增加一个explain方法,用以分析SQL包括索引在内的优化信息。目前只支持SQLITE3,MySQL,PostgreSQL三种Adapter。在Development环境下,config/environments/development.rb增加了个新选项config.active_record.auto_explain_threshold_in_seconds,默认值0.5,意思是当一条SQL语句运行时间超过0.5秒时将自动explain并且记入Log,这个新特性将帮助开发者留意那些效率极低的SQL语句。但是如果存在些不可避免的超过0.5秒的SQL语句,你不希望再看到Rails将它记入日志,将这句语句包含在

ActiveRecord::Base.silence_auto_explain do
  # no automatic EXPLAIN here
end

中,Rails将不会自动explain这其中的SQL。

4. Tagged Logging可以方便在多用户多IP访问应用的情况下观看Log。可以在config/environments/development.rb中增加config.log_tags选项,例如

config.log_tags = [:uuid, :remote_ip]

Log将记录例如

Started GET "/users/1" for 10.10.10.10 at 2012-04-07 22:15:35 +0800
[952ab51671f8d31f14069f6a372bb1f5] [10.10.10.10] Processing by UsersController#show as HTML
[952ab51671f8d31f14069f6a372bb1f5] [10.10.10.10]   Parameters: {"id"=>"1"}
[952ab51671f8d31f14069f6a372bb1f5] [10.10.10.10]   User Load (0.7ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1  [["id", "1"]]
[952ab51671f8d31f14069f6a372bb1f5] [10.10.10.10]   Rendered users/show.html.erb within layouts/application (13.1ms)
[952ab51671f8d31f14069f6a372bb1f5] [10.10.10.10] Completed 200 OK in 288ms (Views: 139.8ms | ActiveRecord: 7.6ms)
[d1a1f1ebb6c56d0fc3f09d592ad4c36c] [10.10.10.10]

这样的信息,可以看到,每行之前都有一个UUID和请求的IP地址。其中UUID是Rails根据HTTP Request生成的独一无二的ID。通过这个功能,我们能够把Log中相同的HTTP Request和相同的请求IP地址取分开来。

5. ~/.railsrc现在可以直接写入rails new命令的默认参数。如果你像我一样,习惯创建没有Test Case,不自动运行bundle install命令的Rails项目,你可以运行

echo -T --skip-bundle > ~/.railsrc

这样以后-T --skip-bundle将作为rails new命令的默认参数。

6. rails命令现在接受d命令,等同于destroy命令,方便像我这样的newbie理解。之前我也确实困惑过,为什么generate可以缩写为g,而destroy命令不能缩写为d呢?现在这样就好多了。

7. rails generate的 model / migration / scaffold 命令生成Model属性的时候可以这样写

rails g scaffold Post title:string:index subtitle author:uniq 'price:decimal{7,2}'

可以看到,原来每个属性只能写两列,而现在可以只写一列或是三列。一列表示默认类型是String,三列中第三列可以使用的modifier有index,表示本列添加index;uniq,表示本列添加unique index。如果类型是数值型,还可以加上{x,y}这样的格式表示数据库中数值的精度和小数的精度(留意price:decimal{7,2}左右的引号,shell中{}有特殊含义,因此可以用引号来保持原意)。

8. rails generate plugin命令被移除,请使用rails plugin new替代。

9. 移除config.paths.app.controller,请使用config.paths['app/controller']替代。

10. Rails::Plugin类过时,并将在Rails 4.0中被彻底移除。

11. 如果你在ApplicationController中指定了layout,同时使用了:only或:expect过滤。那么如果过滤条件失败,Rails现在将使用默认的layout。

12. ActionController::TestCase现在支持用cookies直接修改或清除cookie。而在此之前则不得不使用HTTP_COOKIE或CookieJar这样的方法。

13. send_file方法现在可以猜测MIME类型,如果:type没有提供的话。

14. MIME添加了包括PDF和ZIP在内的几种类型

15. 当Controller的父类显式指定过layout的情况下,子类不再按照约定的方法查询layout。

class ApplicationController
  layout "application"
end
 
class PostsController < ApplicationController
end

比如上述例子,PostsController将不会试图去寻找一个posts的layout。如果你想恢复原来的功能,删除ApplicationController中的layout语句或是显式指定PostsController的layout为nil。

16. ActionController::UnknownAction过时,请用AbstractController::ActionNotFound替代。

17. ActionController::DoubleRenderError过时,请用AbstractController::DoubleRenderError替代。

18. ActionController#rescue_action,ActionController#initialize_template_class和ActionController#assign_shortcuts过时。

19. ActionView::Helpers::FormBuilder支持button_tag方法,默认行为等同于submit_tag

<%= form_for @post do |f| %>
  <%= f.button %>
<% end %>

20. Date helpers支持:use_two_digit_numbers选项,设置为true表示日期的月和日都有两个数字构成,如果小于零则之前补零。

21. form_for方法支持:namespace选项,作为form的id的前缀,确保form的id的唯一性。

22. 限制select_year方法生成的year条目的最大数量为1000,可以通过:max_years_allowed选项设置上限。

23. content_tag_for和div_for方法支持直接传入ActiveRecord对象的集合。例如

@items.each do |item|
  content_tag_for(:li, item) do
     Title: <%= item.title %>
  end
end

现在可以被写为

content_tag_for(:li, @items) do |item|
  Title: <%= item.title %>
end

24. 由timestamps创建的created_at和updated_at字段现在默认为not null。

25. 可以使用ActiveRecord::Base.store功能添加ActiveRecord的Key-Value存储器,例如

class User < ActiveRecord::Base
  store :settings, accessors: [ :color, :homepage ]
end
 
u = User.create(color: 'black', homepage: '37signals.com')
u.color                          # Accessor stored attribute
u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor

如果要让上述代码能够成功运行,必须确保users表有settings字段,类型为text。运行

u = User.create(color: 'black', homepage: '37signals.com')

时,将color和homepage的属性以json的形式存储在users表的settings字段中。注意,如果User自身也有color或homepage字段,这些字段将不被使用。我们可以通过u.color或是u.settings[:color]的方法访问被存储的color字段。试图给settings添加没有被accessors指定的属性也是可以的,唯一的区别就是,将不会在ActiveRecord的实例下添加这个属性的getter和setter。所以上述代码中的u.settings[:country] = 'Denmark'可以正常运行,u.settings[:country]也确实可以取到被存储的值,并且这个值确实在数据库中被持久化。但是不能使用u.country来访问这个值。

26. ActiveRecord::Relation添加pluck,传入这个ActiveRecord的一个字段,将返回包含所有Relation中存储的Record的这个字段的值的数组。

> User.where('id <= 3').pluck(:name)
=> ["bachue", "deltamaster", "anne"]

27. ActiveRecord::Relation添加uniq方法,请看示例

> Client.select('DISTINCT name')
=> [#<User name: "bachue">, #<User name: "bachue">, #<User name: "bachue">]
> User.select(:name).uniq
=> [#<User name: "bachue">]
> User.select(:name).uniq.uniq(false)
=> [#<User name: "bachue">, #<User name: "bachue">, #<User name: "bachue">]

28. :class_name选项现在可以传入Symbol,之前传入Symbol会出错,只能传入类名的字符串。//也曾让我这个newbie confusing了好一阵。

29. 在Development环境下,db:drop命令现在也会drop掉test数据库,和db:create会创建test数据库相对应。

30. 大小写不敏感的uniqueness检查时,如果MySQL的column中已经使用了大小写不敏感的校验,那么Adapter将不会再调用MySQL的LOWER函数来进行这个检查。

31. ActiveRecord::Relation新增first_or_create,first_or_create!和first_or_initialize方法,当Relation没有找到搜索结果时,将创建同时包含Relation的参数和first_or_create的参数的记录,例如

> User.create name: 'bachue', age: 22
=> #<User id: 1, name: "bachue", age: 22>
> User.where(name: 'bachue').first_or_create!(age: 16)
=> #<User id: 1, name: "bachue", age: 22>
# No new record is created
> User.count
=> 1
> User.where(name: 'anne').first_or_create!(age: 16)
=> #<User id: 2, name: "anne", age: 16>
# New record is created
> User.count
=> 2

32. 在Development环境下,config/environments/development.rb增加了新语句

config.active_record.mass_assignment_sanitizer = :strict

功能是当违反mass assignment protection的时候,Rails将抛出ActiveModel::MassAssignmentSecurity::Error异常来阻止这一赋值,而此前仅仅是给出一个warning而并不阻止。

//感觉可以考虑把这句话移到全局的配置文件,阻止Production环境上有人试图用mass assignment攻击系统。

33.  现在将不会自动关闭Thread的数据库连接,例如

Thread.new { Post.find(1) }.join

你现在要在Thread闭包结束时显式关闭连接,例如

Thread.new {
  Post.find(1)
  Post.connection.close
}.join

如果之前你在项目中使用线程的话现在要注意了 //不知道什么情况!Release notes中就这么写的,为啥要修改成这样啊!此前几乎从没有关注过Rails的数据库连接机制好不好!

34. set_table_name,set_inheritance_column,set_sequence_name,set_primary_key,set_locking_column方法过时了,使用setter替代。例如以前写的

class Project < ActiveRecord::Base
  set_table_name "project"
end

现在将被写成

class Project < ActiveRecord::Base
  self.table_name = "project"
end

这种方法的好处在于,你可以使self.table_name变成方法,例如

class Post < ActiveRecord::Base
  def self.table_name
    "special_" + super
  end
end
 
Post.table_name # => "special_posts"

//大爱ruby的local variable和method可以相互替换的特点。也由此可见在ruby中写set_xxxx是没有前途的。

35. ActiveModel::AttributeMethods的define_attr_method方法过时,这方法本来就是用于支持类似于set_table_name方法的,但现在它们都过时了。

36. 新增ActiveSupport:TaggedLogging类,用来包装任何标准的Logger类以提供tagging功能,请看示例

Logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
 
Logger.tagged("BCX") { Logger.info "Stuff" }
# Logs "[BCX] Stuff"
 
Logger.tagged("BCX", "Jason") { Logger.info "Stuff" }
# Logs "[BCX] [Jason] Stuff"
 
Logger.tagged("BCX") { Logger.tagged("Jason") { Logger.info "Stuff" } }
# Logs "[BCX] [Jason] Stuff"

37. 在Date,Time和DateTime类中的beginning_of_week方法现在可以接受一个参数,代表一周的哪一天属于一周的第一天,请看示例

> Date.today.beginning_of_week :monday
=> Mon, 02 Apr 2012 
> Date.today.beginning_of_week :sunday
=> Sun, 08 Apr 2012 
> Date.today.beginning_of_week :saturday
=> Sat, 07 Apr 2012 
> Date.today.beginning_of_week :wednesday
=> Wed, 04 Apr 2012

38.  String类添加safe_constantize方法,用以将字符串转换成同名的Class对象。和constantize方法的区别在于如果无法找到这个Class对象,将返回nil而不是抛掷异常。例如

> 'User'.safe_constantize
=> User(id: integer, name: string, age: integer, created_at: datetime, updated_at: datetime) 
> 'User1'.safe_constantize
=> nil 
> 'User'.constantize
=> User(id: integer, name: string, age: integer, created_at: datetime, updated_at: datetime) 
> 'User1'.constantize
NameError: uninitialized constant User1

39.  增加Array#prepend方法,alias Array#unshift。增加Array#append方法,alias Array#<<。//之前想找给Array push元素的方法,除了<<就找不到了,现在好了。

40.  Time新增all_day,all_week,all_quarter,all_year方法可以生成相应的Range对象,例如:

> Time.now.all_week
=> 2012-04-02 00:00:00 +0800..2012-04-08 23:59:59 +0800

//但是前面才刚提到关于week添加一个选项设定一周的那一天是这周的第一天,这里却又看不到了。

41. 新增ActiveSupport::Cache::NullStore用于Development环境和Testing环境。

42. 移除ActiveSupport::SecureRandom,请使用标准库中的SecureRandom替代。

43. ActiveSupport::Base64过时,请使用::Base64替代。

44. ActiveSupport::Memoizable过时,请使用Ruby的memoization pattern替代。

45. Module#synchronize过时,没有替代方案,请使用ruby标准库的monitor。

46. ActiveSupport::MessageEncryptor#encrypt和ActiveSupport::MessageEncryptor#decrypt方法过时。

47. ActiveSupport::BufferedLogger#silence,如果你不想记录当前语句块的Log,请修改它的Log Level。

48. ActiveSupport::BufferedLogger给你的Log自动创建目录的行为过时,请自行创建。

49. ActiveSupport::BufferedLogger#auto_flushing过时,请像这样设定日志文件的sync level。或是优化你的文件系统。FS cache现在控制着flushing。

f = File.open('foo.log', 'w')
f.sync = true
ActiveSupport::BufferedLogger.new f

50. ActiveSupport::BufferedLogger#flush过时,请设定文件句柄的sync,或是优化你的文件系统。