Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 792
Harshul Pandav Avatar asked Feb 14 '15 23:02

Harshul Pandav


2 Answers

Nice answer from Xavier, but it can be shorter, just require yaml, json and ostruct and parse your YAML, convert it to JSON, parse it in an Openstruct (a Struct would also be possible) like this

object = JSON.parse(YAML.load(yaml).to_json, object_class: OpenStruct)

To load your YAML from a file it's

object = JSON.parse(YAML::load_file("./test.yaml").to_json, object_class: OpenStruct)

This gives

object
=>#<OpenStruct projects=[#<OpenStruct name="Project1", developers=[#<OpenStruct name="Dev1", certifications=[#<OpenStruct name="cert1">]>, #<OpenStruct name="Dev2", certifications=[#<OpenStruct name="cert2">]>]>, #<OpenStruct name="Project2", developers=[#<OpenStruct name="Dev1", certifications=[#<OpenStruct name="cert3">]>, #<OpenStruct name="Dev2", certifications=[#<OpenStruct name="cert4">]>]>]>
object.projects.first.developers.last.certifications.first.name 
=>cert2

I use this for loading configurations from file, a Yaml is easily to maintain and in your code it's easier to use than a configuration in Hash.

Don't do this for repetitive tasks.

like image 137
peter Avatar answered Nov 15 '22 21:11

peter


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.

like image 40
Xavier Shay Avatar answered Nov 15 '22 21:11

Xavier Shay