How to convert a Ruby primitive to a custom domain object in two minutes

You already know why you want to use domain objects instead of Ruby’s built-in primitives… but what do you do if you’ve already used strings and integers all over the place? Did you code yourself into a corner? Are you going to have to change lots of code, and potentially break things? Of course not! You can convert a Ruby primitive into a custom domain object in just a few minutes… here’s how you do it:

  1. Wrap the primitive with a new custom object, and define ==
  2. Replace equality comparisons with domain methods

Example: Convert a Ruby primitive into a custom domain object

We’ll start with a simple class that fetches an HTTP page:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
require 'net/http'

class PageFetcher
  def fetch(url)
    @response = Net::HTTP.get_response URI(url)
    @response.code
  end
end

describe PageFetcher do
  describe '#fetch(url)' do
    let(:response) { double('response', code: '200') }

    before do
      allow(Net::HTTP).to receive(:get_response)
        .and_return(response)
    end

    it 'returns the status code' do
      expect(PageFetcher.new.fetch('http://example.com')).to eq('200')
    end
  end
end

It just fetches the response using Net::HTTP and returns the status code. Now, let’s refactor the String primitive to a custom domain object!

Step 1: Wrap the primitive with a new custom object, and define ==

Simply add a new class, and use it to wrap the return value. You won’t even have to change any tests! Check it out…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
require 'net/http'

class PageFetcher
  def fetch(url)
    @response = Net::HTTP.get_response URI(url)
    Response.new @response
  end

  class Response
    def initialize(response)
      @response = response
    end

    def ==(other)
      @response.code == other
    end
  end
end

describe PageFetcher do
  describe '#fetch(url)' do
    let(:response) { double('response', code: '200') }

    before do
      allow(Net::HTTP).to receive(:get_response)
        .and_return(response)
    end

    it 'returns the status code' do
      expect(PageFetcher.new.fetch('http://example.com')).to eq('200')
    end
  end
end

Step 2. Replace equality comparisons with domain methods

Now that you’re returning a custom object, you can introduce domain methods. You just encapsulate the original comparison in a method. Easy!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
require 'net/http'

class PageFetcher
  def fetch(url)
    @response = Net::HTTP.get_response URI(url)
    Response.new @response
  end

  class Response
    def initialize(response)
      @response = response
    end

    def success?
      @response.code == '200'
    end
  end
end

describe PageFetcher do
  describe '#fetch(url)' do
    let(:response) { double('response', code: '200') }

    before do
      allow(Net::HTTP).to receive(:get_response)
        .and_return(response)
    end

    it 'is successful' do
      expect(PageFetcher.new.fetch('http://example.com')).to be_success
    end
  end
end

Let your new object attract behavior

Now that you have a new domain object, you’ve got a great place to put related behavior! Try it and see what happens…

If you thought THAT was valuable, I've got more where that came from.

Sign up for my mailing list to make sure you never miss out.