Rails: created_atやupdated_atのソートでスコープとconcernを使う(翻訳)
以前の記事『Rails: モデルの外では名前付きスコープだけを使おう』で、クエリAPIの呼び出しをモデルの中にとどめておくのはよいアイデアであると提案しました。Railsが提供するscope
は、常用されるクエリをカプセル化する方法のひとつです。
またRailsは、さまざまな場所で用いられる機能をグループ化する方法のひとつとしてActive Support::Concern
も提供しています。concernは、Rubyの標準的なmodule
のラッパーです。
アプリケーション内の複数の場所で使いたくなることのある機能といえば、組み込みのActiveRecord::Timestamp
を用いて作成時や更新時でモデルをソートする機能です。
この2つを組み合わせて、アプリケーションにこの機能を追加する便利なミニライブラリを作ってみましょう。
以下のように書くよりも
order
にスコープを渡してタイムスタンプ順にソートする。
# app/controllers/books_controller.rb
class BooksController
def index
@books = Book.order(created_at: :asc).limit(20)
end
end
以下のように名前付きスコープを使うとましになる。
# app/models/book.rb
class Book < ApplicationRecord
scope :by_recently_created, -> { order(created_at: :desc) }
end
# app/controllers/books_controller.rb
class BooksController
def index
@books = Book.by_recently_created.limit(20)
end
end
以下のように書く
便利なconcernを作る。
# app/models/concerns/orderable_by_timestamp.rb
module OrderableByTimestamp
extend ActiveSupport::Concern
included do
scope :by_earliest_created, -> { order(created_at: :asc) }
scope :by_recently_created, -> { order(created_at: :desc) }
scope :by_earliest_updated, -> { order(updated_at: :asc) }
scope :by_recently_updated, -> { order(updated_at: :desc) }
end
end
# app/models/book.rb
class Book < ApplicationRecord
include OrderableByTimestamp
end
# app/controllers/books_controller.rb
class BooksController
def index
@books = Book.by_recently_created.limit(20)
end
end
スコープがconcernに切り出されたので、OrderableByTimestamp
を複数のモデルで使い回せるようになります。
そうする理由
このよくあるリファクタリングには、Railsの複数の部分が連携して機能するマジックが表れています。
これらの便利な機能を用いて、よく使う機能を1箇所に集めました。これを「コードのDRY化」と呼ぶことがあります。
そうしない理由があるとすれば
そこそこ一般的な4つのスコープを複数のモデルにinclude
しても、それらの一部しか使われないのであれば、モデルが不必要に肥大する可能性があります。このような理由で、concernのコードはできるだけ小さくしておく必要があります。
謝意
Danは、私たちが手掛けている以下のアプリケーションにこのパターンを取り入れてくれました。
概要
原著者の許諾を得て翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。