0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

モデルのscope

Posted at

モデルにはScopeというものを定義できます。Scopeとは、よく利用する検索条件に名前を付けるひとまとめにしたものです。

繰り返し利用するクエリの再利用性が上がる
クエリに名前を付けることで、可読性が向上する

class Book < ApplicationRecord
  scope :costly, -> {where("price > ?", 3000)}
end
irb(main):001:0> Book.costly
  TRANSACTION (0.2ms)  BEGIN
  Book Load (20.9ms)  SELECT `books`.* FROM `books` WHERE (price > '3000')
=>
[#<Book:0x000000010d930158
  id: 4,
  name: "Book 4",
  published_on: Thu, 08 Aug 2024,
  price: 4000,
  created_at: Sun, 08 Dec 2024 10:04:21.547588000 UTC +00:00,
  updated_at: Sun, 08 Dec 2024 10:04:21.547588000 UTC +00:00>,
 #<Book:0x000000010d97bfb8
  id: 5,
  name: "Book 5",
  published_on: Mon, 08 Jul 2024,
  price: 5000,
  created_at: Sun, 08 Dec 2024 10:04:21.549149000 UTC +00:00,
  updated_at: Sun, 08 Dec 2024 10:04:21.549149000 UTC +00:00>]

3000より大きいものが表示された

スコープをつなげることによって読んだだけで返り血の内容が理解できる

このように定義しておけば、Book.costly.written_about("java")という記述が可能になり、コードを見るだけで「高価」で「Javaについて書かれた」本を探している、ということがより明確になります。

Query Interface(find_byなど)を使ってスコープを作成できる。

:written_aboutScopeのように、特定のパラメータを利用するスコープも定義可能なことを覚えておいてください。Scopeを定義した際の返り値は、先ほどのActiveRecord::Relationとなるので、Scopeのチェインだけでなく、通常のQuery Interfaceを呼び出すこともできる。

クラスメソッドとスコープの違い

クラスメソッドで定義した場合との違いについて説明します。
ここまでのScopeの解説では、クラスメソッドで任意の探索条件を書いた場合とほとんど同じではと感じると思います。多くの場合は、クラスメソッドで条件を記述することでショートハンドとしてScopeを活用できます。しかし次のような場合は違いが現れます。
 次のScopeはfind_byを利用しているため、条件がマッチしなければnilが返ることを想定しています。

class Book < ApplicationRecord
  scope :costly, -> {where("price > ?", 3000)}
  scope :written_about, ->(theme) {where("name like ?", "%#{theme}")}
  scope :find_price, ->(price) {find_by(price: price)}

  def self.find_price2(price)
    self.find_by(price: price)
  end
end

Scopeはないものを呼び出すと意図したものは返ってこない。しかしクラスメソッドはnilが返ってくる

irb(main):002:0> Book.find_price2(10000)
  TRANSACTION (0.2ms)  BEGIN
  Book Load (0.5ms)  SELECT `books`.* FROM `books` WHERE `books`.`price` = 10000 LIMIT 1
=> nil
irb(main):003:0> Book.find_price(10000)
  Book Load (0.4ms)  SELECT books`.* FROM `books` WHERE `books`.`price` = 10000 LIMIT 1
  Book Load (0.4ms)  SELECT books`.* FROM `books`
=>
[#<Book:0x000000010bd393f0
  id: 1,
  name: "Book 1",
  published_on: Fri, 08 Nov 2024,
  price: 1000,
  created_at: Sun, 08 Dec 2024 10:04:21.535099000 UTC +00:00,
  updated_at: Sun, 08 Dec 2024 10:04:21.535099000 UTC +00:00>,
 #<Book:0x000000010ad51d48
  id: 2,
  name: "Book 2",
  published_on: Tue, 08 Oct 2024,
  price: 2000,
  created_at: Sun, 08 Dec 2024 10:04:21.542065000 UTC +00:00,
  updated_at: Sun, 08 Dec 2024 10:04:21.542065000 UTC +00:00>,
 #<Book:0x000000010ad51c80
  id: 3,
  name: "Book 3",
  published_on: Sun, 08 Sep 2024,
  price: 3000,
  created_at: Sun, 08 Dec 2024 10:04:21.545182000 UTC +00:00,
  updated_at: Sun, 08 Dec 2024 10:04:21.545182000 UTC +00:00>,
 #<Book:0x000000010ad51bb8
  id: 4,
  name: "Book 4",
  published_on: Thu, 08 Aug 2024,
  price: 4000,
  created_at: Sun, 08 Dec 2024 10:04:21.547588000 UTC +00:00,
  updated_at: Sun, 08 Dec 2024 10:04:21.547588000 UTC +00:00>,
 #<Book:0x000000010ad51af0
  id: 5,
  name: "Book 5",
  published_on: Mon, 08 Jul 2024,
  price: 5000,
  created_at: Sun, 08 Dec 2024 10:04:21.549149000 UTC +00:00,
  updated_at: Sun, 08 Dec 2024 10:04:21.549149000 UTC +00:00>]

テーブルに存在すると意図した通りに返ってくる

irb(main):005:0> Book.find_price(1000)
  Book Load (0.4ms)  SELECT `books`.* FROM `books` WHERE `books`.`price` = 1000 LIMIT 1
=>
#<Book:0x000000010b932590
 id: 1,
 name: "Book 1",
 published_on: Fri, 08 Nov 2024,
 price: 1000,
 created_at: Sun, 08 Dec 2024 10:04:21.535099000 UTC +00:00,
 updated_at: Sun, 08 Dec 2024 10:04:21.535099000 UTC +00:00>

ScopeはActiveRecord_Relationのクラスを返す

irb(main):007:0> a = Book.written_about("jova").find_price(1000)
  Book Load (0.5ms)  SELECT `books`.* FROM `books` WHERE (name like '%jova') AND `books`.`price` = 1000 LIMIT 1
  Book Load (0.5ms)  SELECT `books`.* FROM `books` WHERE (name like '%jova')
=> []
irb(main):008:0> a.class
=> Book::ActiveRecord_Relation

デフォルトスコープは複雑だと意図しない挙動を起こす

default_scope(scope = nil, all_queries: nil, &block)
Use this macro in your model to set a default scope for all operations on the model.

class Article < ActiveRecord::Base
  default_scope { where(published: true) }
end

Article.all
# SELECT * FROM articles WHERE published = true

default_scopeはそのモデルクラスでの操作すべてに影響を与えるため、複雑な条件での検索や別軸で探索したい場合に意図しない挙動となる場合もあります

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?