Mongoid: упорядочение по вложенному дочернему значению - PullRequest
4 голосов
/ 21 сентября 2011
class Box
  embeds_many :things
  after_init :add_default_things

  def add_default_things
    self.things.build(title: "Socks", value: 2)
    self.things.build(title: "Ring", value: 1)
  end
end

class Thing
  field :title
  field :value, type: Integer
end

все коробки имеют некоторые вещи по умолчанию: носки и кольцо.Каждый может добавить ту или иную вещь в коробку.Так что теперь мне нужно заказать все коробки по количеству носков:

Box.order_by(:thing_with_title_socks.value.desc) # ???

Ответы [ 2 ]

1 голос
/ 05 октября 2011

Я плохо разбираюсь в ruby, поэтому я попытаюсь объяснить это в терминах java.Проблема в том, как вы проектируете свои данные.Например, у вас есть n классов Box, в каждом из которых есть список элементов:

public class Box {
  public List<Item> items;
}

public class Item {
  public String name;
  public int value;
}

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

public class Box {
  public Map<Integer, Item> items;
}

public class Item {
  public int id;
  public String name;
  public int value;
}

таким образом, я бы проверил, существует ли элемент в коробке или нет;получить доступ к значению элементов со сложностью O (1), и я бы не ошибочно положил 2 разных пакета носков в коробку, что привело бы к ошибке (какую бы она отсортировала?).

ваши текущие данныеструктура выглядит следующим образом:

{
  things : [ {"name" : "socks", "value" : 2} , {"name" : "ring", "value" : 5} ]
}

, но если вы собираетесь выполнять сортировку / прямой доступ (используя запрос) к «известному» объекту (например, кольцо или носки), тогда ваши данные должны выглядеть следующим образом:

{
  things : { "socks": 2, "ring" : 5 }
}

если у вас есть дополнительная информация, прикрепленная к этим предметам, они также могут выглядеть следующим образом:

{
  things : { "S01" : { "name" : "super socks", "value" : 1 }, "S02" : { "name" : "different socks", "value" : 2} }
}

таким образом, у вас есть прямой доступ к предметам в коробке.

надеюсь, что это поможет.

Редактировать : Я думал, что это очевидно, но просто для ясности: вы не можете эффективно запрашивать «перечисленные» дочерние данные, если только вызнать точное положение.(Вы всегда можете использовать карту / уменьшить, хотя)

1 голос
/ 05 октября 2011

Это сложно (или невозможно) сделать с помощью одного запроса. Я играл с этим некоторое время. Прежде всего, позвольте мне опубликовать весь мой тестовый скрипт:

require 'mongoid'

Mongoid.configure do |config|
  config.master = Mongo::Connection.new.db("test")
end

class Box
  include Mongoid::Document
  embeds_many :things
  field :name
end

class Thing
  include Mongoid::Document
  field :title
  field :value, type: Integer
end

b = Box.new
b.name = "non sock box"
b.things.push Thing.create!(title: "Ring", value:1)
b.save!

b1 = Box.new
b1.name = "sock box"
b1.things.push Thing.create!(title:"Socks", value:50)
b1.save!

b2 = Box.new
b2.name = "huge sock box"
b2.things.push Thing.create!(title:"Socks", value:1000)
b2.things.push Thing.create!(title:"Ring", value:1100)
b2.save!

b3 = Box.new
b3.name = "huge ring box"
b3.things.push Thing.create!(title:"Socks", value:100)
b3.things.push Thing.create!(title:"Ring", value:2600)
b3.save!

b4 = Box.new
b4.name = "ring first sock box"
b4.things.push Thing.create!(title: "Ring", value: 1200)
b4.things.push Thing.create!(title: "Socks", value: 5)
b4.save!

Итак, вы делаете это несколькими способами. Во-первых, вы можете найти все объекты Thing, которые соответствуют вашим критериям, а затем что-то сделать с Things. Например, получите Box ID.

Thing.desc(:value).where(:title => "Socks").to_a

Это переносит проблему на уровень приложения, что типично для некоторых запросов монго. Но это не то, что вы просили. Поэтому я отправляю этот неприятный запрос. Это работает, только если у вас всегда есть вещи Sock во встроенном документе.

Box.all(conditions: { "things.title" => "Socks" }, 
  sort:[["things.0.value", :desc]]).to_a

Что действительно раздражает, потому что вы заметите, что в моих примерах данных у меня есть «огромный кольцевой ящик», который имеет носки, но также имеет атрибут наибольшего значения, так что фактически он возвращается первым. Если вы пересортируете вещи, этот запрос на самом деле будет работать. Я не знаю, как сказать, сортировать по вещей.значению, равному "foo". Держу пари, ты не можешь.

>> Box.all(conditions: { "things.title" => "Socks" }, 
sort:[["things.0.value", :desc]]).collect(&:name)
=> ["ring first sock box", "huge sock box", "huge ring box", "sock box"]

Видите, это неправильно. Огромная коробка носка должна быть первой. Но это не из-за вещей. 0 выбирает самое большое значение кольца 2600 в (b4) образца данных.

Вы можете заставить работать один лайнер, но он фактически выполняет (как минимум) два запроса:

Thing.desc(:value).where(:title => "Socks").collect(&:id).each {|thing| 
  puts Box.where("things._id" => thing).first.name }

Но опять же, это действительно делает лифтинг в рубине, а не монго. Есть много таких проблем с присоединением. Вы должны сделать два запроса.

Другой вариант может состоять в том, чтобы нормализовать ваши данные и просто сохранить вещи как встроенный массив атрибутов или просто атрибутов. Если сундуки могут содержать что-либо, вам даже не нужно определять атрибуты заранее. Это классная вещь в MongoDB.

class Chest
  include Mongoid::Document
  field :name
end

c = Chest.create!(:name => "sock chest", :socks => 50, :ring => 1)
c1 = Chest.create!(:name => "non sock chest", :ring => 10)
c2 = Chest.create!(:name => "huge sock chest", :ring => 5, :socks => 100)
c3 = Chest.create!(:name => "huge ring chest", :ring => 100, :socks => 25)

Chest.where(:socks.exists => true).desc(:socks).collect(&:name)
=> ["huge sock chest", "sock chest", "huge ring chest"]
...