こんにちは! フリーエンジニアの長瀬です。
みなさんはscopeを使っていますか。
scopeを使えば、SQL文をメソッド化できるのでデータ管理を効率化できます。
この記事では、scopeの使い方について
・scope とは
・scopeの使い方
という基本的な内容から、
・scopeの応用的な使い方
といった一歩進んだ内容についても解説していきます。
scopeとは
モデルにscopeを設定することで、特定のSQL文をメソッド化して使えます。
なので、繰り返しよく使うSQL文があるときにはscopeを使って定義しておきましょう。
今回はUserモデルとPostモデルを使って解説するので、まずは以下のサンプルコードを入力してください。
[UserモデルとPostモデルを作成し、データベースに反映する]
rails g model User name:string age:integer rails g model Post user:references title:string deleted_at:datetime rake db:migrate
これで、今回使うカラムとモデルが作成できました。
続いてデータを入れていきましょう。
rails consoleでコンソールを起動した後、以下のコードを入力してください。
[UserモデルとPostモデルに今回使うデータを入れる
User.create(name:"山田花子",age:3) User.create(name:"長瀬来",age:27) User.create(name:"安田一郎",age:11) User.create(name:"細田俊介",age:34) User.create(name:"安原庄之助",age:88) User.create(name:"中村拓郎",age:81) Post.create(user_id:3,title:"楽しい休日の過ごし方",deleted_at:"2017-05-01 09:37:00".to_datetime,created_at:"2017-04-26 15:29:00".to_datetime) Post.create(user_id:2,title:"先日の旅行での話",created_at:"2017-04-30 23:04:00".to_datetime ,deleted_at:"2017-05-16 9:38:00".to_datetime) Post.create(user_id:1,title:"昨日の出来事" ,created_at: "2017-05-24 13:02:00".to_datetime) Post.create(user_id:6,title:"山登りに行きました",created_at:"2017-05-27 12:37:00".to_datetime ) Post.create(user_id:4,title:"友人が結婚しました",created_at: "2017-05-29 14:40:00".to_datetime) Post.create(user_id:5,title:"友人が結婚しました",created_at: "2017-06-8 13:00:00".to_datetime) Post.create(user_id:4,title:"最近少し気になったこと",created_at:"2017-05-16 9:38:00".to_datetime) Post.create(user_id:2,title:"ランニングのコツ",created_at: "2017-05-2 9:18:00".to_datetime)
これでデータが入りました。
続いて、UserとPostの関連付けを行いますので、それぞれのmodelに以下のサンプルコードを入力してください。
[app/models/user.rbに入力する内容]
has_many :posts
[app/models/post.rbに記述する内容]
belongs_to :user
これで関連付けを設定できました。
では実際にscopeを使ってみましょう。
app/models/user.rbに以下のサンプルコードを入力してください。
[app/models/user.rbに入力する内容]
scope :young, -> { where("age < 20") }
このようにyoungというscopeを設定したので、youngというメソッドが使えるようになります。
ここからはrails cosoleに取り出したデータを出力していくので、出力したデータの見栄えを良くにするために一つgemを追加します。(すでにインストール済みの方は飛ばしてもらってかまいません。)
まず、Gemfileに
[Gemfileに入力する内容]
gem 'hirb' # 出力結果を表として出力するgem gem 'hirb-unicode' # マルチバイト文字の表示を補正するgem
を追記して、コマンドプロンプトで
[Gemfileの内容を反映する]
bundle install
を入力してインストールしてください。
これで、いつでもHirb.enableとコーソールで入力すればHirbのgemが有効になります。
rails consoleでコンソールを起動した後、以下のコードを入力してください。
[自分で設定したyoungメソッドを使ってみる]
Hirb.enable #表形式で出力 User.young
[実行結果]
User Load (3.9ms) SELECT "users".* FROM "users" WHERE (age < 20) +----+----------+-----+---------------------------+---------------------------+ | id | name | age | created_at | updated_at | +----+----------+-----+---------------------------+---------------------------+ | 1 | 山田花子 | 3 | 2017-07-10 00:36:51 +0900 | 2017-07-10 00:36:51 +0900 | | 3 | 安田一郎 | 11 | 2017-07-10 00:36:51 +0900 | 2017-07-10 00:36:51 +0900 | +----+----------+-----+---------------------------+---------------------------+ 2 rows in set
モデルで設定したageが20以下のデータを取り出すyoungメソッドを使うことができました。
このようにscopeを定義すれば、よく使うSQL文をメソッド化して使えます。
これがscopeを使うメリットです。
scopeの基本構文
それでは、さきほど使ったscopeの構文を解説していきます。
[scopeで定義されたyoungメソッドの例]
scope :young, -> { where("age < 20") }
scopeはmodels以下のモデル名.rbファイル内に記述にします。
まず、scope :(メソッドの名前)でメソッド名を定義してそれ以降の部分にSQL文を書いていきます。この構文はラムダ式と呼ばれるものです。
ラムダ(lambda)式とは
ラムダ式とは名前の通り、ラムダを使った式のことで、Rails4以降はscopeの定義にはラムダ式を使うことが必須になりました。
ラムダについて説明すると、1つの記事になってしまいます。
ですので、ここではラムダ式はいくつかのコードをまとめて1つのメソッドとして扱うことを実現してくれる便利なものという認識で大丈夫です。
Rubyにはラムダ(lamda)の他にProcという類似の機能があります。
気になる方はこちらの記事を参考にしてください。
さきほど、使ったサンプルコードを例にすると
scope :young, -> { where("age < 20") }
scope以降の:young, -> 〜がラムダ式で記述されている部分です。
scopeの使い方
scopeの基本的な使い方
それではscopeを実践的に使っていきましょう。
まずはwhereを使ったscopeからです。
app/models/post.rbに以下のサンプルコードを入力してください。
[app/models/post.rbに入力する内容]
scope :two_months, -> { where("created_at < ?",Time.zone.now-2.month) }
whereを使って定義したscopeを使っていきましょう。
rails consoleでコンソールを起動した後、以下のコードを入力してください。
[two_monthsメソッドを使ってデータを取り出す]
Hirb.enable #表形式で出力 Post.two_months
[実行結果]
Post Load (0.2ms) SELECT "posts".* FROM "posts" WHERE (created_at < '2017-05-10 10:10:25.297859') +----+---------+----------------------+---------------------------+---------------------------+---------------------------+ | id | user_id | title | deleted_at | created_at | updated_at | +----+---------+----------------------+---------------------------+---------------------------+---------------------------+ | 1 | 3 | 楽しい休日の過ごし方 | 2017-05-01 18:37:00 +0900 | 2017-04-27 00:29:00 +0900 | 2017-07-10 01:43:31 +0900 | | 2 | 2 | 先日の旅行での話 | 2017-05-16 18:38:00 +0900 | 2017-05-01 08:04:00 +0900 | 2017-07-10 01:43:31 +0900 | | 8 | 2 | ランニングのコツ | | 2017-05-02 18:18:00 +0900 | 2017-07-10 01:43:32 +0900 | +----+---------+----------------------+---------------------------+---------------------------+---------------------------+ 3 rows in set
このように、whereが適応されていることが確認できます。
続いて、orderです。
app/models/post.rbに以下のサンプルコードを入力してください。
[app/models/post.rbに入力する内容]
scope :desc, -> { order("posts.created_at DESC") }
orderを使って定義したscopeを使っていきましょう。
rails consoleでコンソールを起動した後、以下のコードを入力してください。
[descメソッドを使ってデータを取り出す]
Hirb.enable #表形式で出力 Post.desc
[実行結果]
Post Load (2.2ms) SELECT "posts".* FROM "posts" ORDER BY posts.created_at DESC +----+---------+------------------------+---------------------------+---------------------------+---------------------------+ | id | user_id | title | deleted_at | created_at | updated_at | +----+---------+------------------------+---------------------------+---------------------------+---------------------------+ | 6 | 5 | 友人が結婚しました | | 2017-06-08 22:00:00 +0900 | 2017-07-10 01:43:31 +0900 | | 5 | 4 | 友人が結婚しました | | 2017-05-29 23:40:00 +0900 | 2017-07-10 01:43:31 +0900 | | 4 | 6 | 山登りに行きました | | 2017-05-27 21:37:00 +0900 | 2017-07-10 01:43:31 +0900 | | 3 | 1 | 昨日の出来事 | | 2017-05-24 22:02:00 +0900 | 2017-07-10 01:43:31 +0900 | | 7 | 4 | 最近少し気になったこと | | 2017-05-16 18:38:00 +0900 | 2017-07-10 01:43:31 +0900 | | 8 | 2 | ランニングのコツ | | 2017-05-02 18:18:00 +0900 | 2017-07-10 01:43:32 +0900 | | 2 | 2 | 先日の旅行での話 | 2017-05-16 18:38:00 +0900 | 2017-05-01 08:04:00 +0900 | 2017-07-10 01:43:31 +0900 | | 1 | 3 | 楽しい休日の過ごし方 | 2017-05-01 18:37:00 +0900 | 2017-04-27 00:29:00 +0900 | 2017-07-10 01:43:31 +0900 | +----+---------+------------------------+---------------------------+---------------------------+---------------------------+ 8 rows in set
orderで指定したcreated_atの降順でデータを取り出せました。
パラメータ付きのscopeの使い方
また、scopeにはパラメータを設定することもできます。
つまり、ラムダ式に引数を付けるということです。
以下のサンプルコードをapp/models/user.rbに入力してください。
[app/models/user.rbに入力する内容]
scope :age_check, ->(number) { where("age < ?", number) }
これでscopeが設定できたので、rails consoleでコンソールを開いて以下のコードを入力してください。
[設定したageメソッドを使ってみる]
Hirb.enable #表形式で出力 User.age_check(50)
[実行結果]
User Load (0.4ms) SELECT "users".* FROM "users" WHERE (age < 50) +----+----------+-----+---------------------------+---------------------------+ | id | name | age | created_at | updated_at | +----+----------+-----+---------------------------+---------------------------+ | 1 | 山田花子 | 3 | 2017-07-10 00:36:51 +0900 | 2017-07-10 00:36:51 +0900 | | 2 | 長瀬来 | 27 | 2017-07-10 00:36:51 +0900 | 2017-07-10 00:36:51 +0900 | | 3 | 安田一郎 | 11 | 2017-07-10 00:36:51 +0900 | 2017-07-10 00:36:51 +0900 | | 4 | 細田俊介 | 34 | 2017-07-10 00:36:51 +0900 | 2017-07-10 00:36:51 +0900 | +----+----------+-----+---------------------------+---------------------------+ 4 rows in set
50を引き数として渡したので、ageが50よりも小さいuserが取り出されました。
このように、パラメータを設定することで、より柔軟に使えるメソッドを定義できます。
default_scopeの使い方
default_scopeを使えば、モデルに設定したscopeを反映した結果を常に出力できます。
てすが、defall_scopeは常に呼ばれるようになるので、使用上の注意が必要です。
この記事ではPostで削除された記事を表示させないように設定します。
[app/modes/post.rbにdefault_scopeを設定]
default_scope { where(deleted_at:nil) }
これで、deleted_atに日付が入力されているレコードは表示されなくなります。
実際にすべてのPostのレコードを取得してみましょう。
rails consoleでコンソールを起動した後、以下のコードを入力してください。
[Postの中身を全て表示するサンプルコード]
Hirb.enable #表形式で出力 Post.all
[実行結果]
Post Load (0.4ms) SELECT "posts".* FROM "posts" WHERE "posts"."deleted_at" IS NULL +----+---------+------------------------+------------+---------------------------+---------------------------+ | id | user_id | title | deleted_at | created_at | updated_at | +----+---------+------------------------+------------+---------------------------+---------------------------+ | 3 | 1 | 昨日の出来事 | | 2017-05-24 22:02:00 +0900 | 2017-07-10 01:43:31 +0900 | | 4 | 6 | 山登りに行きました | | 2017-05-27 21:37:00 +0900 | 2017-07-10 01:43:31 +0900 | | 5 | 4 | 友人が結婚しました | | 2017-05-29 23:40:00 +0900 | 2017-07-10 01:43:31 +0900 | | 6 | 5 | 友人が結婚しました | | 2017-06-08 22:00:00 +0900 | 2017-07-10 01:43:31 +0900 | | 7 | 4 | 最近少し気になったこと | | 2017-05-16 18:38:00 +0900 | 2017-07-10 01:43:31 +0900 | | 8 | 2 | ランニングのコツ | | 2017-05-02 18:18:00 +0900 | 2017-07-10 01:43:32 +0900 | +----+---------+------------------------+------------+---------------------------+---------------------------+ 6 rows in set
このように、すべて取得しようとしてもdefault_scopeの効果でdelete_atに日付がある記事は表示されなくなりました。
ただdefault_scopeは毎回呼ばれるので、よくバグの原因になったりします。
なので、今回のように論理削除する場合など、使い所を限定して使いましょう。
scopeの応用的な使い方
それでは少し応用の効いたscopeの使い方を紹介します。
関連付けられた2つのデーブルをjoinsで内部結合するときに、scopeではmergeメソッドと組み合わせることで、別のモデルで設定したscopeを参照して使えます。
今回はorderの説明でPostモデルに設定したdescメソッドをUserモデルで使えるようにしてみます。
以下のサンプルコードをapp/models/user.rbに入力してください。
[app/models/user.rbに入力する内容]
has_many :posts scope :desc_posts, -> { joins(:posts).merge(Post.desc) }
次にapp/models/post.rbに以下のサンプルコードを入力してください。
[app/models/post.rbに入力する内容]
belongs_to :user scope :desc, -> { order("posts.created_at DESC") }
それでは、rails consoleでコンソールを起動した後、以下のコードを入力してUserモデルで設定したscopeを使っていきましょう。
[desc_postsメソッドを使うサンプルコード]
Hirb.enable #表形式で出力 User.desc_posts
[実行結果]
SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" ORDER BY posts.created_at DESC +----+------------+-----+---------------------------+---------------------------+ | id | name | age | created_at | updated_at | +----+------------+-----+---------------------------+---------------------------+ | 5 | 安原庄之助 | 88 | 2017-07-10 00:36:51 +0900 | 2017-07-10 00:36:51 +0900 | | 4 | 細田俊介 | 34 | 2017-07-10 00:36:51 +0900 | 2017-07-10 00:36:51 +0900 | | 6 | 中村拓郎 | 81 | 2017-07-10 00:36:51 +0900 | 2017-07-10 00:36:51 +0900 | | 1 | 山田花子 | 3 | 2017-07-10 00:36:51 +0900 | 2017-07-10 00:36:51 +0900 | | 4 | 細田俊介 | 34 | 2017-07-10 00:36:51 +0900 | 2017-07-10 00:36:51 +0900 | | 2 | 長瀬来 | 27 | 2017-07-10 00:36:51 +0900 | 2017-07-10 00:36:51 +0900 | | 2 | 長瀬来 | 27 | 2017-07-10 00:36:51 +0900 | 2017-07-10 00:36:51 +0900 | | 3 | 安田一郎 | 11 | 2017-07-10 00:36:51 +0900 | 2017-07-10 00:36:51 +0900 | +----+------------+-----+---------------------------+---------------------------+ 8 rows in set
Postのcreated_atカラムの日付の降順で内部結合し、データを取り出せました。
このように、joinsで内部結合して別のモデルで定義したscopeを使うときはmergeと組み合わせて使うようにしてください。
まとめ
いかがでしたでしょうか?
この記事では、scopeの使い方を解説しました。
今回紹介した基本的なwhereやorderを使ってscopeを定義するだけでも、SQL文をメソッド化できるので開発効率が上がります。
また適切なメソッド名をつけることで、より直感的にデータを扱えるようになるので、使えるところは積極的にscopeを定義していきましょう。
もしscopeの使い方について忘れてしまったらこの記事を確認してくださいね!