Ruby: Use String to set hash value

时间:2015-05-24 22:14:53

标签: ruby-on-rails ruby hash

I'm using Rails 4 and trying to access a hash variable via string names.

For example, let's say I have policy model with a member hash who has the fields name and address.

I would like to be able to convert policy_member_name into policy[:member][:name].

However, this string may be longer than just 3 sections. I was able to access the value, but not be able to set it using the following:

  ret = obj
  keys.each do |key|
    ret = ret[key.to_sym]
  end
  ret

where keys would be an array such as ['member', 'name'] and obj would be the object, such as Policy.first. However, this method only would return what value is at policy[:member][:name] and does not allow me to do policy[:member][:name]=.

Any help is appreciated, thanks.

EDIT:

I'd like to be able to call policy.member_name to get policy[:member][:name] and have policy.member_name= "Something" to set policy[:member][:name]="Something"

2 个答案:

答案 0 :(得分:2)

使用method_missing

很容易实现
class HashSnake

  def initialize(hash)
    @hash = hash
  end

  def method_missing(method, *args, &block)
    parts = method.to_s.split('_').map(&:to_sym)
    pointer = @hash
    parts.each_with_index do |key, i|             
      if pointer.key? key
        if pointer[key].is_a? Hash
            if (i +1 == parts.length)
                return pointer[key]
            else
                pointer = pointer[key]
            end
        else
          return pointer[key]
        end
      # Checks if method is a setter
      elsif key[-1] == '=' && pointer.key?(key[0..-2].to_sym)
        pointer[key[0..-2].to_sym] = args[0]
      end
    end
  end
end

obj = HashSnake.new(
  member: {
    foo: 'bar',
    child: {
      boo: 'goo'
    }
  }
)

obj.member_foo = 'bax'
puts obj.member_foo.inspect
# "bax"
puts obj.member_child_boo.inspect
# goo

答案 1 :(得分:0)

由于您只允许用户对某些哈希值进行读取或写入访问,因此创建将哈希值(字符串)的名称映射到哈希对象的哈希是没有困难的。例如:

h0 = { a: 1, b: 2 }
h1 = { c: 3, d: { e: 4, f: { g: 5 } } }

hash = { "h0"=>h0, "h1"=>h1 }

完成后,只需创建满足您需求的特定于域的语言(“DSL”)即可。可能有宝石可以开箱即用,或者可以根据您的要求进行修改。

以下是一种可能性的示例。 (我建议你在阅读代码之前先看看这些例子。)

<强>代码

class Array
  def h(hash)
    h = hash[first]
    if last.is_a? Array
      seth(h, *self[1..-2].map(&:to_sym), *last)
    else
      geth(h, *self[1..-1].map(&:to_sym))
    end
  end

  private

  def geth(h, *keys)
    keys.reduce(h) { |h,k| h[k] }
  end

  def seth(h, *keys, last_key, type, val)
    val =
      case type
      when "STR" then val.to_str
      when "INT" then val.to_int
      when "SYM" then val.to_sym
      when "FLT" then val.to_f
      end
    geth(h, *keys)[last_key] = val
  end
end

<强>实施例

以下内容适用于h0h1hash

["h0", "b"].h(hash)
   #=> 2
["h1", "d", "f", "g"].h(hash)
   #=> 5

["h0", "b", ["INT", 3]].h(hash)
   #=> 3
h0 #=> {:a=>1, :b=>3}
["h1", "d", "f", "g", ["STR", "dog"]].h(hash)
   #=> "dog"
h1 #=> {:c=>3, :d=>{:e=>4, :f=>{:g=>"dog"}}, :g=>"dog"}