Skip to content

Instantly share code, notes, and snippets.

@jgandt
Created February 17, 2012 21:43
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jgandt/1855631 to your computer and use it in GitHub Desktop.
Save jgandt/1855631 to your computer and use it in GitHub Desktop.
module ObjectifyFromHash
# Using this as a mix in we are able to maintain a meaningful class structure while exposing the hash keys as viable and visable methods.
# I had a lot more I wanted to do in my FileCopyTask, and I didn't feel that the objectification logic was correctly placed in my class.
# The really neat part about this is that you could extend an individual instance of an object and objectify it in place.
# You could do all sorts of other crazy things with definition of reader/writer/accessor permissions in the objectify call.
# This may be entirely useless and silly but I think it is a slightly better option than returning an object of the class Hashit/ObjectifyFromHash
OpenStruct is a viable alternative. However, I was unable to find a way to use the Plain class as a mixin.
# Thoughts? Comments? Need more of the code around what I'm doing? Is their an easier, clearer way (ActiveModel if working in rails)?
# .jpg
require 'objectify_from_hash'
class FileCopyTask
include ObjectifyFromHash
def initialize( file_copy_task_as_hash )
objectify file_copy_task_as_hash
end
# My implementation implements a method similar to this one. This is just a quick example
def find_a_source_file
# find a file in source dir with the proper file extension.
end
end
source_directory: copy_from_dir
destination_directory: copy_to_dir
module ObjectifyFromHash
def objectify( hash )
hash.each do |k,v|
self.instance_variable_set("@#{key}", value)
# tcocca was playing with this an noticed that manually defining the methods was both slow and silly. Thanks to Tom for this update: https://gist.github.com/tcocca
# Use attr_accessor if you want to allow read-write privileges on the variables.
self.class.send(:attr_accessor, key)
# Use attr_reader if you only want to allow READ privileges on the variables.
#self.class.send(:attr_reader, key)
# Use attr_writer if you only want to allow WRITE privileges on the variables.
#self.class.send(:attr_writer, key)
end
end
end
require 'file_copy_task'
# Now use it!
# load in a hash from a yaml
file_copy_task_hash = YAML::load( File.open('file_copy_task.yml')
# => { :source_directory => 'copy_from_dir', :destination_directory => 'copy_to_dir', :file_extension => '.txt' }
# Now lets make it a useable object
file_copy_task = FileCopyTask.new( file_copy_task )
# => hash keys have now been loaded as attribute accessors. huzzah. use 'em...
file_copy_task.source_directory
# => 'copy_from_dir'
file_copy_task.destination_directory = 'hey_look_a_new_destination_directory'
# => 'hey_look_a_new_destination_directory'
File.cp( file_copy_task.source_directory + file_copy_task.find_a_source_file, file_copy_task.destination_directory )
@jgandt
Copy link
Author

jgandt commented Feb 17, 2012

Please note, I'm writing in poruby (plain old ruby). No rails in my parent project.

I'm pretty sure all of this can be achieved with ActiveModel very easily. I'm new to rails so I shall look into that now. Given the modularization of ActiveSupport and ActiveRecord it might be worth my while to now include those gems for a more rock solid implementation.

@jgandt
Copy link
Author

jgandt commented Feb 17, 2012

Please note, this came about from ideas from:

http://pullmonkey.com/2008/01/06/convert-a-ruby-hash-into-a-class-object/

Thanks to you, anonymous poster...

@tcocca
Copy link

tcocca commented May 3, 2012

I found a much faster way to accomplish the same thing as the initialize method (from the blog post). I was seeing very slow times initializing a set of objects so I re-wrote the #initialize method as follows:

def initialize(hash)
  hash.each do |key, value|
    self.instance_variable_set("@#{key}", value)
    self.class.send(:attr_reader, key)
  end
end

That creates attr_reader methods to access the instance variables. If you want to be able to read and write to those methods just use an attr_accessor:

def initialize(hash)
  hash.each do |key, value|
    self.instance_variable_set("@#{key}", value)
    self.class.send(:attr_accessor, key)
  end
end

I found this code to run much faster than the:
self.class.send(:define_method, k, proc{self.instance_variable_get("@#{k}")})
self.class.send(:define_method, "#{k}=", proc{|v| self.instance_variable_set("@#{k}", v)})

You might want to give it a shot and see if it runs faster for you.

~ Tom

@jgandt
Copy link
Author

jgandt commented May 3, 2012

Very good call. I'll investigate the speed issues and update the gist in the meantime.

Even if the speed update is nil/marginal, it makes the concept so much more clear.

Why does attr_accessor execute so much faster than the two define methods? I'm going to dig into ruby core to investigate.

Thanks so much for the addition. Are you using this concept? Where did you happen upon this?

.jpg

@tcocca
Copy link

tcocca commented May 3, 2012

Did you test it with the attr_reader / attr_accessor? Did you see the same speed results that I saw?

My guess is that attr_reader / attr_accessor are doing nothing but looking for instance variables with the same name as the method as opposed to define_method creating an actual method that is using a proc which is lazy loaded and is doing the exact same thing as attr_reader, just looking for / setting an ivar. If you end up investigating i'd love a more in depth answer (mine is just an assumption) but I agree, this is much clearer that the define_method and the massive speedup was a nice bonus!

@jgandt
Copy link
Author

jgandt commented May 9, 2012

I did test with attr_accessor. I found significant differences in execution time.

I have not yet jumped into Ruby core to discover the reason, but your explaination is reasonable.

My (very rudimentary) test rig is at https://github.com/jgandt/objectify_from_hash.

Thanks again for your input!

.jpg

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment