Railsでスコープを活用してクエリを簡潔に記述する方法
はじめに
前回の記事では、controller
ファイル内で平均難易度が1から3の間で投稿数の多い本を8件取得するコードを作成しました。詳細はこちらの記事をご参照ください。
しかし、今回は平均難易度が2から4や3から5の場合も同様にデータを取得したいと考えています。愚直に実装すると、同じようなコードが繰り返されてしまい、コードが冗長になります。
@books_dif_low = Book
.joins(:posts)
.select('books.*, AVG(posts.difficulty) AS average_difficulty, COUNT(posts.id) AS posts_count')
.group('books.id')
.having('AVG(posts.difficulty) BETWEEN ? AND ?', 1, 3)
.order('posts_count DESC')
.limit(8)
@books_dif_mid = Book
.joins(:posts)
.select('books.*, AVG(posts.difficulty) AS average_difficulty, COUNT(posts.id) AS posts_count')
.group('books.id')
.having('AVG(posts.difficulty) BETWEEN ? AND ?', 2, 4)
.order('posts_count DESC')
.limit(8)
@books_dif_high = Book
.joins(:posts)
.select('books.*, AVG(posts.difficulty) AS average_difficulty, COUNT(posts.id) AS posts_count')
.group('books.id')
.having('AVG(posts.difficulty) BETWEEN ? AND ?', 3, 5)
.order('posts_count DESC')
.limit(8)
そこで、スコープを活用することで、コードを簡潔かつ再利用可能にする方法を紹介します。
スコープの使い方
スコープとは?
Scope(スコープ)は、RailsのActiveRecordモデル内で定義される、再利用可能なクエリの定義方法です。スコープを使用することで、複雑なクエリをシンプルかつ読みやすく管理できます。
なぜスコープを使うのか?
-
再利用性の向上
同じ条件やロジックを複数の場所で使用する際に、一度スコープとして定義しておけば簡単に呼び出せます。
-
コードの可読性向上
複雑なクエリを名前付きのスコープに分割することで、コード全体の理解が容易になります。
-
チェーン可能
複数のスコープを組み合わせて柔軟なクエリを構築できます。
スコープの書き方
基本的なスコープの定義方法は以下の通りです。
scope :scope_name, ->(parameters) { where(condition: value) }
次に、具体的なスコープの定義方法を見ていきましょう。
スコープを使った記述
1. with_average_difficulty_between
scope :with_average_difficulty_between, ->(min, max) {
joins(:posts)
.select('books.*, AVG(posts.difficulty) AS average_difficulty, COUNT(posts.id) AS posts_count')
.group('books.id')
.having('AVG(posts.difficulty) BETWEEN ? AND ?', min, max)
}
役割と機能
このスコープは、平均難易度が指定された範囲(min
からmax
)にある本を取得します。また、各本の平均難易度と投稿数も計算・取得します。
2. ordered_by_posts_count
scope :ordered_by_posts_count, -> {
order('posts_count DESC')
}
役割と機能
このスコープは、投稿数(posts_count
)が多い順に本を並べ替えます。
3. top_ranked
scope :top_ranked, ->(limit = 8) {
limit(limit)
}
役割と機能
このスコープは、取得する本の数を制限します。デフォルトでは8件の本を取得しますが、引数で指定することも可能です。
Scopeを組み合わせたクエリの実行
定義した3つのスコープをチェーン(連結)することで、目的のデータを簡潔に取得できます。以下に具体例を示します。
# app/models/book.rb
class Book < ApplicationRecord
has_many :posts
scope :with_average_difficulty_between, ->(min, max) {
joins(:posts)
.select('books.*, AVG(posts.difficulty) AS average_difficulty, COUNT(posts.id) AS posts_count')
.group('books.id')
.having('AVG(posts.difficulty) BETWEEN ? AND ?', min, max)
}
scope :ordered_by_posts_count, -> {
order('posts_count DESC')
}
scope :top_ranked, ->(limit = 8) {
limit(limit)
}
end
これらのスコープを使用して、平均難易度と投稿数に基づいた本を簡単に取得できます。
# コントローラー内の例
@books_dif_low = Book
.with_average_difficulty_between(1, 3)
.ordered_by_posts_count
.top_ranked
@books_dif_mid = Book
.with_average_difficulty_between(2, 4)
.ordered_by_posts_count
.top_ranked
@books_dif_high = Book
.with_average_difficulty_between(3, 5)
.ordered_by_posts_count
.top_ranked
このようにスコープを活用することで、クエリが簡潔になり、メンテナンス性も向上します。
終わりに
スコープを活用することで、冗長なコードを避けることができ、コードの可読性と再利用性が向上します。また、スコープを定義する際にもSQLの基礎知識が大切になると感じました。