I have a Mongo collection that simply references an ID to another collection. Hypothetically the collection I'm specifically referring to might be called:
Walks. Walks has a reference to owner_id. The owner takes many walks with his many pets every day. What I want to do is query Walks for a list of N owner_ids and get only the last walk they took for each owner and group that by owner_id. To get a list of all walks by said list we'd do something like.
Walk.any_in(:owner_id => list_of_ids)
My question is, is there a way to query that list_of_ids, get only one walk per owner_id (the last one they took which can be sorted by the field created_at
and returned in a hash where each walk is pointed to by an owner_id such as:
{ 5 => {..walk data..}, 10 => {.. walk data ..}}
Here's an answer that uses MongoDB's group command. For the purposes of testing, I've used walk_time instead of created_at . Hope that this helps and that you like it.
class Owner
include Mongoid::Document
field :name, type: String
has_many :walks
end
class Walk
include Mongoid::Document
field :pet_name, type: String
field :walk_time, type: Time
belongs_to :owner
end
test/unit/walk_test.rb
require 'test_helper'
class WalkTest < ActiveSupport::TestCase
def setup
Owner.delete_all
Walk.delete_all
end
test "group in Ruby" do
walks_input = {
'George' => [ ['Fido', 2.days.ago], ['Fifi', 1.day.ago], ['Fozzy', 3.days.ago] ],
'Helen' => [ ['Gerty', 4.days.ago], ['Gilly', 2.days.ago], ['Garfield', 3.days.ago] ],
'Ivan' => [ ['Happy', 2.days.ago], ['Harry', 6.days.ago], ['Hipster', 4.days.ago] ]
}
owners = walks_input.map do |owner_name, pet_walks|
owner = Owner.create(name: owner_name)
pet_walks.each do |pet_name, time|
owner.walks << Walk.create(pet_name: pet_name, walk_time: time)
end
owner
end
assert_equal(3, Owner.count)
assert_equal(9, Walk.count)
condition = { owner_id: { '$in' => owners[0..1].map(&:id) } } # don't use all owners for testing
reduce = <<-EOS
function(doc, out) {
if (out.last_walk == undefined || out.last_walk.walk_time < doc.walk_time)
out.last_walk = doc;
}
EOS
last_walk_via_group = Walk.collection.group(key: :owner_id, cond: condition, initial: {}, reduce: reduce)
p last_walk_via_group.collect{|r|[Owner.find(r['owner_id']).name, r['last_walk']['pet_name']]}
last_walk = last_walk_via_group.collect{|r|Walk.new(r['last_walk'])}
p last_walk
end
end
test output
Run options: --name=test_group_in_Ruby
# Running tests:
[["George", "Fifi"], ["Helen", "Gilly"]]
[#<Walk _id: 4fbfa7a97f11ba53b3000003, _type: nil, pet_name: "Fifi", walk_time: 2012-05-24 15:39:21 UTC, owner_id: BSON::ObjectId('4fbfa7a97f11ba53b3000001')>, #<Walk _id: 4fbfa7a97f11ba53b3000007, _type: nil, pet_name: "Gilly", walk_time: 2012-05-23 15:39:21 UTC, owner_id: BSON::ObjectId('4fbfa7a97f11ba53b3000005')>]
.
Finished tests in 0.051868s, 19.2797 tests/s, 38.5594 assertions/s.
1 tests, 2 assertions, 0 failures, 0 errors, 0 skips
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.