2015-11-16 2 views
3

В рубине Я устал постоянно проверять, если объект имеет все необходимые свойства и подсвойство перед использованием их какЭто хорошая идея использовать method_missing на ноле в рубине

if (obj && obj[:a] && obj[:a][:b] && obj[:a][:b][:c] && obj[:a][:b][:c] > 0) 
    # do something useful 
end 

Это хорошее идея избежать этого, указав method_missing на NilClass, чтобы вернуть nil?

class NilClass 
    def method_missing(method_name, *args, &block) 
     nil 
    end 
end 

Этот способ с правильно написанными сравнениями я могу использовать значения по умолчанию для обработки всего.
Я даже могу сравнить с другими выражениями, как

if (obj[:a][:b][:c] > 0) 
    # do something useful 
else 
    # default behaviour if obj[:a][:b][:c] <= 0 or obj[:a][:b][:c] is undefined 
end 

В исключительных случаях я всегда могу вручную проверить nil.

Что можно сломать?

+1

Выражение «играет с огнем» приходит на ум. Простой пример: '([" chicken "," pig "]. Index (" chikcen ") + 1 == 1)? 1: 2 # => 2'. Здесь ': +' вызывает 'method_missing'. –

+0

Этот контрпример, кажется, не так контр-после ...Похоже, что написано: если есть 'chikcen', тогда возвращайте' 1', в противном случае возвращайте '2', что именно происходит с этим методом. Было бы здорово, если бы вы нашли реальный контрпример. Наверное, тогда мой вопрос станет бессмысленным. – xaxa

+0

Вы согласитесь, что если «цыпленок» не был орфографическим, «1» будет возвращен. Здесь 'index (" chikcen ")' возвращает 'nil',' method missing' заставляет 'nil + 1' возвращать' nil' и 'nil == 1' оценивает' false', поэтому орфографическая ошибка приведет к удивлению на обеденный стол. –

ответ

3

Использование Hash.fetch позволило бы вернуть значение false, если оно не существует и короткое замыкание является условным. http://ruby-doc.org/core-1.9.3/Hash.html#method-i-fetch

if obj.fetch(:a, {}).fetch(:b, {}).fetch(:c, false) 
    #useful stuff 
end 

И, как было упомянуто в комментариях, если вы действительно не хотите, чтобы проверить, если этот объект был хэш не вы могли бы сделать (obj||{}).fetch(:a, {}).fetch(:b, {}).fetch(:c, false)

+1

'{a: 1} .fetch (: a, {}). Fetch (: b, {}) # => NoMethodError: undefined метод 'fetch' для 1: Fixnum'. –

+0

Cary, ошибка в вашем примере '{a: 1}', '1' успешно извлечена из него и не является хешем – Vasfed

+0

@Vasfed, в чем ваш смысл? '1.fetch (: b, {})' вызывает исключение. Предположительно, OP хочет изящного вывода. –

2

Вы не ясно, что obj есть, но я предполагаю, что это хэш.

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

Этот вопрос неоднократно задавался в stackoverflow с несколькими решениями, но самый новый и лучший способ сделать это - использовать Hash#dig, входящий в комплект Ruby 2.3.

obj.dig(:a, :b, :c) 
+1

Я могу представить себе разговор между xaxa и его/ее руководством: «Если вы этого не сделаете, ум, я бы хотел подождать v.2.3, поэтому я могу использовать 'dig'.» –

1

Есть тысячи вещей, которые могут сломаться, если вы их не ожидаете. Например. вы не заметите много опечаток имен методов, так как они молча съедают method_missing.

Тем не менее, для многих людей то, что вы предлагаете, является желательной вещью, если она используется правильно. Из-за этого, Ruby 2.3 будет содержать специальный синтаксис для a save navigation operator:

foo&.bar 

Для хэшей, вы можете использовать Hash#dig в Рубине 2.3 похож на это:

if obj && obj.dig(:a, :b, :c, default: 0) > 0 
    # ... 
end 
+0

Хорошие новости о том, что они планируют ввести эту функцию – xaxa

2

Предполагая obj хэш и ни одно из сокровенных значений nil, вы не должны охлаждаться исцеляя ждет Hash#dig:

v = get_it_if_you_can(obj, :a, :b, :c) 
if v 
    # do something 
end 

где:

def get_it_if_you_can(h, *args) 
    args.reduce(h) { |o,k| o && (o.is_a?(Hash) ? o[k] : nil) } 
end 

Например:

h = { a: { b: { c: "yes" } } } 

get_it_if_you_can(h, :a)    #=> {:b=>{:c=>"yes"}} 
get_it_if_you_can(h, :a, :b)   #=> {:c=>"yes"} 
get_it_if_you_can(h, :a, :b, :c)  #=> "yes" 
get_it_if_you_can(h, :b, :a, :c)  #=> nil 
get_it_if_you_can(h, :a, :b, :c, :d) #=> nil 

Другой способ:

def get_it_if_you_can(h, *args) 
    args.reduce(h) { |o,k| o[k] } rescue nil 
end 

get_it_if_you_can(h, :a)    #=> {:b=>{:c=>"yes"}} 
get_it_if_you_can(h, :a, :b)   #=> {:c=>"yes"} 
get_it_if_you_can(h, :a, :b, :c)  #=> "yes" 
get_it_if_you_can(h, :b, :a, :c)  #=> nil 
get_it_if_you_can(h, :a, :b, :c, :d) #=> nil 

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

def get_it_if_you_can(h, *args) 
    args.redcue(h) { |o,k| o[k] } rescue nil 
end 

get_it_if_you_can(h, :a, :b, :c)  #=> nil 

Эта ошибка будет проскальзывать, хотя даже если бы мы пытались сузить класс исключения:

def get_it_if_you_can(h, *args) 
    begin 
    args.redcue(h) { |o,k| o[k] } 
    rescue NoMethodError 
    nil 
    end 
end 

Вот еще одна проблема с спасением исключения. Если:

h = { a: { "yes"=>1 } } 

можно было бы ожидать:

get_it_if_you_can(h, :a, "yes", 1) 

вернуть nil, но он возвращает 0. Это потому, что последовательные значения блока переменных следующим образом:

o #=>{:a=>{"yes"=>1}} 
k #=> :a 

o #=> {"yes"=>1} 
k #=> "yes" 

o #=> 1, 
k #=> 1 

и

1[1] = 0 

(см Fixnum#[])

+0

Это (вторая версия с 'rescue nil'), кажется, лучшее решение, поэтому – xaxa

+0

Мое предпочтение относится к первому по причине, но я не могу сказать, что это «лучше». –

+0

Первый относится к 'Hash'es, а t второй может быть применен к любому объекту, отвечающему на '[]' – xaxa

0

method_missing влияет на производительность, поскольку проверяется только после того, как все остальное, плюс дополнительное выделение контекста и т. д. Общий шаблон - определить метод с именем, но это не так.

Плюс вы потенциально купите себе несколько часов болезненного времени отладки, если nil неожиданно показывает где-то еще в вашем коде.

Лучшее решение заключается в использовании уже упоминалось Hash#fetch, или если вы хотите, чтобы быть радикальным - сделать ваши хэши вернуть специальный пустой хэш когда ключ отсутствует

> (h = {a:1}).default = Class.new(Hash){ define_method(:default){|k| self }; define_method(:nil?){true}; define_method(:==){|a|a==nil}}.new 
> h[:foo][:bar] 
{} 
> h[:foo][:bar].nil? 
true 
0

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

Вы не объяснили значение obj[:a][:b][:c] > 0, поэтому я предполагаю:

def positive_value_for_c? obj 
    begin 
    obj[:a][:b][:c] > 0 
    rescue NoMethodError 
    false 
    end 
end 
Смежные вопросы