Rails で Value Object パターンを扱うのに便利な composed_ofについて調べた。

オプション

  • class_name: Value Objectの名前。Value Object生成時にこのクラスが使われる
  • mapping: ActiveRecord Object <=> Value Object の相互変換に使われるattributeのマッピング。複数の値をValue Objectに展開する場合 Array of Arrayの形式でここを渡す
  • constructor: ActiveRecord Object => Value Object に展開するときのメソッド
  • converter: Setterとして値をセットしたときに使われる convert メソッド

CIDR クラスをValue Objectとして利用する例

dspinhirne/netaddr-rb: A Ruby library for performing calculations on IPv4 and IPv6 subnets.

class NetworkResource < ActiveRecord::Base
  composed_of :cidr,
    class_name: 'NetAddr::CIDR',
    mapping: [ %w(network_address network), %w(cidr_range bits) ],
    allow_nil: true,
    constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
    converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
end


# This calls the :constructor
network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24)

# These assignments will both use the :converter
network_resource.cidr = [ '192.168.2.1', 8 ]
network_resource.cidr = '192.168.0.1/24'

# This assignment won't use the :converter as the value is already an instance of the value class
network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8')

# Saving and then reloading will use the :constructor on reload
network_resource.save
network_resource.reload