Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

class

ruby

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?

like image 258
JP. Avatar asked Nov 04 '09 19:11

JP.


People also ask

Does a Ruby class need an initialize method?

The initialize method is useful when we want to initialize some class variables at the time of object creation. The initialize method is part of the object-creation process in Ruby and it allows us to set the initial values for an object.

What is attr in Ruby?

attr. The attr method creates an instance variable and a getter method for each attribute name passed as argument. An argument can be a Symbol or a String that will be converted to Symbol module Attr.

What is new () in Ruby?

Creating Objects in Ruby using new Method You can create objects in Ruby by using the method new of the class. The method new is a unique type of method, which is predefined in the Ruby library. The new method belongs to the class methods.


1 Answers

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.

like image 76
Julik Avatar answered Oct 01 '22 05:10

Julik