-
-
Save jgandt/1855631 to your computer and use it in GitHub Desktop.
# 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 ) |
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...
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
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
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!
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
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.