Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails tips: Value Objectパターンでリファクタリング(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

Rails tips: Value Objectパターンでリファクタリング(翻訳)

Value Objectは、Null Objectパターンと同様Rubyの純粋なオブジェクトです。こうしたオブジェクトは値を表現しますが、ユーザーオブジェクトのようなシステム内で一意のオブジェクトを表現するためのものではありません。Valueオブジェクトは常に値だけを返しますが、Policy Objectのように論理値(true/false)だけを返すのではなく、他の値を返すこともできます(ほとんどの場合文字列です)。このパターンにおけるルールは、「シンプルに保つこと」と、「オブジェクトが生きている間は属性値をしないこと」です。

デモ

今回のリファクタリングパターンの基礎となるアイデアをよりわかりやすく示すために、サンプルのクラスを作ってみましょう。

class Report
  def initialize(emails:)
    @emails = emails
  end

  def data
    emails_data = []

    emails.each do |email|
      emails_data << {
        username: email.match(/([^@]*)/).to_s,
        domain: email.split("@").last
      }
    end

    emails_data
  end

  private
  attr_reader :emails
end

渡されたメールアドレスに対して以下を行います。

  1. メールアドレス値は変更しない
  2. 値を1つだけ返す
  3. 操作はプリミティブなオブジェクト上で行う

Value Objectを作る

上のロジックからValue Objectのクラスを作ると、以下のような感じになります。

class Email
  def initialize(email)
    @email = email
  end

  def username
    email.match(/([^@]*)/).to_s
  end

  def domain
    email.split("@").last
  end

  private
  attr_reader :email
end

Value Objectパターンでリファクタリングする

最終的に得られるのはきわめてシンプルなRubyオブジェクトなので、テストも理解も簡単です。Reportクラスをリファクタリングした結果、とても明快でシンプルなコードになりました。

class Report
  def initialize(emails: emails)
    @emails = emails
  end

  def data
    emails_data = []

    emails.each do |email|
      email_obj = Email.new(Email)

      emails_data << {
        username: email_obj.username,
        domain: email_obj.domain
      }
    end

    emails_data
 end

 private
 attr_reader :emails
end

最後のリファクタリング

Report#dataメソッドがシンプルになってロジックも分離されましたが、まだコードが多少長くなっています。Email#to_hメソッドを作成し、オブジェクトのハッシュ形式を返すようにします。これは私にとって非常に自然な書き方であり、Report#dataをワンライナーに書き換えることができます。

class Email
  def initialize(email)
    @email = email
  end

  def username
    email.match(/([^@]*)/).to_s
  end

  def domain
    email.split("@").last
  end

  def to_h
    { username: username, domain: domain }
  end

  private
  attr_reader :email
end
class Report
  def initialize(emails: emails)
    @emails = emails
  end

  def data
    emails.map { |email| Email.new(email).to_h }
  end

  private
  attr_reader :emails
end

Railsでお困りの方にお知らせ

知りたいことがありましたら、twitter または連絡用フォームにてお気軽にお問い合わせください。

RSpec & TDDの電子書籍を無料でダウンロード

もっと稼ぎたい方や会社をさらに発展させたい方へ: テスティングのスキルの重要性にお気づきでしょうか?テストを正しく書き始めることが、唯一のファーストステップです。無料でダウンロードいただける私の書籍『RSpec & Test Driven Developmentの無料ebook』をどうぞお役立てください。

関連記事

Railsアンチパターン: Decoratorの肥大化(翻訳)

肥大化したActiveRecordモデルをリファクタリングする7つの方法(翻訳)

Rails: テストのリファクタリングでアプリ設計を改良する(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。