railsのコントローラーなどの記述が肥大化したときにconcernsディレクトリにファイルをつくって処理を切り出す方法を紹介します。
読みやすいコードになっているかは、ポートフォリオにおいて差をつけるポイントでもあるので参考にしてください。
##結論
controllers/concernsにファイルをつくって以下のように記述します。
(モデルの処理を切り出す場合は、models/concerns)
module Hoge
extend ActiveSupport::Concern
def fuga
#切り出したい処理
end
end
上記のようにコントローラーの処理をモジュールに切り出します。
そして、以下のようにコントローラーのファイルで呼び出せばOKです。
class MessagesController < ApplicationController
include Hoge
def index
fuga
end
end
上記のようにしてモジュールに切り出した処理を呼び出すことができます。
##リファクタリングの実践例
実際にどんな感じでリファクタリングするのかを具体例を出して説明します。
(※趣旨から外れる基礎的なルーティングの記述や保存処理の記述などの説明は省略しています。)
可読性の高いコードを書けるかは中級者以降で差がつくポイントですので、イメージをつかんでみてください。
今回以下のような機能をもったアプリで考えてみます。
ワンクリックで一週間分の運動メニューを保存できる機能をもったアプリです。
どのようにやっているのかコントローラーを確認してみます。
#~省略~
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種類のメニューを用意したい**です。
それぞれのボタンで違うのはメニュー内容だけなので、処理としてはさっきの記述をそのまま使えそうです。
#~省略~
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も付け足しましょう。
#~省略~
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
としました。)
module QuickActions
extend ActiveSupport::Concern
end
上記の記述で基本的な準備はできました。
**module(モジュール)は、処理のまとまりのことです。**クラスと同じような記述で定義できます。クラスとの違いは以下です。
①インスタンスをもつことができない。
②継承できない。
①に関しては、モジュールは「処理」のまとまりと理解すれば何ら問題はないと思います。
②に関しても、継承はできませんが、include モジュール名
とすることでクラスに取り込むことができます。
ActiveSupport::Concern
は、Railsにおいて共通した処理を切り出すために使われる機能と理解しておきましょう。includeするためにはこの記述が必要です。
さて、コントローラーから処理を切り出していきましょう。
現状のコントローラーの記述を改めて確認すると以下のようになっています。
#~省略~
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
に記述してみます。
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
という変数に置き換えました。
あとはこの変数に状況に応じた値が入るようにしてあげます。
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つあった記述をひとつにまとめることができました!
後は、これをコントローラーで呼び出すだけです。
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つにまとめることなどもできそうですね。)
みなさんも可読性が高く保守性の高い開発を目指しましょう!
最後にビフォーアフターを載せておきます。
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
class PlansController < ApplicationController
include QuickActions
before_action :create_plans, only: [:basic, :normal, :hard]
def basic
end
def normal
end
def hard
end
end
ファイル数が増えても、1つのファイルの記述を見やすくするということを意識していくといいと思います。
以上です。