Initialize a Ruby class from an arbitrary hash, but only keys with matching accessors

class, ruby

Solution

This is what I use (I call this idiom hash-init).

 def initialize(object_attribute_hash = {})
  object_attribute_hash.map { |(k, v)| send("#{k}=", v) }
 end

If you are on Ruby 1.9 you can do it even cleaner (send allows private methods):

 def initialize(object_attribute_hash = {})
  object_attribute_hash.map { |(k, v)| public_send("#{k}=", v) }
 end

This will raise a NoMethodError if you try to assign to foo and method "foo=" does not exist. If you want to do it clean (assign attrs for which writers exist) you should do a check

 def initialize(object_attribute_hash = {})
  object_attribute_hash.map do |(k, v)| 
    writer_m = "#{k}="
    send(writer_m, v) if respond_to?(writer_m) }
  end
 end

however this might lead to situations where you feed your object wrong keys (say from a form) and instead of failing loudly it will just swallow them - painful debugging ahead. So in my book a NoMethodError is a better option (it signifies a contract violation).

If you just want a list of all writers (there is no way to do that for readers) you do

 some_object.methods.grep(/\w=$/)

which is "get an array of method names and grep it for entries which end with a single equals sign after a word character".

If you do

  eval("@#{opt} = \"#{val}\"")

and val comes from a web form - congratulations, you just equipped your app with a wide-open exploit.

Problem

Is there a simple way to list the accessors/readers that have been set in a Ruby Class? ``` class Test attr_reader :one, :two def initialize # Do something end def three end end Test.new => [one,two] ``` What I'm really trying to do is to allow initialize to accept a Hash with any number of attributes in, but only commit the ones that have readers already defined. Something like: ``` def initialize(opts) opts.delete_if{|opt,val| not the_list_of_readers.include?(opt)}.each do |opt,val| eval("@#{opt} = \"#{val}\"") end end ``` Any other suggestions?

Original source