Converting data structures to other data structures

443 views Asked by At

I often need to convert some kind of data into other data (usually strings, arrays and hashes). For example:

  • input: "a;simple;list"
  • expected_output: [ { content: "a", length: 1 }, { content: "simple", length: 6 }, { content: "list", length: 4 } ]

This can be done by:

input.split(";").map{|s| { content: s, length: s.size } }

but I want to use conversion at different places. So I need to provide encapsulated logic to achieve and reuse this. Three ways pop into mind:

  1. Use a helper method (put a single method into a module):

    module Converter
      extend self
      def convert(input)
        input.split(";").map{|s| { content: s, length: s.size } }
      end
    end
    
    module Caller
      Converter.convert(input)
    end
    
  2. Use a dedicated class with parameters:

    class Converter
      def initialize(input)
        @input = input
      end
      def convert
        @input.split(";").map{|s| { content: s, length: s.size } }
      end
    end
    
    module Caller
      Converter.new(input).convert
    end
    
  3. Use refinements, using monkey patching to create a method on the data object but letting a caller decide if it should be included.

    module Converter  
      refine String do
        def convert
          self.split(";").map{|s| { content: s, length: s.size } }
        end
      end
    end
    
    module Caller
      using Converter
      input.convert
    end
    

I'm not satisfied with any of them, but the third option seems the cleanest because that is usually what you would do if you deal with custom objects. But it also feels wrong because it's monkey patching light. What are your thoughts?

1

There are 1 answers

2
Jake Worth On

It's great that you are approaching this from an OO perspective.

I like a variation on your second suggestion, with a small difference-- use an object, but don't instantiate it.

class StringConverter
  def self.to_size_hash(string)
    string.split(';').map { |s| { content: s, length: s.size } }
  end
end


2.2.2 :001 > StringConverter.to_size_hash "a;simple;list"
 => [{:content=>"a", :length=>1}, {:content=>"simple", :length=>6}, {:content=>"list", :length=>4}]

As long as this class is only going to do one thing, once, I can't think of a good reason to persist it in memory.

The benefits of this approach are that you're encapsulating the logic in the StringConverter class, without writing another module (the first option in your question), or changing the expected behavior of the String class (the third option). It's a dedicated class for this transformation that you can use anywhere you need it.