Rachel9494
Rachel9494

Reputation: 824

Ruby on Rails - Change Order Behavior

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

Answers (3)

Coenwulf
Coenwulf

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

Jeff F.
Jeff F.

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

steakchaser
steakchaser

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

Related Questions