I’ve never used composed_of or seen in it used in any of the Rails apps I’ve worked on. The class method is very well documented. In this post I’ll explore this, for me, unused method which on the surface looks useful.

So, what is the use case for composed_of? The documentation says

Active Record implements aggregation through a macro-like class method called composed_of for representing attributes as value objects.

Aggregation in this case should not be confused with aggregate functions in SQL for doing things like COUNT(), AVG() and SUM(). A more clear definition could be that composed_of provides a way to represent a group of (one or more) DB columns as value objects.

Monetary values is a good use case for composed_of. Here’s a runnable test case:

# frozen_string_literal: true

begin
  require "bundler/inline"
rescue LoadError => e
  $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
  raise e
end

gemfile(true) do
  source "https://rubygems.org"

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  # gem "rails", github: "rails/rails"
  gem "rails", "~> 5.2"
  gem "sqlite3"
  gem "money", "~> 6.12"
end

require "active_record"
require "minitest/autorun"
require "logger"

# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :products, force: true do |t|
    t.integer :price_in_cents
    t.string :currency
  end
end

class Product < ActiveRecord::Base
  composed_of :price, class_name: "Money", mapping: [%w(price_in_cents fractional), %w(currency currency_as_string)]
end

class ProductTest < Minitest::Test
  def test_product_price_is_set
    product = Product.create!(price: Money.new(100_00, "USD"))

    assert_equal "USD", product.currency
    assert_equal 100_00, product.price_in_cents
  end

  def test_product_price_can_be_queried
    Product.delete_all
    Product.create!(price: Money.new(100_00, "USD"))
    Product.create!(price: Money.new(10_00, "EUR"))
    Product.create!(price: Money.new(100_00, "DKK"))
    Product.create!(price: Money.new(10_00, "DKK"))

    assert_equal 1, Product.where(price_in_cents: 100_00, currency: "DKK").count
    assert_equal 1, Product.where(price: Money.new(100_00, "DKK")).count
  end
end

The Rails docs has a similar example with money. Other useful examples where value objects could be introduced is with weight, length and temperature, basically any case where we have a value + unit.

Depending on your problem domain composed_of can also be useful for representing 2D or 3D coordinates as Points. Latitude and longitude coordinates are also a good candidate for use with composed_of.

Final thoughts

I’ll be more on the lookout for value objects in my code base as composed_of provides an easy way to expose them via DB columns.