【Ruby】クラウド勤怠におけるゴーストメソッド利用例

初めに

こんにちは。「マネーフォワード クラウド勤怠」のエンジニアをしています katuo です。自分はマネーフォワードに約10ヶ月前に入社し、同時にRubyを初めて業務で触ってから約10ヶ月が経ちました。

サーバーサイドの言語でいうと静的型付け言語であるGoを約3年ほど書いていたのですが、業務で使う動的型付け言語はRubyが初めてで諸々新鮮でした。そんな中、Rubyの勉強をしていくうちにRuby自体の言語仕様を活用したテクニックなどを知る場面が幾度もあり、今回はその中の1つの「ゴーストメソッド」について書いていきたいと思います。

ゴーストメソッドとは

例えば以下の図のような、Class Aを基底クラスとした継承関係を持つClass達が存在するとします。

ObjectからClassA, ClassB, ClassC が〇〇メソッドを定義していない場合、Callerが〇〇メソッドを呼び出そうとしたとき、NoMethodError等の例外が吐かれます。(メソッド探索で対象メソッドを発見できなかった場合に吐かれる例外)

メソッド探索の最中にmethod_missingメソッドを定義するとCallerによって呼び出されたメソッドが継承元まで遡っても見つからなかったときにmethod_missingメソッドが呼び出されます。(method_missingメソッドは派生クラスから基底クラスに向かって検索されます)

例えば↑のように継承チェーンの中のClass Bにmethod_missingが定義されており、Caller側から〇〇メソッドを呼び出した場合、method_missingで処理を拾うことができます。この原理を活用することで継承チェーンの中に定義されていないメソッドをCaller側(利用者側)が呼び出した時にあたかも定義されているかのように見せることができます。このような実際には存在しないが呼び出すことができるメソッドをゴーストメソッドと呼びます。

クラウド勤怠での利用例

では次にゴーストメソッドがクラウド勤怠でどのように利用されているのかについての一例をお話しします。クラウド勤怠が提供する機能の中に打刻、従業員などの様々なデータをCSVファイル形式で出力する機能があります。以下の図はCSVファイル出力を行う処理の一部を視覚化したものです。(※ 厳密なクラス図ではありません。矢印の向きは参照方向を表しています)

大雑把な処理の流れとして、MonthlyAttendanceItemCsvOptionまたはDailyAttendanceItemCsvExporterOptionCsvBuilder::Item(CSVファイルのデータ構造を規定したクラス)のインスタンスを集め、CsvBuilderにインスタンスに渡し、CsvBuilderbuildメソッドからCsvBuilder::Evaluatorevaluateメソッドを呼び出し、CsvBuilder::Itemのlambdaであるvalueを実行します。

今回解説するゴーストメソッド(method_missing)はCsvBuilder::Evaluatorクラスに定義されています。(※コードの一部抜粋)

class CsvBuilder
  class Evaluator < BasicObject
    # @param locals [Hash] evaluateで渡されたblockがアクセス可能な変数を定義する
    def initialize(locals = {})
      @locals = locals
    end

    # lambdaを評価する
    #
    # @param block [Lambda]
    # @return [any]
    def evaluate(&block)
      instance_exec(&block)
    end

    private

    def method_missing(method_name, *args)
      if @locals.key?(method_name) && args.size.zero?
        @locals[method_name]
      else
        super
      end
    end

    ...<略>

↑のmethod_missingが呼ばれるメカニズムとして、lambdaのvalueevaluateメソッド内の instance_exec を使って実行されたとき、lambda内に記述されているメソッドを見つけられず例外が吐かれ、これをmethod_missingがフックすることで呼び出されます。

※ lambdaのvalueのイメージが付きやすいように、実際のソースコードの一部添付しておきます。

CsvBuilder::Item.new(
  ...<略>

  value: -> { employee.employment_position_at(to)&.name }, // ※ employeeが見つからないと言う例外が発生 & method_missingでフックされる

  ...<略>
)

定義されているmethod_missingメソッド内部で行なっていることはシンプルで@localにlambdaであるvalueのメソッド(例: employee)が存在する場合はそのメソッド(例: employee)を返し、存在しない場合はsuperを実行します。(引数をつけずにsuperを実行した場合、NoMethodErrorの例外が吐かれます)

これらの構成により、実行時に呼び出されるvalue内のメソッドをCsvBuilderbuildメソッドのlocalを通じて動的に差し込むことができるようになり、将来的にクラウド勤怠がユーザーが設定した項目をCSVファイル出力できる動的な機能を追加する場面が来たときに比較的対応しやすい、拡張性&柔軟性が高い設計になっています。

最後に

ゴーストメソッドを利用すると、潜在的なバグを埋め込んでしまったり、可読性が下がってしまう(特に入門者などがコードを読む時)などのデメリットもあります。一方で記述するコードの量を減らせたり、柔軟性のあるコードも書けたりするメリットもあるのでその辺も各場面で判断して利用していきたいなと思います。 最後までお読みいただきありがとうございました。

またマネーフォワードではエンジニアの採用活動を続けております。もし興味があればこちらをどうぞ。

参考文献


マネーフォワードでは、エンジニアを募集しています。
ご応募お待ちしています。

【サイトのご案内】
マネーフォワード採用サイト
Wantedly
福岡開発拠点
京都開発拠点

【プロダクトのご紹介】
お金の見える化サービス 『マネーフォワード ME』 iPhone,iPad Android

ビジネス向けバックオフィス向け業務効率化ソリューション 『マネーフォワード クラウド』

おつり貯金アプリ 『しらたま』

お金の悩みを無料で相談 『マネーフォワード お金の相談』

だれでも貯まって増える お金の体質改善サービス 『マネーフォワード おかねせんせい』

金融商品の比較・申し込みサイト 『Money Forward Mall』

くらしの経済メディア 『MONEY PLUS』

Pocket