Reputation: 13486
Let's say I have a User
with a field name
, and which has_many teams
, and a Team
that belongs_to a user
, and belongs_to a sport
. A Sport
has a field name
and has_many teams
.
I want to walk through the sports
, do some stuff, and collect an array of the teams
sorted by the name
of the user
.
result = []
Sport.asc(:name).each do |spt|
# some other stuff not relevant to this question but that
# justifies my looping through each sport.
spt.teams.asc(:'user.name').each { |t| result << t }
end
This runs but and the sorting of the sports
is as expected, but the order of the teams in result
is not sorted as I'd expect.
What is the correct way, using Mongoid
to sort a collection by the value of a relation?
Upvotes: 1
Views: 519
Reputation: 9649
I don't think there is a way to do this with Mongoid. It might work if the field you were sorting by was part of an embedded document, but not in this case where you're using a referenced document.
I guess you probably have two options here, the less efficient way would be to just sort the collection of teams in ruby:
sport.teams.sort{|t1, t2| t1.user.name <=> t2.user.name}.each{ |team| result << team }
The better, and arguably more 'MongoDB-y' solution, would be to cache the user name inside each team, using a before_save
callback, and then use that to sort the teams:
# app/models/team.rb
class Team
include Mongoid::Document
field :user_name, :type => String
before_save :update_user_name
protected
def update_user_name
self.user_name = self.user.name if self.user
end
end
Then you can just do:
spt.teams.asc(:user_name).each { |t| result << t }
Obviously, if the user's name field is mutable, then you'll to trigger it to save each child-team whenever the user's name field is changed.
class User
after_save :update_teams_if_name_changed
def update_teams_if_name_changed
if self.name_changed?
self.teams.each { |team| team.save }
end
end
end
Given that is not fantastically simple to maintain, this could arguably be a good candidate to use an observer, rather than callbacks, but you get the idea.
Upvotes: 3