简体   繁体   中英

How do I dynamically set object attributes from a YAML file?

I have a YAML file which maps a bunch of properties to a class. I'd like to be able to loop through all of the properties from my YAML file and set them on an object dynamically.

How can I do this?

Here's the gist of what I have so far:

YAML contents:

my_obj:
    :username: 'myuser'
    :password: 'mypass'
    ...

Ruby:

settings = YAML::load_file SETTINGS_FILE
settings = settings['my_obj']

settings.each do |s|
  #psuedo-code
  #example: my_obj.username = settings[:username] if my_obj.has_property?(:username)
  #my_obj.[s] = settings[:s] if my_obj.has_property?(:s)
end

I'm not sure if doing this is necessarily a best practice, but there are a lot of properties and I thought this would be a cleaner way than manually setting each property directly.

Depending on what you actually need, you may be able to use Ruby's default YAML dump and load behaviour. When dumping an arbitrary object to YAML, Psych (the Yaml parser/emitter in Ruby) will look at all the instance variables of the object and write out a Yaml mapping with them. For example:

class MyObj
  def initialize(name, password)
    @name = name
    @password = password
  end
end

my_obj = MyObj.new('myuser', 'mypass')

puts YAML.dump my_obj

will print out:

--- !ruby/object:MyObj
name: myuser
password: mypass

You can then load this back with YAML.load and you will get an instance of MyObj with the same instance variables as your original.

You can override or customise this behaviour by implementing encode_with or init_with methods on your class.

As per your sample yaml file content,you would get the below Hash .

require 'yaml'
str = <<-end
my_obj:
  :username: 'myuser'
  :password: 'mypass'
end
settings = YAML.load(str)  
# => {"my_obj"=>{:username=>"myuser", :password=>"mypass"}}
settings['my_obj'][:username] # => "myuser"
settings['my_obj'][:password] # => "mypass"

You'll see this used a lot in Ruby gems and Rails. Here's a dummy class:

require 'yaml'

class MyClass
  attr_accessor :username, :password
  def initialize(params)
    @username, @password = params.values_at(*[:username, :password])
  end
end

We can load the data in using YAML's load_file :

data = YAML.load_file('test.yaml') # => {"my_obj"=>{:username=>"myuser", :password=>"mypass"}}

Passing the structure just read, which in this case is a hash, and which, oddly enough, or maybe even magically, is what the class initializer takes, creates an instance of the class:

my_class = MyClass.new(data['my_obj'])

We can see the instance variables are initialized correctly from the contents of the YAML file:

my_class.username # => "myuser"
my_class.password # => "mypass"

This appears to be working. It allows me to set attributes on my_obj dynamically from the YAML settings hash.

settings.each do |key, value|
  my_obj.instance_variable_set("@#{key}", value) if my_obj.method_defined?("#{key}")
end

my_obj has attr_accessor :username, :password , etc.

The snippet above appears to let me set these attributes dynamically using instance_variable_set , while also ensuring that my_obj has the attribute I'm trying to assign with method_defined? .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM