Hash for views
Posted by Guy Naor Wed, 24 May 2006 21:40:00 GMT
One of my team members asked for a way to use non ActiveRecord objects in views. We settled on a modified hash that will have accessors for all the keys. Pretty simple with method_missing()
class ObjectiveHash < Hash
def method_missing(method_id, *args, &block)
begin
super
rescue NoMethodError => e
is_assign = method_id.to_s[-1,1] == '='
the_key = (method_id.to_s[0..(is_assign ? -2 : -1)]).to_sym
self[the_key] = args if args && is_assign
raise e if !self[the_key]
return self[the_key]
end
end
private
def initialize other_hash = {}
super
update(other_hash)
end
endNow we can pass it to the view, and retireve into it on submit:
# Setting values
@settings = ObjectiveHash.new
@settings.name = "John"
@settings.family = "Doe"
# Getting values on submit
@settings = ObjectiveHash.new params[:settings].symbolize_keys
# Accessing the values
@settings.name # => John
@settings.family # => DoePlease note that it'll be slower than regular hash access (which still works), and that all keys are symbols.


















You might run into some name clashes. Try @settings.class = "foo" for instance.
Why not just use the built-in OpenStruct? Or for an even more robust solution have a look at Facet's OpenObject.
The problem with using OpenStruct is that it's not enumerable and doesn't interchange with hash, so it's a bit less convenient. The problem with assigning to the class element is the same with OpenStruct as well, and probably with OpenObject (haven't seen it yet), as it's a method of every class in ruby.
Right, it wouldn't be Enumerable b/c that adds yet another set of methods that could name clash.
OpenObject doesn't suffer from the "class" problem since it subclasses BasicObject (ie. BlankSlate) clearing out most methods. As for Enumerable, you have the same problem of course. It does have a #to_h method though and it can also be used with Enumerator (becuase #each works).
Thanks for pointing me to OpenObject. I'll have to look at it.
BTW, each does work on this modified hash as it's just a hash. But indeed you have to be careful with name clashes. We had this problem with a field named zip we needed for address, as zip is also an Enumerable method. We just changed it to zipcode.
I can confirm that OpenStruct works perfectly for this purpose:
e.g.
require 'ostruct' @example = OpenStruct.new params[:example] render :partial => 'example_form', :object => @example
For me anyways. I had a clunky hand-rolled class for each form partial, and this was a drop-in replacement.