Hash keys as accessors in a class
I'm working on a class that reads some sensor information and returns it as a hash. I would like to use the hash keys as accessors, but I'm not having much luck getting it work. Here are the relevant parts of my code so far:
I've tried it both with method_missing and by using the :define_method
method.
attr_reader :sensor_hash
def method_missing(name, *args, &blk)
if args.empty? && blk.nil? && @sensor_hash.has_key?(name.to_s)
@sensor_hash[name.to_s]
else
super
end
end
def sensor(*sensor_to_return)
sensor_output = run_command(this_method_name)
sensor_output = sensor_output.split("\n")
sensor_output.map! { |line| line.downcase! }
unless sensor_to_return.empty?
sensor_to_return = sensor_to_return.to_s.downcase
sensor_output = sensor_output.grep(/^#{se开发者_如何转开发nsor_to_return}\s/)
end
@sensor_hash = Hash.new
sensor_output.each { |stat| @sensor_hash[stat.split(/\s+\|\s?/)[0].gsub(' ','_').to_sym] = stat.split(/\s?\|\s?/)[1..-1].each { |v| v.strip! } }
@sensor_hash.each do |k,v|
puts v.join("\t")
self.class.send :define_method, k { v.join("\t") }
end
return @sensor_hash
The data returned is a hash with the sensor name as the key and and the value is an array of everything else returned. My goal is to be able to call Class.sensor.sensor_name
and get the output of Class.sensor[:sensor_name]
. Currently, all I'm able to get is an undefined method error. Anybody have any idea what I'm doing wrong here?
Maybe OpenStruct does what you want. From the doc :"It is like a hash with a different way to access the data. In fact, it is implemented with a hash, and you can initialize it with one."
require 'ostruct'
s=OpenStruct.new({:sensor_name=>'sensor1',:data=>['something',1,[1,2,3]]})
p s.sensor_name
#=> "sensor1"
Just a quick example. Do you have any reasons to not monkey-patch your Hash?
irb(main):001:0> class Hash
irb(main):002:1> def method_missing(name, *args, &blk)
irb(main):003:2> if self.keys.map(&:to_sym).include? name.to_sym
irb(main):004:3> return self[name.to_sym]
irb(main):005:3> else
irb(main):006:3* super
irb(main):007:3> end
irb(main):008:2> end
irb(main):009:1> end
=> nil
irb(main):012:0> h = {:hello => 'world'}
=> {:hello=>"world"}
irb(main):013:0> h.hello
=> "world"
You could use a wrapper class with method missing so you don't have to monkey patch Hash
.
class AccessibleHash
def initialize(hash)
@hash = hash
end
def method_missing(name, *args, &block)
sname = name.to_sym
if @hash.keys.include? sname
return @hash[sname]
else
super
end
end
end
Or, if you are working with Rails it has some nice built in object delegation with SimpleDelegator
. That would allow you to define accessors on your hash as well as any nested hashes within it.
class AccessibleHash < SimpleDelegator
def initialize
define_accessors(self.keys)
end
def define_accessors(keys)
keys.each do |key|
defind_accessors(body[key].keys)
self.define_singleton_method(key) { self[key] }
end
end
end
ah = AccessibleHash.new({ some: 'hash', with: { recursive: 'accessors' })
ah.with.recursive == 'accessors'
=> true
This would be less performant at instantiation than method_missing
, because it has to run recursively over your delegatee object as soon as it's created. However, it's definitely safer than method_missing
, and certainly way safer than monkey patching your Hash
class. Of course, safety is relative to your goals, If it's all your application does then monkey patch away.
And if you want the recursive, nested accessors without rails you could do something like this with a combination of the above...
class AccessibleHash
def initialize(hash)
@hash = hash
define_accessors(@hash.keys)
end
def define_accessors(keys)
keys.each do |key|
@hash[key] = self.class.new(@hash[key]) if @hash.keys.present?
self.define_singleton_method(key) { self[key] }
end
end
end
But at that point you're getting pretty crazy and it's probably worth reevaluating your solution in favor of something more Object Oriented. If I saw any of these in a code review it would definitely throw up a red flag. ;)
精彩评论