【Rails】concernsを使ってリファクタリング！太ったコントローラーをスリムにする！

railsのコントローラーなどの記述が肥大化したときにconcernsディレクトリにファイルをつくって処理を切り出す方法を紹介します。

**読みやすいコードになっているかは、ポートフォリオにおいて差をつけるポイント**でもあるので参考にしてください。

##結論

controllers/concernsにファイルをつくって以下のように記述します。
(モデルの処理を切り出す場合は、models/concerns)

```rb:app/controllers/concerns/hoge.rb
module Hoge
  extend ActiveSupport::Concern
  
  def fuga
    #切り出したい処理
  end

end
```

上記のようにコントローラーの処理をモジュールに切り出します。
そして、以下のようにコントローラーのファイルで呼び出せばOKです。

```rb:controllers/messages_controller.rb
class MessagesController < ApplicationController
  include Hoge

  def index
    fuga
  end
end
```

上記のようにしてモジュールに切り出した処理を呼び出すことができます。


##リファクタリングの実践例

実際にどんな感じでリファクタリングするのかを具体例を出して説明します。
（※趣旨から外れる基礎的なルーティングの記述や保存処理の記述などの説明は省略しています。）

**可読性の高いコードを書けるかは中級者以降で差がつくポイント**ですので、イメージをつかんでみてください。



今回以下のような機能をもったアプリで考えてみます。




[![Image from Gyazo](https://i.gyazo.com/06185c5f23226950613455c8d0244d69.gif)](https://gyazo.com/06185c5f23226950613455c8d0244d69)

ワンクリックで一週間分の運動メニューを保存できる機能をもったアプリです。

どのようにやっているのかコントローラーを確認してみます。

```ruby:plans_controller.rb
#~省略~
  def basic 
    7.times do |x|
      plan1 = Plan.create(plan: "ウォーキング20分", date: (Date.today + x), user_id: current_user.id)
      plan2 = Plan.create(plan: "腕立て伏せ15回", date: (Date.today + x), user_id: current_user.id)
      plan3 = Plan.create(plan: "腹筋15回", date: (Date.today + x), user_id: current_user.id)
        plans = {plan1: plan1, plan2: plan2, plan3: plan3}
    end
    render json: {plans: plans}
  end
```

「Basicメニューで保存」を押したら、`plans_controller#basic`が動きます。

basicアクション内では、timesメソッドをつかって一週間分の運動メニューを保存するように処理を書いています。
（保存した運動メニューを非同期で表示するために、jsにjson形式で情報を送っています。）

これでボタン一つで一週間分の運動メニューを保存することができました。

さて、今回ですが**「Basicメニュー」「Normalメニュー」「Hardメニュー」と３種類のメニューを用意したい**です。

[![Image from Gyazo](https://i.gyazo.com/626cf6eff0bd8cb5034c3645f3a598fa.gif)](https://gyazo.com/626cf6eff0bd8cb5034c3645f3a598fa)

それぞれのボタンで違うのはメニュー内容だけなので、処理としてはさっきの記述をそのまま使えそうです。

```ruby:plans_controller.rb
#~省略~
  def basic 
    7.times do |x|
      plan1 = Plan.create(plan: "ウォーキング20分", date: (Date.today + x), user_id: current_user.id)
      plan2 = Plan.create(plan: "腕立て伏せ15回", date: (Date.today + x), user_id: current_user.id)
      plan3 = Plan.create(plan: "腹筋15回", date: (Date.today + x), user_id: current_user.id)
        plans = {plan1: plan1, plan2: plan2, plan3: plan3}
    end
    render json: {plans: plans}
  end

  #追加↓
  def normal 
    7.times do |x|
      plan1 = Plan.create(plan: "ウォーキング40分", date: (Date.today + x), user_id: current_user.id)
      plan2 = Plan.create(plan: "腕立て伏せ30回", date: (Date.today + x), user_id: current_user.id)
      plan3 = Plan.create(plan: "腹筋30回", date: (Date.today + x), user_id: current_user.id)
        plans = {plan1: plan1, plan2: plan2, plan3: plan3}
    end
    render json: {plans: plans}
  end
```

normalアクションを付け加えました。
これでbasicよりも厳しいメニューの保存もできます。
さらに、hardも付け足しましょう。


```ruby:plans_controller.rb
#~省略~
  def basic 
    7.times do |x|
      plan1 = Plan.create(plan: "ウォーキング20分", date: (Date.today + x), user_id: current_user.id)
      plan2 = Plan.create(plan: "腕立て伏せ15回", date: (Date.today + x), user_id: current_user.id)
      plan3 = Plan.create(plan: "腹筋15回", date: (Date.today + x), user_id: current_user.id)
        plans = {plan1: plan1, plan2: plan2, plan3: plan3}
    end
    render json: {plans: plans}
  end

  def normal 
    7.times do |x|
      plan1 = Plan.create(plan: "ウォーキング40分", date: (Date.today + x), user_id: current_user.id)
      plan2 = Plan.create(plan: "腕立て伏せ30回", date: (Date.today + x), user_id: current_user.id)
      plan3 = Plan.create(plan: "腹筋30回", date: (Date.today + x), user_id: current_user.id)
        plans = {plan1: plan1, plan2: plan2, plan3: plan3}
    end
    render json: {plans: plans}
  end

  def hard 
    7.times do |x|
      plan1 = Plan.create(plan: "ランニング40分", date: (Date.today + x), user_id: current_user.id)
      plan2 = Plan.create(plan: "腕立て伏せ60回", date: (Date.today + x), user_id: current_user.id)
      plan3 = Plan.create(plan: "腹筋60回", date: (Date.today + x), user_id: current_user.id)
        plans = {plan1: plan1, plan2: plan2, plan3: plan3}
    end
    render json: {plans: plans}
  end
```

これで３種類のメニューを用意できました。

しかし、**記述が重複していて、DRYの原則に反しています。**
コントローラーが太りまくっています。

このままではまずいので、リファクタリングしていきましょう。

###concernsを使って、リファクタリングしていこう！

さて、ここからが本題です。



concernsディレクトリにファイルをつくって処理を切り出し、コントローラーをスリムにしていきます。

**concernsとは、モジュールを置くディレクトリです。modelやcontrollerで使いたい処理をmodule化して共通で使えるようにできます。**

今回はコントローラー内の処理を切り出したいので、app/controllers/concernsにファイルをつくっていきます。(`quick_actions.rb`としました。)


```rb:app/controllers/concerns/quick_actions.rb
module QuickActions
  extend ActiveSupport::Concern

end
```

上記の記述で基本的な準備はできました。

**module(モジュール)は、処理のまとまりのことです。**クラスと同じような記述で定義できます。クラスとの違いは以下です。

```モジュール
①インスタンスをもつことができない。
②継承できない。
```
①に関しては、**モジュールは「処理」のまとまり**と理解すれば何ら問題はないと思います。
②に関しても、継承はできませんが、`include モジュール名`とすることでクラスに取り込むことができます。

`ActiveSupport::Concern`は、Railsにおいて共通した処理を切り出すために使われる機能と理解しておきましょう。includeするためにはこの記述が必要です。


さて、コントローラーから処理を切り出していきましょう。

現状のコントローラーの記述を改めて確認すると以下のようになっています。

```ruby:plans_controller.rb
#~省略~
  def basic 
    7.times do |x|
      plan1 = Plan.create(plan: "ウォーキング20分", date: (Date.today + x), user_id: current_user.id)
      plan2 = Plan.create(plan: "腕立て伏せ15回", date: (Date.today + x), user_id: current_user.id)
      plan3 = Plan.create(plan: "腹筋15回", date: (Date.today + x), user_id: current_user.id)
        plans = {plan1: plan1, plan2: plan2, plan3: plan3}
    end
    render json: {plans: plans}
  end

  def normal 
    7.times do |x|
      plan1 = Plan.create(plan: "ウォーキング40分", date: (Date.today + x), user_id: current_user.id)
      plan2 = Plan.create(plan: "腕立て伏せ30回", date: (Date.today + x), user_id: current_user.id)
      plan3 = Plan.create(plan: "腹筋30回", date: (Date.today + x), user_id: current_user.id)
        plans = {plan1: plan1, plan2: plan2, plan3: plan3}
    end
    render json: {plans: plans}
  end

  def hard 
    7.times do |x|
      plan1 = Plan.create(plan: "ランニング40分", date: (Date.today + x), user_id: current_user.id)
      plan2 = Plan.create(plan: "腕立て伏せ60回", date: (Date.today + x), user_id: current_user.id)
      plan3 = Plan.create(plan: "腹筋60回", date: (Date.today + x), user_id: current_user.id)
        plans = {plan1: plan1, plan2: plan2, plan3: plan3}
    end
    render json: {plans: plans}
  end
```
メソッドbasic・normal・hardは、ほとんどの記述が共通しています。
**違うのは「ウォーキング20分」などのplanキーのバリューとなっている運動メニューの部分だけです。**後は全部同じです。

ということは、ここの部分を変数にしてあげれば、ひとつにまとめられそうです！
`concerns/quick_actions.rb`に記述してみます。

```ruby:concerns/quick_actions.rb
module QuickActions
  extend ActiveSupport::Concern
  
  def create_plans #←basic,normal,hardをまとめてcreate_plansというメソッド名にしています。
    7.times do |x|
      plan1 = Plan.create(plan: menu1 ←ここを変数化, date: (Date.today + x), user_id: current_user.id)
      plan2 = Plan.create(plan: menu2 ←ここを変数化, date: (Date.today + x), user_id: current_user.id)
      plan3 = Plan.create(plan: menu3 ←ここを変数化, date: (Date.today + x), user_id: current_user.id)
      plans = {plan1: plan1, plan2: plan2, plan3: plan3}
    end
    render json: {plans: plans}
  end  

end
```

planキーに対するバリューを`menu1``menu2``menu3`という変数に置き換えました。

あとはこの変数に状況に応じた値が入るようにしてあげます。

```ruby:concerns/quick_actions.rb
module QuickActions
  extend ActiveSupport::Concern
  
  def create_plans 
   
   #条件によって変数の値を変える
   if params[:action] == "basic"
      menu1 = "ウォーキング20分"
      menu2 = "腕立て伏せ15回"
      menu3 = "腹筋15回"
    elsif params[:action] == "normal"
      menu1 = "ランニング20分"
      menu2 = "腕立て伏せ30回"
      menu3 = "腹筋30回"
    else
      menu1 = "ランニング40分"
      menu2 = "腕立て伏せ60回"
      menu3 = "腹筋60回"
    end

    7.times do |x|
      plan1 = Plan.create(plan: menu1, date: (Date.today + x), user_id: current_user.id)
      plan2 = Plan.create(plan: menu2, date: (Date.today + x), user_id: current_user.id)
      plan3 = Plan.create(plan: menu3, date: (Date.today + x), user_id: current_user.id)
      plans = {plan1: plan1, plan2: plan2, plan3: plan3}
    end
    render json: {plans: plans}
  end  

end
```

できました！
**３つあった記述をひとつにまとめることができました！**

後は、これをコントローラーで呼び出すだけです。

```rb:plans_controller.rb
class PlansController < ApplicationController
  include QuickActions
  before_action :create_plans, only: [:basic, :normal, :hard]

  def basic
  end

  def normal
  end

  def hard
  end
end
```

コントローラーが別人のようにスリムになりました！！

`include QuickActions`こちらの記述で先程つくったモジュールを取り込んでいます。
そして、`before_action :create_plans`で処理を実行しています。

（さらに、basic・normal・hardと３つのメソッドを１つにまとめることなどもできそうですね。）



みなさんも可読性が高く保守性の高い開発を目指しましょう！

最後にビフォーアフターを載せておきます。

```ruby:before
class PlansController < ApplicationController

  def basic 
    7.times do |x|
      plan1 = Plan.create(plan: "ウォーキング20分", date: (Date.today + x), user_id: current_user.id)
      plan2 = Plan.create(plan: "腕立て伏せ15回", date: (Date.today + x), user_id: current_user.id)
      plan3 = Plan.create(plan: "腹筋15回", date: (Date.today + x), user_id: current_user.id)
        plans = {plan1: plan1, plan2: plan2, plan3: plan3}
    end
    render json: {plans: plans}
  end

  def normal 
    7.times do |x|
      plan1 = Plan.create(plan: "ウォーキング40分", date: (Date.today + x), user_id: current_user.id)
      plan2 = Plan.create(plan: "腕立て伏せ30回", date: (Date.today + x), user_id: current_user.id)
      plan3 = Plan.create(plan: "腹筋30回", date: (Date.today + x), user_id: current_user.id)
        plans = {plan1: plan1, plan2: plan2, plan3: plan3}
    end
    render json: {plans: plans}
  end

  def hard 
    7.times do |x|
      plan1 = Plan.create(plan: "ランニング40分", date: (Date.today + x), user_id: current_user.id)
      plan2 = Plan.create(plan: "腕立て伏せ60回", date: (Date.today + x), user_id: current_user.id)
      plan3 = Plan.create(plan: "腹筋60回", date: (Date.today + x), user_id: current_user.id)
        plans = {plan1: plan1, plan2: plan2, plan3: plan3}
    end
    render json: {plans: plans}
  end
end
```

```rb:after
class PlansController < ApplicationController
  include QuickActions
  before_action :create_plans, only: [:basic, :normal, :hard]

  def basic
  end

  def normal
  end

  def hard
  end
end
```

**ファイル数が増えても、１つのファイルの記述を見やすくする**ということを意識していくといいと思います。

以上です。