Reputation: 824
I'm having a minor problem with how RoR behaves when I tell it to display a result in a certain order.
I have a table called categories that contains a code column. Values in this code column include 1, 6, 12A, and 12B. When I tell the system to display the result (a dropdown) by order according to a foreign key id number and then a code value, it lists the codes in the order of 1, 12A, 12B, and 6 (which ideally should be 1, 6, 12A, and 12B).
Here is the dropdown:
collection_select(:category, :category_id, Category.order(:award_id, :code), :id, :award_code_category)
I know part of the problem is the A and B part of those two codes, I can't treat them as strict integers (code is a string type in the table).
I would love any thoughts about this.
Upvotes: 1
Views: 140
Reputation: 1937
To sort an array of those values you would:
["1", "6", "12A", "12B"].sort do |x, y|
res = x.to_i <=> y.to_i
res = x <=> y if res == 0
res
end
To get the categories sorted in that order could do something like:
categories = Category.all.sort do |x, y|
res = x.code.to_i <=> y.code.to_i
res = x.code <=> y.code if res == 0
res
end
From your code I inferred that you may want to sort on award_id with a second order sort on code. That would look like:
categories = Category.all.sort do |x, y|
res = x.award_id <=> y.award_id
res = x.code.to_i <=> y.code.to_i if res == 0
res = x.code <=> y.code if res == 0
res
end
Upvotes: 0
Reputation: 1067
steakchaser's answer called on:
['1', '12B', '12A', '6']
would return
['1', '6', '12B', '12A']
You lose the ordering of the letters.
You could create a helper to do the sorting:
def self.sort_by_category_codes(categories)
sorted_categories = categories.sort do |cat1, cat2|
if cat1.award_id == cat2.award_id
# award id matches so compare by code
if cat1.code.to_i == cat2.code.to_i
# the leading numbers are the same (or no leading numbers)
# so compare alphabetically
cat1.code <=> cat2.code
else
# there was a leading number so sort numerically
cat1.code.to_i <=> cat2.code.to_i
end
else
# award ids were different so compare by them
cat1.award_id <=> cat2.award_id
end
end
return sorted_categories
end
Both ['1', '12A', '12B', '6']
and ['1', '12B', '12A', '6']
would return ['1', '6', '12A', '12B']
Then call:
collection_select(:category, :category_id, sort_by_category_codes(Category.all), :id, :award_code_category)
The only issue I see with my solution is that codes that start with letters such as just 'A' would be returned ahead of numbers. If you need 'A' to be returned after '1A' you'll need some extra logic in helper method.
Upvotes: 1
Reputation: 5249
You could use a regex (most flexible depending on the pattern you really need to find) as part of the sorting to extract out the numeric portion:
['1', '12A', '12B', '6'].sort{|c1, c2| c1[/\d*(?:\.\d+)?/].to_i <=> c2[/\d*(?:\.\d+)?/].to_i}
Deleting non-integers when sorting is also a little easier to read:
['1', '12A', '12B', '6'].sort{|c1, c2| c1.delete("^0-9.").to_i <=> c2.delete("^0-9.").to_i}
Upvotes: 0