Alex's Slip-box

These are my org-mode notes in sort of Zettelkasten style

Value objects

:ID: F6ABC426-2ADF-405E-821A-6189532066DD

Sometimes I find myself coding business logic around object’s property. Depending on how much is involved, it might make sense to extract an object that represents the value to contain that business logic.

# Color example

I had for example an object with a color property, whose value is a color hex code. I wanted to know if the color was considered dark or light, the RGB representation and if it was the default color all the objects are instantiated with.

In this case, I created a ColorType value object

class ColorType
  DEFAULT = 'e4f2fe'.freeze

  attr_reader :hex

  def initialize(hex = nil)
    @hex = hex.presence || DEFAULT

  def to_rgb
    [r, g, b]

  def r = hex[0..1].to_i(16)
  def g = hex[2..3].to_i(16)
  def b = hex[4..5].to_i(16)

  def darkish?
    to_rgb.sum < (255 * 3) / 3.6

  def default?
    hex == DEFAULT

Which I then wrapped around the object’s color value (ActiveRecord object in this case)

class Memo < ApplicationRecord
  def color

Example usage.

irb(main):001> m =
irb(main):002> m.color
=> #<ColorType:0x0000ffff5fe732e0 @hex="e4f2fe">
irb(main):003> m.color.hex
=> "e4f2fe"
irb(main):004> m.color.to_rgb
=> [228, 242, 254]
irb(main):005> m.color.darkish?
=> false
irb(main):006> m.color.default?
=> true

Why do this? All these methods could just be added to the model, but it kinda begs for a separate object. Not only for the behavior encapsulation, but reusability if needed.

We could do more with this. Need equality comparison? That can added:

def ==(other)
  hex == other.hex

# Why value objects

  1. Clarity: Value objects provide a clear and expressive way to represent domain concepts, making the code more readable and easier to understand. For me this is the primary reason.
  2. Reusability: Value objects can be easily reused across different parts of the application, as they are self-contained and do not have any external dependencies.
  3. Immutability: Value objects should be immutable. This makes them thread-safe and easier to reason about, as they cannot be accidentally modified by other parts of the application.

# Some canonical examples

# Money

class Money
  attr_reader :amount

  def initialize(amount)
    @amount = amount

  def +(other) + other.amount)

  def -(other) - other.amount)

  def ==(other)
    amount == other.amount

# Address

class Address
  attr_reader :street, :city, :state, :zip

  def initialize(street, city, state, zip)
    @street = street
    @city = city
    @state = state
    @zip = zip

  def ==(other)
    street == other.street && city == && state == other.state && zip ==

  def to_s
    "#{street}, #{city}, #{state} #{zip}"

Search Results