Rails

Rails.composed_of

More than 1 year has passed since last update.


はじめに

調べ物をしていたら、複数カラムをバリューオブジェクトに構成することで仮想的に一つのカラムとして扱う、composed_ofなるものを見つけたので備忘録します。


住所-よくあるパターン

class User

attr_accessor :city_address, :town_address, :building_address
def address
city_address + town_address + building_address
end
end

attr_accessorの部分は実際にはカラムになってると思ってください。

上のパターンにはいくつかの問題があります。



  • addressのような整形用メソッドがモデルを汚してしまう

  • 本来意味を持つのはaddressなのに、メソッドで返ってくるのは単なる文字列

  • 一旦addressを取得しても、そこからcity_addressを取り出すのは容易ではない


住所-改善されたパターン

根本的な問題は、addressは本当はAddressクラスのオブジェクトであるべきなのにDBの都合でそうなっていない、という点にあります。そこで、addressメソッドでAddressクラスのオブジェクトを返すような実装が思い浮かびます。

class User

attr_accessor :city_address, :town_address, :building_address
def address
Address.new(city_address, town_address, building_address)
end
end

class Address
attr_reader :city, :town, :building
def initialize(city, town, building)
@city, @town, @building = city, town, building
end
end

ここで、Addressクラスが単なるRubyオブジェクト(PORO, Plain Old Ruby Object)であることが重要です。これで、address自体が意味を持ちますし、cityの取り出しもできます。


住所-composed_ofを使うパターン

addressメソッドはいいのですが、どうにも単純なメソッドです。これはもしかして、 Rails Wayで楽ができるのではないでしょうか。

class User

attr_accessor :city_address, :town_address, :building_address
composed_of :address, mapping: [%w(city_address city), %w(town_address town), %w(building_address building)]
end

class Address
attr_reader :city, :town, :building
def initialize(city, town, building)
@city, @town, @building = city, town, building
end
end

composed_ofを使うことで、よりシンプルに書けるようになりました。

mappingオプションには、配列の配列を指定します。内側の配列は2要素で、まずカラム名を、次にバリューオブジェクトの属性名を指定します。これにより、user.city_addressuser.address.cityにマップされるようになります。


まとめ

composed_ofを使ってみたわけですが、実は重要なのはcomposed_ofではなく、バリューオブジェクトの方なのかな、と思ったりします。要は、アプリケーション内でそれ自体の意味があるものをそれ自体のクラスにくくりだすということです。

これは最近思っていることですが、「アプリケーション内でPOROが多く使われているほど、そのアプリケーションは見通しが良い」ということは考えられないでしょうか。この点についてはもっと熟達した方々の意見を待ちたいと思います。

【追記:参考資料】

http://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html

http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/