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