Ruby: Можете ли вы определить, вызывается ли у объекта один из его методов?

Я не уверен, что задаю правильный вопрос. Возможно, я неправильно подхожу к проблеме, но в основном у меня такая ситуация:

obj = get_user(params)
obj.profile => {:name => "John D", :age => 40, :sex => "male"} #Has to be of class Hash
obj.profile.name => "John D"
obj.profile[:name] => "John D"
obj.profile.job => nil

В общем, я должен выполнить все эти условия, и я не уверен, как именно к этому подойти (я только что выучил Ruby сегодня).

Обратите внимание на точечную запись для доступа к внутренним переменным, в противном случае я хотел бы, чтобы профиль был хэшем символов. Итак, я попробовал два метода, которые только помогают мне

Способ 1: сделать профиль OpenStruct

Так что это позволяет мне получить доступ к имени, возрасту и полу, используя точечную запись, и автоматически возвращает nil, если ключ не существует, однако obj.profile имеет тип OpenStruct вместо Hash

Способ 2: сделать профиль своим собственным классом

При этом я устанавливаю их как переменные экземпляра и могу использовать method_missing для возврата nil, если они не существуют. Но я снова сталкиваюсь с проблемой, что obj.profile не является правильным типом / классом

Я что-то упускаю? Есть ли способ отличить

obj.profile
obj.profile.name

в функции получения и вернуть хеш или иначе?

Могу ли я изменить то, что возвращает мой пользовательский класс для профиля, так что вместо этого он возвращает хэш?

Я даже пытался проверить args и **kwargs в функции get для obj.profile, и ни один из них, похоже, не помогает или не заполняется, если я вызываю obj.profile.something

3 ответа

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

def define_getters(hash)
  hash.instance_eval do
    def name
      get_val(__method__)
    end

    def job
      get_val(__method__)
    end

    def get_val(key)
      self[key.to_sym]
    end
  end
end

profile = person.profile #=> {name: "John Doe", age: 40, gender: "M"}
define_getters(profile)

person.profile.name #=> "John Doe"
person.profile.job #=> nil

Отражает также измененные значения (если вам интересно):

person.profile[:name] = "Ralph Lauren"
person.profile.name #=> "Ralph Lauren"

При таком подходе вам не придется переопределять method_missing создавать новые классы, наследуя от Hash или обезьяна-патч Hash учебный класс.

Тем не менее, чтобы иметь возможность доступа к неизвестным ключам через вызовы методов и возврата nil вместо ошибок вам придется задействовать method_missing,

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

class Hash
  def method_missing(*args)
    if args.size == 1  
      self[args[0].to_sym]
    else
      self[args[0][0..-2].to_sym] = args[1] # last char is chopped because the equal sign is included in the string, print out args[0] to see for yourself
    end
  end
end

См. Следующий вывод IRB для подтверждения:

1.9.3-p194 :001 > test_hash = {test: "testing"}
 => {:test=>"testing"} 
1.9.3-p194 :002 > test_hash.test
 => "testing" 
1.9.3-p194 :003 > test_hash[:test]
 => "testing" 
1.9.3-p194 :004 > test_hash.should_return_nil
 => nil 
1.9.3-p194 :005 > test_hash.test = "hello"
 => "hello" 
1.9.3-p194 :006 > test_hash[:test]
 => "hello" 
1.9.3-p194 :007 > test_hash[:test] = "success"
 => "success" 
1.9.3-p194 :008 > test_hash.test
 => "success" 
1.9.3-p194 :009 > test_hash.some_new_key = "some value"
 => "some value" 
1.9.3-p194 :011 > test_hash[:some_new_key]
 => "some value" 

Если это обязательно должно быть Hash:

require 'pp'

module JSHash
  refine Hash do
    def method_missing(name, *args, &block)
      if !args.empty? || block
        super(name, *args, &block)
      else
        self[name]
      end
    end
  end
end

using JSHash

profile = {:name => "John D", :age => 40, :sex => "male"}

pp profile.name    # "John D"
pp profile[:name]  # "John D"
pp profile.job     # nil
pp profile.class   # Hash

Но все же лучше не быть HashЕсли только это абсолютно не нужно:

require 'pp'

class Profile < Hash
  def initialize(hash)
    self.merge!(hash)
  end
  def method_missing(name, *args, &block)
    if !args.empty? || block
      super(name, *args, &block)
    else
      self[name]
    end
  end
end

profile = Profile.new({:name => "John D", :age => 40, :sex => "male"})

pp profile.name
pp profile[:name]
pp profile.job
Другие вопросы по тегам