MongoId документы и метод to_a
Сегодня натолкнулся на странный баг в программе. Использовал Mongoid для работы с MongoDb. Моделька была примерно такая:
field :coordinates, :type => Array
field :index, :type => Integer
index [[ :coordinates, Mongo::GEO2D ]]
referenced_in :parent, :class_name => 'Content::City', :inverse_of => :children, :index => true, :validate => false
def city
parent
end
def to_a
[
code,
name,
coordinates.try(:join, ':'),
city.try(:name),
city.try(:index).try(:to_i),
index.to_i
]
end
end
В классе Content::Place добавлялись некоторые общие поля и модуль Mongoid::Tree.
При попытке создать аэропорт привязанный к городу выпадала ошибка:
from /Users/undr/.rvm/gems/ruby-1.9.2-p180/gems/bson-1.3.1/lib/bson/bson_c.rb:24:in `serialize'
from /Users/undr/.rvm/gems/ruby-1.9.2-p180/gems/bson-1.3.1/lib/bson/bson_c.rb:24:in `serialize'
from /Users/undr/.rvm/gems/ruby-1.9.2-p180/gems/mongo-1.3.1/lib/mongo/cursor.rb:425:in `construct_query_message'
После дебагинга выяснилось что метода to_a не должно быть в классах документа Mongoid.
Метод create_relation из файла /lib/mongoid/relations/accessors.rb, ищет объект Mongoid::Relations::Referenced::Proxy для связывания двух моделей:
type = @attributes[metadata.inverse_type]
target = metadata.builder(object).build(type)
target ? metadata.relation.new(self, target, metadata) : nil
end
А строчка target = metadata.builder(object).build(type) ищет целевой объект, куда добавить аэропорт, то есть объект Content::City. Выглядет это так:
return object unless query?
begin
(type ? type.constantize : metadata.klass).find(object)
rescue Errors::DocumentNotFound
return nil
end
end
Тут object может принимать два вида значений, первый – искомый объект, то есть объект Content::City или запрос на поиск искомого объекта (как я понял хэш или BSON::ObjectId). Если object и есть требуемый объект, то он возвращается из метода, если нет, то происходит поиск и возвращается результат. Эта проверка реализована в методе query? и она довольно странная:
return true unless object.respond_to?(:to_a)
obj = object.to_a.first
!obj.respond_to?(:attributes) && !obj.nil?
end
То есть, если object не имеет метод to_a, то он считается требуемым объектом. Почему так сделано сложно сказать, возможно потому что object может быть не самим объектом, а Mongoid::Criteria указывающая на него, а BSON::ObjectId и Hash имеют метод to_a. Даже в этом случае эта проверка выглядит странно.
В итоге нельзя использовать метод to_a в документе Mongoid.

