こんにちは!
最近 リファクタリング Rubyエディション を読み始めました。本書は、復刻版が最近発売された名著でリファクタリングの様々なパターンをRubyのサンプルコードと共に紹介しています。
紹介されているリファクタリングのパターンの数は膨大なので、いいなと思ったパターンを1つずつブログにまとめています。
今回はその第2弾で「モジュールの抽出」についてまとめます。
第1弾の「委譲の隠蔽」のまとめはこちらです。
委譲の隠蔽【リファクタリング Rubyエディションまとめ1】 - 銀行員からのRailsエンジニア
リファクタリング前のコード
早速サンプルコードを見ていきましょう。
こちらはリファクタリング前のコードです。
ItemクラスとUserクラスで、インスタンスの保存の前にデフォルトの名前を設定しています。
class Item < ApplicationRecord #・・・ before_save :set_default_name def set_default_name self.name = default_name end #・・・ end class User < ApplicationRecord #・・・ before_save :set_default_name def set_default_name self.name = default_name end #・・・ end
このコードの問題点は、言うまでもなくコードが重複している点です。
ただ、set_default_name というメソッドは、Itemクラス・Userクラスそれぞれのインスタンスを更新しているため、別のクラスに共通のメソッドを作ってそのクラスのインスタンスに処理を委譲するというやり方ではうまくいきません。
今回のような、共通化したいコードがそれぞれのクラスでしか意味を持たないケースで「モジュールの抽出」は有効です。
モジュールの抽出を使ったリファクタリング
モジュールの抽出を使ったリファクタリング後のコードはこのようになります。
Itemクラス、Userクラスでは新しく作った SetDefaultNameモジュールをincludeしているだけでとてもシンプルですよね。
module SetDefaultName def self.included(klass) # ① klass.class_eval do # ② before_save :set_default_name end end def set_default_name self.name = default_name end end class Item < ApplicationRecord #・・・ include SetDefaultName #・・・ end class User < ApplicationRecord #・・・ include SetDefaultName #・・・ end
① includedメソッド は、モジュール(今回だとSetDefaultName)が include された時に、include をしたクラス(今回だと Item, User)を引数に実行されるメソッドです。
② class_evalメソッド は、ブロックに記載したメソッドをレシーバ(このコードだと klass)内に定義してくれるメソッドです。
つまり self.included メソッドでは、SetDefaultName モジュールが include されるタイミングで、include したクラスに before_save :set_default_name を定義しているのです。
「モジュールの抽出」という名前は、単に共通の処理をモジュールにして抽出していることを表しており、シンプルで覚えやすいですね。
おわりに
ここまでお読みいただきありがとうございます。
includedメソッドを使ってモジュールが include されたタイミングでメソッドを定義する方法は、知っておかないとこの方法を使っているコードを読むのが難しいので、覚えておいて損はないと思います。
このブログのサンプルコードは、全く同じではないもののリファクタリング Rubyエディションのサンプルコードを参考にさせていただきました。本書の中では、さらに丁寧に解説されていてとても分かりやすいので、ご興味ある方は是非ご覧ください。
- 作者:ジェイ・フィールズ,シェーン・ハービー,マーティン・ファウラー
- 発売日: 2020/03/21
- メディア: 単行本
次回はまた違うパターンをまとめていきます!
(追記)
第3弾として「継承から委譲へ」についてまとめました。
是非合わせてご覧ください。
ysk-pro.hatenablog.com