スライドショー

【Rails入門】同じscopeを書くならActiveSupport::Concernを使おう

Ruby on Rails(以降、Rails)に、ActiveSupport::Concernというモジュールがあることを知っていますか?

ActiveSupport::Concernは、ModelやControllerのコードを分割するときに使えるモジュールです。

複数のModelやControllerで書かなくてはいけなくなった同じコードを、別のモジュールに分割してまとめることもできます。

他方、ActiveSupport::Concernの使いどころがわかりにくかったり、使いすぎると逆にコードがわかりにくくなったりするという問題もあります。

この記事では、分かりやすい例としてActiveSupport::Concernを使ってscopeをまとめる方法を説明します。

ぜひ試してみてください!

scopeとは

scopeって何だっけ?というところから始めましょう。

scopeは、特定のSQL文をメソッド化するための構文です。

Modelに以下のようなコードを追加すると…

  scope :birth_before, ->(date) { where("birth < ?", date) }

以下のような使いかたで、1990/1/1より前に産まれた人を抽出できます。

User.birth_before("1990-01-01")

scopeについては、以下の記事で詳しく説明していますので、ぜひご覧ください。

【Rails入門】scopeの使い方まとめ
更新日 : 2017年7月15日

ActiveSupport::Concernとは

ActiveSupport::Concernは、ModelやControllerのコードを分割するときに使えるモジュールです。

言い方を変えると、ControllerやModelのコードをモジュール化するために使います。

Viewの場合は部分テンプレート(partial)を使って小分けにしますが、ControllerやModelの場合はActiveSupport::Concernを使って小分けにするイメージです。

以下の記事では、Banana Controllerの一部をBananaOneモジュールに切り出す例を紹介しました。

【Rails入門】Controllerでよく使われる機能を紹介!
更新日 : 2018年8月21日

この記事では、もう少し実践的な使いかたを見ていきましょう。

動作を理解するためのWebアプリを作成する

scopeとActiveSupport::Concernの雰囲気をつかんだところで、動作を確認するために、Rails 5.1をインストールしてWebアプリを作りましょう。

(1)Railsをインストールします。

私は、以下の記事を参考に、VirtualBoxで作成した仮想パソコンにインストールしたLinux Mintに、Railsの開発環境を作成しました。

基本的には記事の手順に従って操作しますが、app/samurai/sample1ディレクトリを作成する代わりに、app/samurai/concern-demoディレクトリを作成しました。

Railsを起動して、ブラウザで画面が表示されることを確認したら、いったんRailsを終了してから次に進みます。

初心者でもかんたん!Ruby on Rails の開発環境の構築手順(Mac/Windows 両対応)
更新日 : 2019年8月9日

Linux Mintのインストールについては、以下の記事で詳しく説明しています。

Linux Mint Cinnamonエディションを使ってみよう
更新日 : 2019年12月5日

(2)Gemfileの最終行に以下の内容を追記します。

gem 'hirb'
gem 'hirb-unicode'

Hirbについては、以下の記事で詳しく説明していますので、あわせてご覧ください。

【Rails入門】初心者が知っておくべき3つのgem
更新日 : 2019年8月9日

(3)新しい「端末」を起動して、以下のコマンドを1行ずつ順番に入力します。

cd app/samurai/concern-demo
bundle install
bin/rails generate scaffold User name:string birth:date check:boolean
bin/rails generate scaffold Post user_id:integer title:string month:integer check:boolean
bin/rails db:migrate
bin/rails console

(4)以下のコードを1行ずつ順番に入力します。

User.create(name:"山田太郎", birth:"1991-09-22", check: false)
User.create(name:"長瀬来", birth:"1991-10-18", check: false)
User.create(name:"立川裕美", birth:"1968-06-28", check: false)
User.create(name:"前田達郎", birth:"1984-10-12", check: false)
User.create(name:"細川修二", birth:"1971-03-24", check: false)
User.create(name:"木村拓磨", birth:"1995-05-19", check: false)
Post.create(user_id:5, title:"楽しい休日の過ごし方", month:3, check: false)
Post.create(user_id:1, title:"先日の旅行での話", month:2, check: false)
Post.create(user_id:3, title:"昨日の出来事", month:12, check: false)
Post.create(user_id:3, title:"山登りに行きました", month:8, check: true)
Post.create(user_id:4, title:"友人が結婚しました", month:4, check: false)
Post.create(user_id:2, title:"最近少し気になったこと", month:1, check: true)
Post.create(user_id:4, title:"ランニングのコツ", month:9, check: false)
Post.create(user_id:3, title:"Ruby on Railsの日", month:9, check: true)
exit

UserテーブルとPostテーブルにデータが入力され、Railsコンソールが終了します。

これで準備ができました。

scopeを使ってみよう

まずは、ActiveSupport::Concernは脇に置いて、scopeを使ってみましょう。

(1)app/models/user.rbを編集します。

変更前:

class User < ApplicationRecord
end

変更後:

class User < ApplicationRecord
  scope :birth_before, ->(date) { where("birth < ?", date) }
end

(2)「端末」で、以下のコマンドを入力します。

bin/rails console

(3)以下のコードを1行ずつ順番に入力します。

Hirb.enable
User.birth_before("1990-01-01")

実行結果:

  User Load (1.4ms)  SELECT "users".* FROM "users" WHERE (birth < '1990-01-01')
+----+----------+------------+-------+-------------------------+-------------------------+
| id | name     | birth      | check | created_at              | updated_at              |
+----+----------+------------+-------+-------------------------+-------------------------+
| 3  | 立川裕美 | 1968-06-28 | false | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
| 4  | 前田達郎 | 1984-10-12 | false | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
| 5  | 細川修二 | 1971-03-24 | false | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
+----+----------+------------+-------+-------------------------+-------------------------+
3 rows in set

確かに、1990/1/1より前に産まれた人だけが抽出されていますね。

scopeで定義したメソッドは、update_allなどと組み合わせて使えます。

update_allについては、以下の記事で説明していますので、あわせてご覧ください。

【Rails入門】update_allでレコードをサクッと更新する
更新日 : 2018年8月21日

(4)以下のコードを1行ずつ順番に入力します。

User.birth_before("1990-01-01").update_all(check: true)
User.all

実行結果:

  User Load (0.2ms)  SELECT "users".* FROM "users"
+----+----------+------------+-------+-------------------------+-------------------------+
| id | name     | birth      | check | created_at              | updated_at              |
+----+----------+------------+-------+-------------------------+-------------------------+
| 1  | 山田太郎 | 1991-09-22 | false | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
| 2  | 長瀬来   | 1991-10-18 | false | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
| 3  | 立川裕美 | 1968-06-28 | true  | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
| 4  | 前田達郎 | 1984-10-12 | true  | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
| 5  | 細川修二 | 1971-03-24 | true  | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
| 6  | 木村拓磨 | 1995-05-19 | false | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
+----+----------+------------+-------+-------------------------+-------------------------+
6 rows in set

1990/1/1より前に産まれた人のchecktrueに変わりました。

(5)以下のコードを入力します。

exit

Railsコンソールが終了します。

birth_beforeは、app/models/user.rb(User Model)に追加したため、別のModel(たとえば、app/models/post.rb)で使用する場合は、あらためてapp/models/post.rbを編集する必要があります。

ActiveSupport::Concernを使ってみよう

ここからは、複数のModelで同じscopeを使いたい場合は、ActiveSupport::Concernを使おうという話をします。

上で説明した誕生日のように、User Model特有のカラムを扱う場合は、app/models/user.rb(User Model)に直接書くことは正しい対応です。

一方、複数のモデルで共通の処理がある場合は、ActiveSupport::Concernを使ってモジュール化するほうが正しい対応でしょう。

ここでは、User ModelにもPost Modelにも存在するcheckを対象にしたscopeを書いてみましょう。

具体的には、checkがtrueのデータだけを表示するscopeを作成します。

(1)app/models/concerns/common_scope.rbを以下の内容で作成します。

module CommonScope
  extend ActiveSupport::Concern

  included do
    scope :check, ->(bool){ where(check: bool) }
  end
end

(2)app/models/post.rbを編集します。

変更前:

class Post < ApplicationRecord
end

変更後:

class Post < ApplicationRecord
  include CommonScope
end

(3)app/models/user.rbを編集します。

変更前:

class User < ApplicationRecord
  scope :birth_before, ->(date) { where('birth < ?', date) }
end

変更後:

class User < ApplicationRecord
  include CommonScope
  scope :birth_before, ->(date) { where('birth < ?', date) }
end

(4)「端末」で、以下のコマンドを入力します。

bin/rails console

(5)以下のコード1行ずつ順番に入力します。

Hirb.enable
User.check(true)

実行結果:

  User Load (0.1ms)  SELECT "users".* FROM "users" WHERE "users"."check" = ?  [["check", "t"]]
+----+----------+------------+-------+-------------------------+-------------------------+
| id | name     | birth      | check | created_at              | updated_at              |
+----+----------+------------+-------+-------------------------+-------------------------+
| 3  | 立川裕美 | 1968-06-28 | true  | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
| 4  | 前田達郎 | 1984-10-12 | true  | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
| 5  | 細川修二 | 1971-03-24 | true  | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
+----+----------+------------+-------+-------------------------+-------------------------+
3 rows in set

app/models/concerns/common_scope.rbで定義したscopeが使えていますね。

Post Modelにもcheck(true)が使えるか確認するために、まずはPost Modelの全レコードを確認します。

(6)以下のコードを入力します。

Post.all

実行結果:

  Post Load (0.2ms)  SELECT "posts".* FROM "posts"
+----+---------+------------------------+-------+-------+-------------------------+-------------------------+
| id | user_id | title                  | month | check | created_at              | updated_at              |
+----+---------+------------------------+-------+-------+-------------------------+-------------------------+
| 1  | 5       | 楽しい休日の過ごし方   | 3     | false | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
| 2  | 1       | 先日の旅行での話       | 2     | false | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
| 3  | 3       | 昨日の出来事           | 12    | false | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
| 4  | 3       | 山登りに行きました     | 8     | true  | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
| 5  | 4       | 友人が結婚しました     | 4     | false | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
| 6  | 2       | 最近少し気になったこと | 1     | true  | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
| 7  | 4       | ランニングのコツ       | 9     | false | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
| 8  | 3       | Ruby on Railsの日      | 9     | true  | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
+----+---------+------------------------+-------+-------+-------------------------+-------------------------+
8 rows in set

checkを見ると、trueのレコードとfalseのレコードがありますね。

さて、check(true)が使えるでしょうか。

(7)以下のコードを入力します。

Post.check(true)

実行結果:

  Post Load (0.1ms)  SELECT "posts".* FROM "posts" WHERE "posts"."check" = ?  [["check", "t"]]
+----+---------+------------------------+-------+-------+-------------------------+-------------------------+
| id | user_id | title                  | month | check | created_at              | updated_at              |
+----+---------+------------------------+-------+-------+-------------------------+-------------------------+
| 4  | 3       | 山登りに行きました     | 8     | true  | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
| 6  | 2       | 最近少し気になったこと | 1     | true  | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
| 8  | 3       | Ruby on Railsの日      | 9     | true  | 2018-07-26 23:41:42 UTC | 2018-07-26 23:41:42 UTC |
+----+---------+------------------------+-------+-------+-------------------------+-------------------------+
3 rows in set

確かにPost Modelに対してもscopeが使えています。

まとめ

今回は、scopeの使いかたをおさらいしてから、ActiveSupport::Concernの使いかたを紹介しました。

ActiveSupport::Concernを使うと、ControllerやModelを小分けにできることも説明しました。

この記事では、複数のModelで共通のscopeを定義しましたが、ControllerでもActiveSupport::Concernを使えますので、ぜひ挑戦してみてください!

ただ、ModelでもControllerでも、小分けにすることで、どこで何が定義されているのか分かりにくくなってしまうという問題もあります。

特に複数人で開発しているときには、無闇に小分けにして開発効率が落ちないように注意しましょう。

いろいろなパターンで試してみて、ちょうどよい方法を見つけてください!

LINEで送る
Pocket

無料でSEからWebエンジニアへ転職しませんか?



侍エンジニア塾では、完全未経験の方から現在SEだけどプログラミングはやっていないという経験者まで、幅広い方々の人生を好転させるプログラミング指導を行ってきました。SEの方とお話していくなかで、

  • システムエンジニアという職業だけどコードが書けない
  • 事務作業が多くスキルがないため将来が不安
  • スクールに通うと完全未経験者と同じスタートになるからレベルが合わない
という、すでに知識があるSEならではのお悩みがあることに気づきました。そんな方におすすめなのが、弊社の「転職コース 」です。

弊社では、マンツーマンでレッスンを行いますので、現在お持ちの知識レベルからカリキュラムを作成いたします。さらにこちらの転職コースは無料で受講を始められて転職成功でそのまま卒業できるというとてもお得なコースとなっています。

既に知識のあるSEといっても転職は年齢が若いほど受かりやすいため、まずは無料体験レッスンで今の現状や理想の働き方について一緒に考えていきましょう。

まずは無料体験レッスンを予約する

書いた人

侍テック編集部

侍テック編集部

おすすめコンテンツ

あなたにぴったりなプログラミング学習プランを無料で診断!

プログラミング学習の効率を劇的に上げる学習メソッドを解説