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

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

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).

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

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

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

Метод 2. Сделать профиль отдельным классом

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

Есть что-то, что мне не хватает? Есть ли способ, возможно, различать

obj.profile
obj.profile.name

в функции-получателе и вернуть либо хэш, либо что-то другое?

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

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


person user1561753    schedule 20.02.2015    source источник
comment
Я бы сказал, что obj.profile => {:name => "John D", :age => 40, :sex => "male"} означает только то, что он должен возвращать представление, похожее на представление хеша. Это не обязательно означает, что это должен быть экземпляр Hash. Было бы неправильно интерпретировать ожидания таким образом?   -  person spickermann    schedule 20.02.2015
comment
В подсказке было сказано вернуть символьный хеш, и к OpenStruct можно получить доступ точно такими же способами. Единственная разница в том, что его класс не тот... Я посмотрю, возможен ли тот способ, который я интерпретировал. Если нет, то я буду использовать OpenStruct   -  person user1561753    schedule 20.02.2015


Ответы (3)


Если это обязательно должно быть 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
person Amadan    schedule 20.02.2015
comment
merge! является разрушительным merge : последний возвращает результат, не затрагивая получателя (или аргументы), первый изменяет получателя. В большинстве случаев a.merge!(b) эквивалентно a = a.merge(b), но это меняет идентичность a; и, очевидно, изменение личности self не сработает. - person Amadan; 20.02.2015

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

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.

person SHS    schedule 20.02.2015

Это переопределение 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" 
person Mike S    schedule 20.02.2015