I have an array of objects that I need to sort by a position attribute that could be an integer or nil, and I need the objects that have the nil position to be at the end of
To be fair, I'm not very familiar with Ruby, so take this as more of an algorithm idea rather than a code one... and rewrite the ?: operator as whatever Ruby has that's cleaner.
Can't you just check for nil in the comparison:
class Parent
def sorted_children
children.sort{|a,b|( a and b ) ? a <=> b : ( a ? -1 : 1 ) }
end
end
Edited to use Glenra's code, which implements the same thing as mine but in a smaller (and probably easier to read) amount of code.
I handle these kinds of things like this:
children.sort_by {|child| [child.position ? 0 : 1,child.position || 0]}
You can do this without overriding the spaceship operator by defining a new comparison method.
class Child
include Comparable
def compare_by_category(other)
return 0 if !category && !other.category
return 1 if !category
return -1 if !other.category
category.position <=> other.category.position
end
end
The sort
method can take a block, so you can then sort using this new method:
children.sort {|a,b| a.compare_by_category(b) }
The most simple solution for me is
def sorted_children(children)
children.sort_by { |child| child.position || -1}
end
I haven't done Ruby in a while, but you could split the null-checking from the sorting (and just allow Child#position to return null):
def sorted_children
children.reject{|c| c.position.nil?}.sort_by(&:position) +
children.select{|c| c.position.nil?}
end
Admittedly it's not the most efficient solution, but it doesn't have any magic numbers.
How about in Child
defining <=>
to be based on category.position
if category
exists, and sorting items without a category
as always greater than those with a category
?
class Child
# Not strictly necessary, but will define other comparisons based on <=>
include Comparable
def <=> other
return 0 if !category && !other.category
return 1 if !category
return -1 if !other.category
category.position <=> other.category.position
end
end
Then in Parent
you can just call children.sort
.