LoginSignup
0
0

More than 5 years have passed since last update.

拥抱 Rails 4_2

Last updated at Posted at 2013-03-23

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 了,只不过是多了一层防范措施,以减少初学者不小心造成的性能问题。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0