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
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.
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.