[Rails]値オブジェクトとは?〜使い方と注意点を押さえる〜


はじめに

開発の規模が大きくなればなるほど、モデルが肥大化するFat Modelという問題が発生しやすくなります。そこで、値(Value)オブジェクトという設計手法を利用することでこの問題が解決出来ますので、利用方法と注意点をしっかり押さえて導入を検討してみましょう!

値オブジェクトとは?

値オブジェクトとは、読んで字の如く、「値」を表すオブジェクトなのですが、この値とはドメイン駆動設計という設計手法に登場する概念で、同一性を判断できない情報のことを指します。
値オブジェクトを利用することで、コードを綺麗に保ち見通しが良くなることで、テストが書きやすくなり、Fat Modelの問題を解消できます。また、オープン・クローズドの原則という、あるクラスを修正したとき、他のクラスに影響が出ない設計を実装できます。

山田さんと田中さんがいるとします。この2人の血液型はどちらもA型です。血液型が同じだからといって、同一人物となるわけではありません。
こういった、値が同じでも同一だと断定できない情報のことを値オブジェクトと呼びます。
※住所や名前なども同様です

前提条件

  • ユーザー(User)、管理者(Admin)という2つのクラスがあるとする
  • それぞれ名前として姓と名を持つ
  • 姓と名からフルネームを取得できる

使い方

それぞれのユーザーの名前を値オブジェクトにします。

1.名前を切り出したNameクラスを実装する。

app/values/name.rb
class Name
  attr_reader :first_name, :last_name

  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end

  def full_name
    "#{first_name} #{last_name}"
  end
end

2.それぞれのユーザーのクラスに値オブジェクトを使えるように設定する。

app/models/user.rb
class User < ApplicationRecord
  # 第一引数に値オブジェクト名、第二引数には属性のマッピング
  composed_of :name, mapping: [%w(first_name first_name), %w(last_name last_name)]
end
app/models/admin.rb
class Admin < ApplicationRecord
  # 第一引数に値オブジェクト名、第二引数には属性のマッピング
  composed_of :name, mapping: [%w(first_name first_name), %w(last_name last_name)]
end

利用例

Valueオブジェクトを用いた検索

users = User.where(name: Name.new('Taro', 'Yamada'))

ユースケース

  • 複数の値を組み合わせてひとつの値を算出するもの

    • 名前(姓・名・ミドルネーム)
    • 住所(国や県・市区町村など)
  • 値の表現や比較を行うもの

    • 日付
    • 通過
    • 気温
    • 商品レビューにおける星の数

注意点

  • 導入タイミングは、値オブジェクトを複数のクラスから利用することになったとき

  • app/values下に配置

  • ファイル名はValueオブジェクトの名前にする

    • name.rb
    • address.rb
  • 必要最低限のインタフェースのみを公開する

    • 公開したインタフェースに対するユニットテストを書く

終わりに

上記の通り、何もかも切り出して値オブジェクトとしても良いわけではないようですね。注意点をしっかり押さえることで、見通しが良いコードを実装出来るので、これからタイミングを見て使っていきましょう!

参考

Rails: Value Objectを検討してみよう(翻訳)

Railsのデザインパターン: Valueオブジェクト

composed_of を使って Rails で値オブジェクトを扱う