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

谁说我不能喝 提交于 2019-12-10 14:58:01

问题


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


回答1:


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.




回答2:


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.



来源:https://stackoverflow.com/questions/28521571/create-nested-object-from-yaml-to-access-attributes-via-method-calls-in-ruby

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!