そこはhas_oneを使いましょうよ

## はじめに
has_manyなどで関連付けされているモデルで特定の条件の関連レコードを取得する際に安易にメソッドを定義したりしていませんか？

## たとえば

```ruby
# ユーザー
class User < ApplicationRecord
  has_many :articles
end

# 記事
class Article < ApplicationRecord
  belongs_to :user
end
```

という関係があった場合、最新の記事を取得するのに

```ruby
def latest_article
  articles.order(created_at: :desc).first
end
```

などというメソッドを安易に定義しがちではないかと思います。

## メソッドの場合

これが一つのviewで一度しか呼び出されないのならば特に問題はないかと思います。
でもリスト表示などの場合、

```ruby
# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def index
    @users = User.all
  end
end
```

```erb
# index.html.erb
<% @users.each do |user| %>
  <%= user.name %>
  <%= user.latest_article.title %>
<% end %>
```

のようなviewを表示するのに

```sql
  User Load (0.5ms)  SELECT `users`.* FROM `users`
  Article Load (0.4ms)  SELECT  `articles`.* FROM `articles` WHERE `articles`.`user_id` = 1 ORDER BY `articles`.`created_at` DESC LIMIT 1
  Article Load (0.4ms)  SELECT  `articles`.* FROM `articles` WHERE `articles`.`user_id` = 2 ORDER BY `articles`.`created_at` DESC LIMIT 1
  Article Load (0.3ms)  SELECT  `articles`.* FROM `articles` WHERE `articles`.`user_id` = 3 ORDER BY `articles`.`created_at` DESC LIMIT 1
```

とユーザーの数だけArticleをロードするようになります。
さらに

```erb
# index.html.erb
<% @users.each do |user| %>
  <%= user.name %>
  <%= user.latest_article.title %>
  <%= user.latest_article.body %>
<% end %>
```

などのようにしようものなら、

```sql
  User Load (0.3ms)  SELECT `users`.* FROM `users`
  Article Load (0.4ms)  SELECT  `articles`.* FROM `articles` WHERE `articles`.`user_id` = 1 ORDER BY `articles`.`created_at` DESC LIMIT 1
  CACHE Article Load (0.0ms)  SELECT  `articles`.* FROM `articles` WHERE `articles`.`user_id` = 1 ORDER BY `articles`.`created_at` DESC LIMIT 1  [["user_id", 1], ["LIMIT", 1]]
  Article Load (0.3ms)  SELECT  `articles`.* FROM `articles` WHERE `articles`.`user_id` = 2 ORDER BY `articles`.`created_at` DESC LIMIT 1
  CACHE Article Load (0.0ms)  SELECT  `articles`.* FROM `articles` WHERE `articles`.`user_id` = 2 ORDER BY `articles`.`created_at` DESC LIMIT 1  [["user_id", 2], ["LIMIT", 1]]
  Article Load (0.5ms)  SELECT  `articles`.* FROM `articles` WHERE `articles`.`user_id` = 3 ORDER BY `articles`.`created_at` DESC LIMIT 1
  CACHE Article Load (0.0ms)  SELECT  `articles`.* FROM `articles` WHERE `articles`.`user_id` = 3 ORDER BY `articles`.`created_at` DESC LIMIT 1  
```

キャッシュを使っていますが`user.latest_article`がメモ化されるわけでもなく、その都度Articleを探しに行ってしまいます。

## has_oneを利用した場合

そこでメソッドでなく以下のように関連付けで定義してみると

```ruby
  has_one :latest_article, lambda {
    order(created_at: :desc)
  }, class_name: :Article
```


```erb
# index.html.erb
<% @users.each do |user| %>
  <%= user.name %>
  <%= user.latest_article.title %>
  <%= user.latest_article.body %>
<% end %>
```

```sql
  User Load (0.4ms)  SELECT `users`.* FROM `users`
  Article Load (0.3ms)  SELECT  `articles`.* FROM `articles` WHERE `articles`.`user_id` = 1 ORDER BY `articles`.`created_at` DESC LIMIT 1
  Article Load (0.4ms)  SELECT  `articles`.* FROM `articles` WHERE `articles`.`user_id` = 2 ORDER BY `articles`.`created_at` DESC LIMIT 1
  Article Load (0.3ms)  SELECT  `articles`.* FROM `articles` WHERE `articles`.`user_id` = 3 ORDER BY `articles`.`created_at` DESC LIMIT 1
```

view内では同じく二度latest_articleが呼ばれているのですが、ArticleをLoadするのは一度づつに減っています。

さらにコントローラーでpreloadしてやると

```ruby
class HomeController < ApplicationController
  def index
    @users = User.preload(:latest_article).all
  end
end
```

```sql
  User Load (2.1ms)  SELECT `users`.* FROM `users`
  Article Load (0.3ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` IN (1, 2, 3) ORDER BY `articles`.`created_at` DESC
```

とArticleをLoadするのは一度だけになりました。

## 結論
has_manyなどで既に関連付けされているモデルから特定の条件でレコードを取得する際に安易にメソッドやviewから関連モデルを呼び出しがちですが、条件を付加したhas_oneやhas_manyで定義することでパフォーマンスにもお財布にも優しいコードになるかと思います。