Rails における値オブジェクトと ActiveRecord の composed_of

Rails の ActiveRecord には値オブジェクト (Value Object) を便利に使うための `composed_of` というものがあるらしいのでメモっとく。

# エンティティ (Entity) と値オブジェクト

エンティティと値オブジェクトはドメイン駆動設計におけるモデルを表現する3パターンの要素のうちの2つだ。

## エンティティ

本からエンティティの説明を引用しておこう。

> 多くのオブジェクトは、本質的に、その属性によってではなく、連続性と同一性 (identity) によって定義される。

例えば Person クラスがあり、その属性は firstname・lastname・age だとしよう。Person クラスの2つのインスタンスがあり、それらが同姓同名同年齢だったとしても、その属性が一致しているからといって同一の人であるとは言えない。なので Person クラスのインスタンスはエンティティということになる。

Rails では `ActiveRecord::Base` を継承しているクラスのインスタンスはほとんどがエンティティを表現することになる。ID で同一性を識別するからね。

## 値オブジェクト

本から値オブジェクトの説明を引用しておこう。

> 多くのオブジェクトには概念的な同一性がない。そういうオブジェクトは、物事の特徴を記述する。

例えば Location クラスがあり、その属性は country・city だとしよう。Location クラスの2つのインスタンスがあり、それらが同国同市だったとしたら、その属性が一致しているので同一の場所と言える。なので Location クラスのインスタンスは値オブジェクトということになる。

Rails には値オブジェクトを表現する専用の機能はないようだ。値オブジェクトを表現する方法は次項で説明する。

# 値オブジェクトの表現

Rails で値オブジェクトを表現する方法にはいくつかある。[DDD for Rails Developers. Part 2: Entities and Values](https://www.sitepoint.com/ddd-for-rails-developers-part-2-entities-and-values/) を参考にすると下記の3パターンがあるようだ。

* Struct のインスタンスを継承して表現する
* `ActiveRecord::Base` を継承して表現する
* Plain Old Ruby Objects (PORO) で表現する

今回は「Struct のインスタンスを継承して表現する」方法で実装してみる。なお「`ActiveRecord::Base` を継承して表現する」方法は [DDD for Rails Developers. Part 2: Entities and Values](https://www.sitepoint.com/ddd-for-rails-developers-part-2-entities-and-values/) に例がある。また「Plain Old Ruby Objects (PORO) で表現する」方法は [ActiveRecord::Aggregations::ClassMethods](http://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html) に例がある。

## Location クラス

`Location` クラスを `Struct` を利用して定義してみる。これで `Location` クラスのインスタンスは値オブジェクトだ。

```rb
class Location < Struct.new(:country, :city)
end
```

`irb` で試してみるとこんな感じ。

```rb
> tokyo = Location.new('Japan', 'Tokyo')
=> #<struct Location country="Japan", city="Tokyo">
> paris = Location.new('France', 'Paris')
=> #<struct Location country="France", city="Paris">
> edo = Location.new('Japan', 'Tokyo')
=> #<struct Location country="Japan", city="Tokyo">
> tokyo == paris
=> false
> tokyo == edo
=> true
```

クラスと属性が一致していれば同一になる。`Struct` の便利な使い道はこんなところにある。

## Person クラス

エンティティが値オブジェクトを持つ例を見るために `Person` クラスも定義してみる。ここで `Location` クラスのインスタンスが入る `location` インスタンス変数も定義しておいた。

```rb
class Person
  attr_accessor :firstname, :lastname, :age, :location

  def initialize(firstname, lastname, age, location)
    @firstname, @lastname, @age, @location = firstname, lastname, age, location
  end
end 
```

`irb` で試してみるとこんな感じ。

```rb
> tokyo = Location.new('Japan', 'Tokyo')
=> #<struct Location country="Japan", city="Tokyo">
> takuya = Person.new('Takuya', 'Tsuchida', 30, tokyo)
=> #<Person:0x007fa5538d42a8 @firstname="Takuya", @lastname="Tsuchida", @age=30, @location=#<struct Location country="Japan", city="Tokyo">>
> takuya.location == tokyo
=> true
```

問題なく動作している。

# composed_of

さて本題だ。ご存知のとおり ActiveRecord は O/R マッパーだ。ここまではオブジェクトモデルで考えてきたけど、ここからはリレーショナルモデルのことも考えなければならない。

ActiveRecord で前述の `Person` クラスを表現して永続化する場合には `Location` クラスのインスタンス（値オブジェクト）をどう扱うかが問題になる。この問題を Rails では `composed_of` というクラスメソッドで解決する。

この `composed_of` を用いた解決策は `Person` クラスの構成要素として `Location` クラスを埋め込んでしまうというものだ。`Location` クラスのインスタンス（値オブジェクト）をリレーショナルモデル上で `people` テーブルの `country` カラムと `city` カラムとして表現し、オブジェクトモデル上に戻ってきたときに `Location` クラスのインスタンス（値オブジェクト）として再構築する。これによって値オブジェクトを含むオブジェクトモデルとリレーショナルモデルの相互変換が実現できるということになる。

実例を見た方がわかりやすいと思うので、ここからはコードを見ていこう。

まずは `rails generate` で Person モデルを生成する。このときに `country` カラムと `city` カラムに対応するフィールドも指定しておこう。

```shell-session
$ rails g model person firstname:string lastname:string age:integer country:string city:string
$ rails db:migrate
```

`Location` クラスも作成しておこう。これは前述のものと全く同じクラスだ。

```rb:app/models/location.rb
class Location < Struct.new(:country, :city)
end
```

生成された `Person` クラスに `composed_of` で `location` フィールドを追加しよう。フィールド名から暗黙的に `Location` クラスが紐付けられる。`class_name` オプションで明示的にクラス名を指定することもできる。`mapping` オプションで「エンティティの属性」から「値オブジェクトの属性」への紐付けを指定する。また `mapping` オプションに指定した属性の順序で値オブジェクトのコンストラクタに属性が渡されるので属性の順序には注意してほしい。

```rb:app/models/person.rb
class Person ActiveRecord::Base
  composed_of :location, mapping: [ %w(country country), %w(city city) ]
end
```

`rails console` で試してみるとこんな感じになる。

```rb
irb(main):001:0> tokyo = Location.new('Japan', 'Tokyo')
=> #<struct Location country="Japan", city="Tokyo">
irb(main):002:0> takuya = Person.new(firstname: 'Takuya', lastname: 'Tsuchida', age: 30, location: tokyo)
=> #<Person id: nil, firstname: "Takuya", lastname: "Tsuchida", age: 30, country: "Japan", city: "Tokyo", created_at: nil, updated_at: nil>
irb(main):003:0> takuya.save
   (0.2ms)  begin transaction
  SQL (0.9ms)  INSERT INTO "people" ("firstname", "lastname", "age", "country", "city", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?)  [["firstname", "Takuya"], ["lastname", "Tsuchida"], ["age", 30], ["country", "Japan"], ["city", "Tokyo"], ["created_at", 2016-08-17 10:59:50 UTC], ["updated_at", 2016-08-17 10:59:50 UTC]]
   (0.9ms)  commit transaction
=> true
irb(main):004:0> tsuchida = Person.first
  Person Load (0.3ms)  SELECT  "people".* FROM "people" ORDER BY "people"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Person id: 2, firstname: "Takuya", lastname: "Tsuchida", age: 30, country: "Japan", city: "Tokyo", created_at: "2016-08-17 10:59:50", updated_at: "2016-08-17 10:59:50">
irb(main):005:0> takuya.location == tsuchida.location
=> true
```

`composed_of` の使い方を理解してもらえただろうか。

ちなみに `composed_of` の実装は本の下記の説明とリンクしていると思う。ただし、Rails では値オブジェクトをオブジェクトモデルとリレーショナルモデルの間で相互変換する方法であり、本ではパフォーマンスチューニングの方法である。

> 関係データベースでは、特定の値を独立したテーブルに入れて関連を生成するよりも、その値オブジェクトを所有するエンティティのテーブルに入れたいことがあるかもしれない。

なお「`ActiveRecord::Base` を継承して表現する」方法であれば `Person` クラスと `Location` クラスのアソシエーションとして解決できるはずだ。しかしながら、値オブジェクトを「Struct のインスタンスを継承して表現する」方法で実現し `composed_of` で ActiveRecord と関連させる方がコードの上ではシンプルだと思う。

---

今回は値オブジェクトを「Struct のインスタンスを継承して表現する」方法で実現し `composed_of` で ActiveRecord と関連させる方法について説明した。Rails に乗っかりつつもかっこいい設計を目指したいものだね。

# 参考文献

エリック・エヴァンスのドメイン駆動設計
https://www.sitepoint.com/ddd-for-rails-developers-part-2-entities-and-values/
http://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html