1
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?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-09-19

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

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

##結論

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

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

end

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

controllers/messages_controller.rb
class MessagesController < ApplicationController
  include Hoge

  def index
    fuga
  end
end

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

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

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

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

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

Image from Gyazo

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

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

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メニュー」と3種類のメニューを用意したい**です。

Image from Gyazo

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

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も付け足しましょう。

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

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

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

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

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

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

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

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

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

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

end

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

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

①インスタンスをもつことができない。
②継承できない。

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

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

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

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

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に記述してみます。

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という変数に置き換えました。

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

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

できました!
3つあった記述をひとつにまとめることができました!

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

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と3つのメソッドを1つにまとめることなどもできそうですね。)

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

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

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
after
class PlansController < ApplicationController
  include QuickActions
  before_action :create_plans, only: [:basic, :normal, :hard]

  def basic
  end

  def normal
  end

  def hard
  end
end

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

以上です。

1
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
1
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?