MongoDB - карта Mongoid уменьшает базовую операцию

Я только что начал с MongoDB и mongoid. Самая большая проблема, с которой я сталкиваюсь, заключается в понимании функциональности карты/сокращения, чтобы иметь возможность выполнять очень базовую группировку и т.д.

Предположим, у меня есть такая модель:

class Person
  include Mongoid::Document
  field :age, type: Integer
  field :name
  field :sdate
end

Эта модель создаст такие объекты:

#<Person _id: 9xzy0, age: 22, name: "Lucas", sdate: "2013-10-07">
#<Person _id: 9xzy2, age: 32, name: "Paul", sdate: "2013-10-07">
#<Person _id: 9xzy3, age: 23, name: "Tom", sdate: "2013-10-08">
#<Person _id: 9xzy4, age: 11, name: "Joe", sdate: "2013-10-08">

Может ли кто-нибудь показать, как использовать карту mongoid уменьшить, чтобы получить коллекцию тех объектов, сгруппированных по полю sdate? И чтобы получить сумму возрастов тех, которые используют одно и то же поле sdate?

Я знаю об этом: http://mongoid.org/en/mongoid/docs/querying.html#map_reduce Но почему-то это поможет увидеть, что это применимо к реальному примеру. Где этот код идет, в модели, я думаю, требуется область и т.д.

Я могу сделать простой поиск с помощью mongoid, получить массив и вручную построить все, что мне нужно, но я думаю, что сокращение карты - вот здесь. И я полагаю, что эти функции js, упомянутые на странице mongoid, отправляются в БД, что делает эти операции внутренне. Исходя из активной записи, эти новые концепции немного странны.

Я на Rails 4.0, Ruby 1.9.3, Mongoid 4.0.0, MongoDB 2.4.6 на Heroku (mongolab), хотя у меня есть локальный 2.0, который я должен обновить.

Спасибо.

Ответ 1

Взяв примеры из http://mongoid.org/en/mongoid/docs/querying.html#map_reduce и адаптировав их к вашей ситуации и добавив комментарии для объяснения.

map = %Q{
  function() {
    emit(this.sdate, { age: this.age, name : this. name }); 
      // here "this" is the record that map
      // is going to be executed on
  }  
}

reduce = %Q{
  function(key, values) {   
           // this will be executed for every group that
           // has the same sdate value
    var result = { avg_of_ages: 0 };
    var sum = 0;    // sum of all ages
    var totalnum = 0  // total number of people
    values.forEach(function(value) {
      sum += value.age;    
    });
    result.avg_of_ages = sum/total   // finding the average
    return result;
  }
}

results = Person.map_reduce(map, reduce) //You can access this as an array of maps

first_average = results[0].avg_of_ages

results.each do |result|
   // do whatever you want with result
end

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

 results = Person.collection.aggregate([{"$group" => { "_id" => {"sdate" => "$sdate"}, 
                                                "avg_of_ages"=> {"$avg" : "$age"}}}])

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