Fedora / Debian 用 RVM 安装 Ruby on Rails 最新版本
Cucumber Introduction

Rails 3.2 新特性简介

bachue posted @ 2012年4月08日 00:12 in Ruby with tags ruby mysql rails deltamaster ruby on rails 3.2 ActiveModel ActiveRecord Anne Bachue ActiveController Rainux GitCafe , 7212 阅读

好久没有写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,或是优化你的文件系统。

逍遥慕容 说:
2012年12月29日 00:55

总结得非常好。感谢

路人甲 说:
2015年5月20日 10:25

总结的不错!

seo service london 说:
2024年1月16日 19:16

You have performed a great job on this article. It’s very precise and highly qualitative. You have even managed to make it readable and easy to read. You have some real writing talent. Thank you so muc


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter