Ordering HABTM?

387 views
Skip to first unread message

Alex Wolfe

unread,
Aug 17, 2011, 8:02:13 PM8/17/11
to Mongoid
Should reordering a HABTM association have the expected effect? I ask
because reordering the association seems to reorder the parent's child
ID collection, but not the child instance collection.


require 'mongoid'

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

class Car
include Mongoid::Document
has_and_belongs_to_many :drivers
end

class Driver
include Mongoid::Document
has_and_belongs_to_many :cars
end

driver1 = Driver.create
driver2 = Driver.create

car = Car.create
car.drivers = [driver1, driver2]
car.save

# Try reordering the existing association
car.drivers.delete(driver1)
car.drivers << driver1

car.save
car.reload

expected_order = [driver2.id, driver1.id]

if car.driver_ids != expected_order
raise "Driver IDs were not reordered"
end

if car.drivers.collect(&:id) != expected_order
raise "Drivers were not reordered"
end

Nick Hoffman

unread,
Aug 17, 2011, 8:46:23 PM8/17/11
to mon...@googlegroups.com
On Wednesday, August 17, 2011 4:02:13 PM UTC-4, Alex Wolfe wrote:
Should reordering a HABTM association have the expected effect?  I ask
because reordering the association seems to reorder the parent's child
ID collection, but not the child instance collection.

Hey Alex. At first, one would think that the order of IDs would matter. However, that's not the case. The related documents that MongoDB returns are ordered according to the query that MongoDB executes.

If MongoDB receives a query with no sorting options defined, the documents that're returned are in "forward natural order". See this page in the MongoDB docs for more info:
http://www.mongodb.org/display/DOCS/Sorting+and+Natural+Order

Andy Selvig

unread,
Mar 5, 2012, 11:23:30 PM3/5/12
to mon...@googlegroups.com
Hey Alex. At first, one would think that the order of IDs would matter. However, that's not the case. The related documents that MongoDB returns are ordered according to the query that MongoDB executes.

This is interesting, I just came upon this problem as well. I thought that I could change the order of my HABTM by simply arranging the id's stored in the other document. 

So, there is still a problem that needs to be solved here. I can think of a couple ways to do it:

 - Store an array of object id's manually and get the objects with something like: obj.other_ids.map{|id| Other.find(id)}

 - Make a join collection like I would with a relational database, and keep an order property in each document

Neither of these seems ideal, and it doesn't seem like Mongoid is giving me any help. Is there a better way to accomplish this?

Nick Hoffman

unread,
Mar 9, 2012, 4:14:03 PM3/9/12
to mon...@googlegroups.com
Hi Andy. There's a third, better, solution, actually: retrieve the related documents, then sort them based on the position of their ID in the foreign key. See Car#sorted_drivers in this example:

class Car
  include Mongoid::Document

  has_and_belongs_to_many :drivers

  def sorted_drivers
    drivers.sort {|a, b| driver_ids.index(a.id) <=> driver_ids.index(b.id) }
  end
end

class Driver
  include Mongoid::Document

  has_and_belongs_to_many :cars
end

Car.delete_all
Driver.delete_all

driver1 = Driver.create
driver2 = Driver.create
driver3 = Driver.create

puts "driver1 = #{driver1.id}"
puts "driver2 = #{driver2.id}"
puts "driver3 = #{driver3.id}"
puts

car = Car.create
car.drivers = [driver3, driver2, driver1]
car.save

puts "car.driver_ids      = #{car.driver_ids}"
puts

car.drivers.delete driver2
car.save
car.drivers << driver2

car.save
car.reload

puts "car.driver_ids      = #{car.driver_ids}"
puts

puts "car.drivers         = #{car.drivers.map(&:id)}"
puts

puts "car.sorted_drivers  = #{car.sorted_drivers.map(&:id)}"
puts

Andy Selvig

unread,
Mar 9, 2012, 7:35:00 PM3/9/12
to mon...@googlegroups.com
Thanks for the response, Nick. This makes sense, although I could swear that the reordering assignment wasn't actually being saved to the database. Specifically, calling something like:

car.drivers = [driver3, driver2, driver1]
car.save

after car.drivers had already been set had no effect on the database. I'll go back and verify, though.

Nick Hoffman

unread,
Mar 10, 2012, 5:17:37 PM3/10/12
to mon...@googlegroups.com
On Friday, 9 March 2012 14:35:00 UTC-5, Andy Selvig wrote:
Thanks for the response, Nick. This makes sense, although I could swear that the reordering assignment wasn't actually being saved to the database. Specifically, calling something like:

car.drivers = [driver3, driver2, driver1]
car.save

after car.drivers had already been set had no effect on the database. I'll go back and verify, though.


You'll probably have more luck setting car.driver_ids , because that specifically sets the order of the IDs.

Andy Selvig

unread,
Mar 10, 2012, 5:56:20 PM3/10/12
to mon...@googlegroups.com
Yeah, I tried that as well. I'm still not able to get it to stick.

My models in question are Page and Section. They both have a HABTM declaration to each other. Here's some controller code where I'm trying to rearrange the sections for a particular page:

@page = Page.find params[:page_id]
old_section_ids = @page.section_ids
@page.section_ids = params[:section_ids].map{|id| BSON::ObjectId.from_string(id)}
logger.debug "-- set section ids from #{old_section_ids} to #{@page.section_ids}"
@page.save

and here's the log output:

Processing by PageController#arrange_sections as JSON
  Parameters: {"section_ids"=>["4f5ae8ea594cc402df000004", "4f56ee32594cc447b800005b"], "page_id"=>"4f245424594cc4316b00000a"}

MONGODB tsb_development['pages'].find({:_id=>BSON::ObjectId('4f245424594cc4316b00000a')}).limit(-1).sort([[:_id, :asc]])

-- set section ids from [BSON::ObjectId('4f56ee32594cc447b800005b'), BSON::ObjectId('4f5ae8ea594cc402df000004')] to [BSON::ObjectId('4f5ae8ea594cc402df000004'), BSON::ObjectId('4f56ee32594cc447b800005b')]

MONGODB tsb_development['pages'].update({"_id"=>BSON::ObjectId('4f245424594cc4316b00000a')}, {"$set"=>{"updated_at"=>2012-03-10 17:50:17 UTC}})

As you can see, the new section_ids value is being correctly set on the page object (from [*5b,*04] to [*04,*5b]), but does not actually get persisted. Am I doing something wrong?

Robin Stocker

unread,
Mar 12, 2012, 2:33:44 PM3/12/12
to mon...@googlegroups.com
On Saturday, March 10, 2012 6:56:20 PM UTC+1, Andy Selvig wrote:
As you can see, the new section_ids value is being correctly set on the page object (from [*5b,*04] to [*04,*5b]), but does not actually get persisted. Am I doing something wrong?

Sounds a lot like this (which is fixed in >= 2.4.4):

https://github.com/mongoid/mongoid/issues/1705

What version are you using?

Another similar issue which you may encounter is this (which is only on master AFAICS):

https://github.com/mongoid/mongoid/pull/1709

Andy Selvig

unread,
Mar 12, 2012, 3:14:37 PM3/12/12
to mon...@googlegroups.com
Ah, I'm on 2.4.3. I'll upgrade and give it a shot. Thanks, Robin.
Reply all
Reply to author
Forward
0 new messages