Traversing a single or an array of objects

1 minute read

I will explain a simple pattern to process a single or an array of objects uniformly. Since ruby is not a strongly typed language(< ruby 3), the burden may lie on the input function.

I will start with an example.

Let say we need to parse an XML schema. Which may or may not have a repeating block.

Example 1

<?xml version="1.0" encoding="UTF-8"?>
<comments>
    <comment>
        <description>TEST COMMENT 123</description>
    </comment>
</comments>

Example 2

<?xml version="1.0" encoding="UTF-8"?>
<comments>
    <comment>
        <description>TEST COMMENT 123</description>
    </comment>
    <comment>
        <description>TEST COMMENT 456</description>
    </comment>
</comments>

First lets write a function to parse the data. I will be using nori

require 'nori'

def fetch_descriptions_1(raw_data:)
  comments = Nori.new.parse(raw_data)['comments']['comment']
end

Next, let us try to fetch the descriptions. Since comment can be both a single object or an array of objects. We can use a simple if-else clause for this.

def fetch_descriptions_1(raw_data:)
  comments = Nori.new.parse(raw_data)['comments']['comment']
  if comments.is_a?(Array)
    comments.map { |comment| comment['description'] }
  else
    [comments['description']]
  end
end

The above perfectly handles the volatile nature of the input data.

But we can do this in a better manner. There is an opportunity to refactor.

Consider this, if we put the single_comment inside an array, then we will only need to handle a single input type, array of objects.

def fetch_descriptions_2(raw_data:)
  comments = [Nori.new.parse(raw_data)['comments']['comment']].flatten
  comments.map { |comment| comment['description'] }
end

Full example

require 'nori'

single_comment = <<~EOF
<?xml version="1.0" encoding="UTF-8"?>
<comments>
    <comment>
        <description>TEST COMMENT 123</description>
    </comment>
</comments>
EOF

multiple_comments = <<~EOF
<?xml version="1.0" encoding="UTF-8"?>
<comments>
    <comment>
        <description>TEST COMMENT 123</description>
    </comment>
    <comment>
        <description>TEST COMMENT 456</description>
    </comment>
</comments>
EOF

def fetch_descriptions_1(raw_data:)
  comments = Nori.new.parse(raw_data)['comments']['comment']
  if comments.is_a?(Array)
    comments.map { |comment| comment['description'] }
  else
    [comments['description']]
  end
end

def fetch_descriptions_2(raw_data:)
  comments = [Nori.new.parse(raw_data)['comments']['comment']].flatten
  comments.map { |comment| comment['description'] }
end

Comments