ActiveRecord
Finders
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 中,推荐这样用:
Book.where(author: 'Albert Yu')
没人不爱它!而且还没完,同样的变化还有:
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
也不例外:
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 参数:
book_param = { title: 'Rails 4', author: 'Albert Yu' }
Book.find_by(book_param)
find_by
方法的内部实现其实很简单:
# activerecord/lib/active_record/relation/finder_methods.rb
def find_by(*args)
where(*args).take
end
这意味着这样用也没有问题:
Book.find_by("published_on < ?", 3.days.ago)
find_or_*
这两种方法不再推荐使用了:
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.
让我们从善如流:
Book.find_or_initialize_by(title: 'Rails 4')
Book.find_or_create_by(title: 'Rails 4')
还有一种容易让人迷惑的用法
Book.where(title: 'Rails 4').first_or_create
# 若找不到…
Book.where(title: 'Rails 4').create
这方法在 Rails 3 和 Rails 4 里都可以用,它先是查询是否有符合条件的记录,若没有就以该条件创建一个。听起来还不错,然而当存在这样的代码时,其表现就不是你想的那样了:
class Book < ActiveRecord::Base
after_create :foo
def foo
books = books.where(author: 'Albert Yu')
...
end
end
产生的 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 里推荐这样来做:
Book.find_or_create_by(title: 'Rails 4')
# 若找不到…
Book.create(title: 'Rails 4')
这样就不会产生叠加副作用,真正的 SQL 语句如下:
SELECT "books".* FROM "books" WHERE "books"."author" = 'admin'
#update & #update_column
是不是经常被 #update_attributes
和 #update_attribute
还有 #update_column
搞晕?好消息来了——Rails 4 重新整理了属性更新的方法,现在的方式简单明了:
@book.update(post_params) # 会触发验证
@book.update_columns(post_params) # 构建 SQL 语句,直接执行于数据库层,不会触发验证
就这俩,不会搞错了吧?以前的方式也还能用,但是不排除会被废弃。既然 Rails 4 提供了更好用的方法,那就不要再犹豫了。
Model.all
也不是所有的变化都那么显而易见的令人愉悦,一部分人大概会对接下来的变化感到不适应。以前普遍认为不要直接使用 Model.all
,因为这会产生很严重的性能问题,开发者更倾向于先对 Model 进行 scope:
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
,你可以继续进行链式调用:
def index
@books = Book.all # 我不会碰数据库的哦,直到你告诉我下一个条件…
if params[:recent]
@books = @books.recent # 这时候我才会行动
end
end
当然,这并不是说不能用 scoped model 了,只不过是多了一层防范措施,以减少初学者不小心造成的性能问题。