Search code examples
rubyparsingyamlopenstruct

Create nested object from YAML to access attributes via method calls in Ruby


I am completely new to ruby. I have to parse a YAML file to construct an object

YAML File

projects:
  - name: Project1
    developers:
      - name: Dev1
        certifications:
          - name: cert1
      - name: Dev2
        certifications:
          - name: cert2
  - name: Project2
    developers:
      - name: Dev1
        certifications:
          - name: cert3
      - name: Dev2
        certifications:
          - name: cert4

I want to create an object from this YAML for which I wrote the following code in Ruby

require 'yaml'
object = YAML.load(File.read('./file.yaml'))

I can successfully access the attributes of this object with [] For e.g.

puts object[projects].first[developers].last[certifications].first[name]
# prints ABC

However, I want to access the attributes via method calls

For e.g.

puts object.projects.first.developers.last.certifications.first.name
# should print ABC

Is there any way to construct such an object whose attributes can be accessed in the (dots) way mentioned above? I have read about OpenStruct and hashugar. I also want to avoid usage of third party gems


Solution

  • If you are just experimenting, there is a quick and dirty way to do this:

    class Hash
      def method_missing(name, *args)
        send(:[], name.to_s, *args)
      end
    end
    

    I wouldn't use that in production code though, since both method_missing and monkey-patching are usually recipes for trouble down the road.

    A better solution is to recursively traverse the data-structure and replace hashes with openstructs.

    require 'ostruct'
    def to_ostruct(object)
      case object
      when Hash
        OpenStruct.new(Hash[object.map {|k, v| [k, to_ostruct(v)] }])
      when Array
        object.map {|x| to_ostruct(x) }
      else
        object
      end
    end
    
    puts to_ostruct(object).projects.first.developers.last.certifications.first.name
    

    Note that there are potentially performance issues with either approach if you are doing them a lot - if your application is time-sensitive make sure you benchmark them! This probably isn't relevant to you though.