Undr

На память

MongoId документы и метод to_a

1 Star2 Stars3 Stars4 Stars5 Stars (Еще не оценили)
Loading ... Loading ...

without comments

Сегодня натолкнулся на странный баг в программе. Использовал Mongoid для работы с MongoDb. Моделька была примерно такая:

class Content::Airport < Content::Place
  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.

При попытке создать аэропорт привязанный к городу выпадала ошибка:

BSON::InvalidDocument: Cannot serialize an object of class Content::City into BSON.
    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 для связывания двух моделей:

def create_relation(object, metadata)
  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. Выглядет это так:

def build(type = nil)
  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? и она довольно странная:

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

Написал undr ()

4 августа 2011 в 16:50

Оставьте комментарий