Refactoring Ruby on Rails

Ruby on Rails / Refactoring – value object pattern

Ruby on Rails / Refactoring – value object pattern January 31, 20183 Comments

Similar to null object pattern, Value Object is a Ruby plain object. Such object represents a value but not something unique in your system like a user object. Value Objects always return only values. They are like policy objects but instead of boolean values they can return also other values, mostly strings. The rule of this pattern is to keep it simple and don’t change attributes value during object life cycle.

Demonstration

To better demonstrate the idea behind this refactoring pattern let’s build example class:

class Report
  def initialize(emails:)
    @emails = emails
  end
  
  def data
    emails_data = []
    
    emails.each do |email|
      emails_data << {
        username: email.match(/([^@]*)/).to_s,
        domain: email.split("@").last
      }
    end
    
    emails_data
  end
  
  private
  attr_reader :emails
end

We are doing a few things with given email:

1. We don’t change email value
2. We return only values
3. We are operating on a primitive object

Building a value object

If we would build a value object from above logic, our class would look like this:

class Email
  def initialize(email)
    @email = email
  end
  
  def username
    email.match(/([^@]*)/).to_s
  end
  
  def domain
    email.split("@").last
  end
  
  private
  attr_reader :email
end

Refactoring with the value object pattern

We end up with a very simple Ruby object, easy to test and understand. After refactoring Report class final solution is quite clear and simple:

class Report
  def initialize(emails: emails)
    @emails = emails
  end
      
  def data
    emails_data = []
        
    emails.each do |email|
      email_obj = Email.new(Email)
          
      emails_data << {
        username: email_obj.username,
        domain: email_obj.domain
      }
    end
        
    emails_data
 end
      
 private
 attr_reader :emails
end

Final refactoring

Since our Report#data method is simpler now and logic is separated it’s still quite long. Let’s create #to_h method in Email report that would return hash representation of the object. For me it’s very natural and allows us to transform Report#data to one-liner:

class Email
  def initialize(email)
    @email = email
  end
  
  def username
    email.match(/([^@]*)/).to_s
  end
  
  def domain
    email.split("@").last
  end
  
  def to_h
    { username: username, domain: domain }
  end
  
  private
  attr_reader :email
end
class Report
  def initialize(emails: emails)
    @emails = emails
  end
  
  def data
    emails.map { |email| Email.new(email).to_h }
  end
  
  private
  attr_reader :emails
end

Do you want to read about given topic?

Hit me on twitter or use contact form and let me know!

Download free RSpec & TDD ebook

Do you want to earn more or jump to the next level in your company? Do you know that testing skills are one of the most desired skills? There is only first step: start testing and do it right. My ebook can help you. Subscribe to the newsletter to get a free copy of the book.

3 comments

Leave a Reply

Your email address will not be published. Required fields are marked *