2016-07-20 3 views
0

Вот моя проблема. Мне нравится Andrea Pavoni способ использования вложенного хэша для инициализации класса.Ruby: инициализируйте класс Ruby с вложенным хешем и некоторыми предопределенными значениями по умолчанию

require 'ostruct' 

class DeepStruct < OpenStruct 
    def initialize(hash=nil) 
    @table = {} 
    @hash_table = {} 

    if hash 
     hash.each do |k,v| 
     @table[k.to_sym] = (v.is_a?(Hash) ? self.class.new(v) : v) 
     @hash_table[k.to_sym] = v 

     new_ostruct_member(k) 
     end 
    end 
    end 

    def to_h 
    @hash_table 
    end 

end 

Но я не могу найти способ включить хэш (в классе) с конкретными значениями по умолчанию, так что поведение будет выглядеть следующим образом:

Оригинальное поведение без умолчанию (с выше код):

input_hash = {a: {b: 1}} 
new_object = DeepStruct.new hash 
new_object.a  # => #<DeepStruct b=1> 
new_object.a.b # => 1 
new_object.a.to_h # => {b: 1} 

Со следующим default_h определенным внутри класса:

default_h = {a: {dc: 2}, dd: {de: 4}} 

input_hash и default_h должны объединить следующим образом (фактически используя deep_merge для вложенного хэша)

{:a=>{:dc=>2, :b=>1}, :dd=>{:de=>4}} 

поведение с по умолчанию хэш должен быть:

new_object = DeepStruct.new hash 
new_object.a.b # => 1 
new_object.a.dc # => 2 
new_object.a.to_h # => {:dc=>2, :b=>1} 

Я не могу найти способ для реализации этого поведения внутри класса. Я бы очень признателен за любую помощь в этом вопросе.

Edit: Теперь пытаются использовать код Дэвида в классе:

class CompMedia 
    require 'ostruct' 
    attr_accessor :merged_h 

    def initialize(hash) 
     defaults = {a: {dc: 2}, dd: {de: 4}} 
     @merged_h = {} 
     deep_update(merged_h, defaults) 
     deep_update(merged_h, hash) 
     @merged_h 
    end 

    def deep_update(dest, src) 
     src.each do |key, value| 
     if value.is_a?(Hash) 
      dest[key] = {} if !dest[key].is_a?(Hash) 
      deep_update(dest[key], value) 
     else 
      dest[key] = value 
     end 
     end 
    end 

    def deep_open_struct(hash) 
     result = OpenStruct.new 
     hash.each do |key, value| 
     if value.is_a?(Hash) 
      result[key] = deep_open_struct(value) 
     else 
      result[key] = value 
     end 
     end 
     result 
    end 

end # class CompMedia 

input_hash = {a: {b: 1}} 

cm = CompMedia.new(input_hash) 

object = cm.deep_open_struct(cm.merged_h) 

p object.marshal_dump # {:a=>#<OpenStruct dc=2, b=1>, :dd=>#<OpenStruct de=4>} 
p object.a    # <OpenStruct dc=2, b=1> 
p object.a.marshal_dump # {:dc=>2, :b=>1} 
p object.a.b    # 1 
p object.a.dc   # 2 
p object.dd    # <OpenStruct de=4> 

Очевидно, что я не нашел способ получить в простой форме вложенных хэш-элементы из объекта openstruct. Моя цель - создать класс, который будет инициализирован хешем по умолчанию (вложенным), содержащимся в классе, и хэш-вложенным вводом. Кроме того, я хочу иметь возможность добавлять методы, которые обрабатывали бы хэш внутри класса. Я еще не там.

С другой стороны, я мог бы просто использовать слитый хэш, и это будет работать, хотя и с немного более громоздкими обозначениями:

class CompMedia 
    attr_accessor :merged_h 

    def initialize(hash) 
     defaults = {a: {dc: 2}, dd: {de: 4}} 
     @merged_h = {} 
     deep_update(merged_h, defaults) 
     deep_update(merged_h, hash) 
     @merged_h 
    end 

    def deep_update(dest, src) 
     src.each do |key, value| 
     if value.is_a?(Hash) 
      dest[key] = {} if !dest[key].is_a?(Hash) 
      deep_update(dest[key], value) 
     else 
      dest[key] = value 
     end 
     end 
    end 

    def multiply_by(k) 
     merged_h[:a][:dc] * k 
    end 

end 

input_hash = {a: {b: 1}} 

cm = CompMedia.new(input_hash) 
p cm.merged_h   # {:a=>{:dc=>2, :b=>1}, :dd=>{:de=>4}} 
p cm.merged_h[:a]  # {:dc=>2, :b=>1} 
p cm.merged_h[:a][:dc] # 2 
p cm.merged_h[:dd]  # {:de=>4} 
p cm.multiply_by(10) # 20 

Я буду рассматривать последнюю версию, как мое решение, если кто-то не может сделать код с OpenStruct, который я бы предпочел.

ответ

1

Вот код, который делает то, что вы хотите, за исключением того, что я отбросил идею подкласса OpenStruct, потому что не был уверен, была ли это хорошая идея. Кроме того, я внедрил deep_merge сам, потому что это было довольно легко сделать, но вы можете попробовать использовать версию ActiveSupport, если хотите.

require 'ostruct' 

# Merges two hashes that could have hashes inside them. Default 
# values/procs of the input hashes are ignored. The output hash will 
# not contain any references to any of the input hashes, so you don't 
# have to worry that mutating the output will affect the inputs. 
def deep_merge(h1, h2) 
    result = {} 
    deep_update(result, h1) 
    deep_update(result, h2) 
    result 
end 

def deep_update(dest, src) 
    src.each do |key, value| 
    if value.is_a?(Hash) 
     dest[key] = {} if !dest[key].is_a?(Hash) 
     deep_update(dest[key], value) 
    else 
     dest[key] = value 
    end 
    end 
end 

def deep_open_struct(hash) 
    result = OpenStruct.new 
    hash.each do |key, value| 
    if value.is_a?(Hash) 
     result[key] = deep_open_struct(value) 
    else 
     result[key] = value 
    end 
    end 
    result 
end 

input_hash = {a: {b: 1}} 
defaults = {a: {dc: 2}, dd: {de: 4}} 
object = deep_open_struct(deep_merge(defaults, input_hash)) 
p object.a.b 
p object.a.dc 
p object.a.to_h 
+0

Спасибо, это работает, но я считаю, что мне нужен класс, который содержит хэш по умолчанию, и инициализируется объединенным хэшем (по умолчанию + ввод). Причина в том, что я хочу включить дополнительные методы, обрабатывающие содержимое хэша. Я работаю над этим с вашим кодом и опубликую его, если найду что-нибудь. Почему это плохой подход к подклассу OpenStruct? – JMor

+0

Не должно быть слишком сложно иметь класс, который содержит хэш по умолчанию. Вы можете оставить код в этом сообщении как есть, и создать класс, который использует его внутри. –

+0

Я сделал это. Но я борюсь с методами этого класса, которые будут обращаться к объединенному хешу. – JMor