# 拥抱 Rails 4_2 ## ActiveRecord ### Finders ```ruby Book.find(:all, conditions: { author: 'Albert Yu' }) ``` 这种方法已经用了很久了吧?在 Rails 4 中,你会看到如下警告: > DEPRECATION WARNING: Calling #find(:all) is deprecated. Please call #all directly instead. You have also used finder options. These are also deprecated. Please build a scope instead of using finder options. 实际上,老式的 finders 已经被抽取成了 `activerecord-deprecated_finders` gem,你要还想用就得自己安装它。 在 Rails 4 中,推荐这样用: ```ruby Book.where(author: 'Albert Yu') ``` 没人不爱它!而且还没完,同样的变化还有: ```ruby Book.find_all_by_title('Rails 4') # r3 way Book.find_last_by_author('Albert Yu') # r3 way Book.where(title: 'Rails 4') # r4 way Book.where(author: 'Albert Yu').last # r4 way ``` 动态的 `find_by` 也不例外: ```ruby Book.find_by_title('Rails 4') # 接收单个参数的用法在 r3 & r4 都可以 Book.find_by(title: 'Rails4') # 不过 r4 更偏爱这样写 Book.find_by_title('Rails 4', conditions: { author: 'Albert Yu' }) # 这就不好了,得改 Book.find_by(title: 'Rails4', author: 'Albert Yu') # Wow! 太棒了! ``` 统一使用 `find_by` 不仅有更好的一致性,而且更便于接收 hash 参数: ```ruby book_param = { title: 'Rails 4', author: 'Albert Yu' } Book.find_by(book_param) ``` `find_by` 方法的内部实现其实很简单: ```ruby # activerecord/lib/active_record/relation/finder_methods.rb def find_by(*args) where(*args).take end ``` 这意味着这样用也没有问题: ```ruby Book.find_by("published_on < ?", 3.days.ago) ``` ### find\_or_* 这两种方法不再推荐使用了: ```ruby Book.find_or_initialize_by_title('Rails 4') Book.find_or_create_by_title('Rails 4') ``` 会抛出如下警告: > DEPRECATION WARNING: This dynamic method is deprecated. Please use e.g. Post.find_or_initialize_by(name: 'foo') instead. > DEPRECATION WARNING: This dynamic method is deprecated. Please use e.g. Post.find_or_create_by(name: 'foo') instead. 让我们从善如流: ```ruby Book.find_or_initialize_by(title: 'Rails 4') Book.find_or_create_by(title: 'Rails 4') ``` 还有一种容易让人迷惑的用法 ```ruby Book.where(title: 'Rails 4').first_or_create # 若找不到… Book.where(title: 'Rails 4').create ``` 这方法在 Rails 3 和 Rails 4 里都可以用,它先是查询是否有符合条件的记录,若没有就以该条件创建一个。听起来还不错,然而当存在这样的代码时,其表现就不是你想的那样了: ```ruby class Book < ActiveRecord::Base after_create :foo def foo books = books.where(author: 'Albert Yu') ... end end ``` 产生的 SQL 是: ```sql SELECT "books".* FROM "books" WHERE "books"."title" = 'Rails 4' AND "books"."author" = 'Albert Yu' ``` 注意,这里的 `after_create` 回调原本是在创建一条记录后立刻返回__所有作者是 Albert Yu 的记录__,但最终的结果却是__所有标题是 Rails 4 并且作者是 Albert Yu 的记录__。这是因为触发该回调函数的方法调用已经有了 `title: 'Rails 4'` 的作用域,于是产生了作用域叠加。 Rails 4 里推荐这样来做: ```ruby Book.find_or_create_by(title: 'Rails 4') # 若找不到… Book.create(title: 'Rails 4') ``` 这样就不会产生叠加副作用,真正的 SQL 语句如下: ```sql SELECT "books".* FROM "books" WHERE "books"."author" = 'admin' ``` ### #update & #update_column 是不是经常被 `#update_attributes` 和 `#update_attribute` 还有 `#update_column` 搞晕?好消息来了——Rails 4 重新整理了属性更新的方法,现在的方式简单明了: ```ruby @book.update(post_params) # 会触发验证 @book.update_columns(post_params) # 构建 SQL 语句,直接执行于数据库层,不会触发验证 ``` 就这俩,不会搞错了吧?以前的方式也还能用,但是不排除会被废弃。既然 Rails 4 提供了更好用的方法,那就不要再犹豫了。 ### Model.all 也不是所有的变化都那么显而易见的令人愉悦,一部分人大概会对接下来的变化感到不适应。以前普遍认为不要直接使用 `Model.all`,因为这会产生很严重的性能问题,开发者更倾向于先对 Model 进行 scope: ```ruby def index @books = Book.scoped if params[:recent] @books = @books.recent end end ``` 然而,Rails 4 会抛出如下警告: > DEPRECATION WARNING: Model.scoped is deprecated. Please use Model.all instead. WTF?`Model.all` 又回来了? 没错。不过你不用担心,Rails 4 里的 `Model.all` 不会立即执行对数据库的查询,而仅仅是返回一个 `ActiveRecord::Relation`,你可以继续进行链式调用: ```ruby def index @books = Book.all # 我不会碰数据库的哦,直到你告诉我下一个条件… if params[:recent] @books = @books.recent # 这时候我才会行动 end end ``` 当然,这并不是说不能用 scoped model 了,只不过是多了一层防范措施,以减少初学者不小心造成的性能问题。