But sometimes, when we read an external API (XML or JSON) and parse the result, we get data which is a set of nested hashes and arrays. Then we want to initialize an object in our application with this data. I wrote the following module to do that.
init_from_hash.rb
-----------------------------
module InitFromHash
def initialize(*args)
args.first.each do |k, v|
unless defined?(k).nil? # check if it's included as a reader attribute
result = v.instance_of?(Array) ? v.inject([]) {|arr, v1| arr << init_object(v1, k)} : init_object(v, k)
instance_variable_set("@#{k}", result)
end
end if (args.length == 1 && args.first.is_a?(Hash))
end
def init_object value, klass
value.instance_of?(Hash) ? (get_module_name + klass.to_s.capitalize).constantize.new(value) : value
end
def get_module_name
(self.class.name =~ /^(.+::).+$/) ? $1 : ''
end
end
Assumptions:
1. Our object also contains nested objects. For example, we have an author who has articles, and each article has comments.
2. Names of our classes match the keys in our data.
3. All the classes of objects to be initialized have the same module name.
4. We want only attributes specified in the reader_attr to be present
Example of usage:
Module Blog
class Author
include InitFromHash
attr_reader :name, :article
end
end
module Blog
class Article
include InitFromHash
attr_reader :name, :abstract, :comment
end
end
module Blog
class Comment
include InitFromHash
attr_reader :text
end
end
When we receive our data in the form:
data = {"author" => 'Mike', "article" => [{"name" => "Is there life on Mars",
"abstract" => "Probably", "comment" => [{"text" => "Really?"},
{"text" => "No way!"}]}, {"name" => "Milk is good for you",
"abstract" => "Scientists discovered", "comment" => [{"text" => "Really?"},
{"text" => "No way!"}] }}
Then we can initialize our instance as simple as
author = Author.new(data)