Как обнаружить значение, подобное массиву или набору, избегая проверки типа

У меня есть метод, который принимает аргумент, который может быть объектом типа Array/Set или Hash. Суть метода примерно такая:

def find(query = {})
  if Array === query or Set === query
    query = {:_id => {'$in' => query.to_a}}
  end
  mongo_collection.find(query)
end

Метод примет набор объектов ID и превратит его в хеш-условие для MongoDB.

Две проблемы с приведенным выше кодом:

  1. Это не удастся, если 'set' не требуется из стандартной библиотеки. Я не хочу требовать зависимость только для выполнения проверки.
  2. Я не хочу делать строгие сравнения типов. Я хочу принять любое значение, подобное массиву или набору, и привести его к массиву значений с помощью to_a.

Как бы вы выполнили эту проверку? Некоторые соображения, которые следует иметь в виду:

  1. Я мог бы проверить метод to_ary, но Сет не отвечает на to_ary. Объекты, реализующие этот метод, должны быть массивами, и я согласен, что Set не является массивом. См. Последствия реализации to_int и to_str в Ruby
  2. Я не могу проверить to_a, так как Hash отвечает на него
  3. Методы, общие для Array и Set, но не для Hash:

    [:&, :+, :-, :<<, :collect!, :flatten!, :map!, :|]
    

Я решил пойти с чем-то вроде этого:

query = {:_id => {'$in' => query.to_a}} if query.respond_to? :&

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


person mislav    schedule 20.07.2011    source источник
comment
По сути, вы говорите об утиной печати. Вам все равно, что это за объект, если он может действовать как массив. Итак, добавьте .to_a к объекту и посмотрите, может ли он преобразоваться в массив. Если вам нужен хэш, вы, вероятно, захотите преобразовать его в массив, а затем создать хэш: Hash[*array.to_a.flatten]. В этот момент работа вызывающей стороны состоит в том, чтобы требовать соответствующие классы, а не вас.   -  person the Tin Man    schedule 21.07.2011


Ответы (4)


Как насчет того, чтобы выяснить, похож ли запрос на Hash?

def find(query = {})
  query = {:_id => {'$in' => query.to_a}} unless query.respond_to?(:has_key?)
  mongo_collection.find(query)
end

Разумно ожидать, что объект будет Hash или Hash, если он ответит на has_key?.

person Emmanuel Oga    schedule 21.07.2011
comment
Это кажется наиболее элегантным решением этой конкретной проблемы, но не отвечает на первоначальный общий вопрос: как обнаружить значения, подобные массиву или множеству. Ну что ж - person mislav; 07.08.2011

Вот мое мнение:

if not Hash === query and query.respond_to? :to_a

Я просто проверяю to_a, это единственный интересующий меня метод, но также проверяю, что это не объект Hash. Я использую строгую проверку типов для Hash, но только потому, что это наименее вероятный объект, который будет передан как совершенно отдельный класс, который по сути является хэшем.

person mislav    schedule 20.07.2011

Проверка того, определен ли Set, решит вашу первую проблему. Во-вторых, вы могли бы проверить предков класса запроса, чтобы увидеть, есть ли в них Array, но это, вероятно, не поймает все «массивные» объекты. Я, вероятно, не стал бы проверять наличие методов для проверки массивности, поскольку вы проверяете имена, а не поведение. В частности, Arel отвечает (или отвечал до того, как он стал устаревшим) &, но этот тип объекта не будет работать так, как вы этого хотите.

person Preston Marshall    schedule 20.07.2011

Лично я думаю...

def find(query = {})      
  mongo_collection.find(query_formatter(query))
end

def query_formatter(query)
  if query.respond_to?(:to_a) && !query.kind_of?(Hash)
    {:_id => {'$in' => query.to_a}}
  else
    query
  end
end
person Jeff Casimir    schedule 20.07.2011