1

I am trying to get a custom sort working. Eventually I will try to apply this to a database query but before that I want to prove the logic works in an in-memory sort. The problem is, I cannot get even the most simplest of sort methods to work!

Sort method:

  def self.sort!(reputation_alerts)
    #reputation_alerts.sort { |a, b| b <=> a }
    reputation_alerts.sort { |a, b| b.id <=> a.id }
  end

I had hoped the first sort (currently commented out) would return reputation_alerts in reverse order. It did nothing.

I had hoped the second sort would return reputation alerts sorted by id in reverse order. It did nothing.

I am testing the sort by iterating through the returned reputation_alerts object and dumping the contents of each one; every time the "sorted" results are the same as the original order...

Test method:

  test "ReputationAlertSort PassedArray SortsArrayCorrectly" do
    @customer = customers(:one)
    @customer.update(keywords: { 1 => "bob", 2 => "wibble", 3 => "random" } )
    @customer.get_keywords
    @customer.reputation_alerts.clear

    @customer.reputation_alerts.build(
        id: 2, rep_keyword: "random", search_engine: "bing", rank: 1)
    @customer.reputation_alerts.build(
        id: 1, rep_keyword: "wibble", search_engine: "google", rank: 2)
    @customer.reputation_alerts.build(
        id: 3, rep_keyword: "wibble", search_engine: "google", rank: 1)
    @customer.reputation_alerts.build(
        id: 4, rep_keyword: "wibble", search_engine: "yahoo", rank: 2)
    @customer.reputation_alerts.build(
        id: 5, rep_keyword: "wibble", search_engine: "yahoo", rank: 1)
    @customer.reputation_alerts.build(
        id: 6, rep_keyword: "bob", search_engine: "google", rank: 2)
    @customer.reputation_alerts.build(
        id: 7, rep_keyword: "bob", search_engine: "yahoo", rank: 1)
    @customer.reputation_alerts.build(
        id: 8, rep_keyword: "random", search_engine: "bing", rank: 2)

    ReputationAlert.sort!(@customer.reputation_alerts)

    assert_sorted(1, 7)
    assert_sorted(2, 6)
    assert_sorted(3, 5)
    assert_sorted(4, 4)
    assert_sorted(5, 3)
    assert_sorted(6, 2)
    assert_sorted(7, 1)
    assert_sorted(8, 8)

  end

  def assert_sorted(post_sort_position, pre_sort_position)
    assert_equal post_sort_position,
        @customer.reputation_alerts[pre_sort_position - 1].id, dump
  end

  def dump
    dump = ""
    @customer.reputation_alerts.each do |a|
      dump += "\n#{a.id}, #{a.rep_keyword}, #{a.search_engine}, #{a.rank}"
    end
    dump
  end

Any suggestions? Once I can get this working I can move on to the actual (complex) sort...

EDIT:

I thought the type of object I was trying to sort might be posing a problem, so I tried an even more basic sort:

temp.sort:

  test "ReputationAlertSort PassedArray SortsArrayCorrectly" do
    temp = 1..7
    temp.sort { |a, b| b <=> a }
    raise "#{temp}"
  end

This returns temp still unsorted. (Kind of expected due to the lack of a !)

temp.sort!

  test "ReputationAlertSort PassedArray SortsArrayCorrectly" do
    temp = 1..7
    temp.sort! { |a, b| b <=> a }
    raise "#{temp}"
  end

This gives the error: NoMethodError: undefined method `sort!' for 1..7:Range

temp = temp.sort!

  test "ReputationAlertSort PassedArray SortsArrayCorrectly" do
    temp = 1..7
    temp = temp.sort { |a, b| b <=> a }
    raise "#{temp}"
  end

This DOES work, at least, so maybe I can use that as a starting point to figure this out...

Zild
  • 43
  • 7

3 Answers3

1

You're not overriding the reputation_alerts object with your sort. Just add an exclamation mark to the sort itself:

def self.sort!(reputation_alerts)
  #reputation_alerts.sort! { |a, b| b <=> a }
  reputation_alerts.sort! { |a, b| b.id <=> a.id }
end

Edit:

Oops, I didn't think about it calling itself. I did try adding a similar method to my model and it seems to be working OK.

def self.sorting(models)
  models.sort!{ |a, b| b.id <=> a.id }
end

Model.sorting(models).map(&:id)
=> [17,16,15...]

On another note, all of this seems redundant, as .sort method should exist for any collection as it's in Enumerable. You should just be able to call reputation_alerts.sort! { |a, b| b.id <=> a.id }.

Edit2:

If you don't have sort! for some reason, just use the .sort as in your added question info:

models = models.sort{...}

This is the same as sort!.

nesiseka
  • 1,268
  • 8
  • 22
  • Thank, but apparently the method `.sort!` does not exist. (Well, that is not technically true; `.sort!` tries to call the the `self.sort!` function, but fails due to having the wrong number of parameter. After renaming the `self.sort!` function to something else, ruby then complains that `.sort!` does not exist...) I based the sort method on the example here: http://stackoverflow.com/questions/25484167/ruby-active-record-custom-sorting-order (As mentioned in that answer, I placed it in the corresponding model and made it a static method.) – Zild Jun 23 '15 at 08:25
  • nesiseka Thanks for the help, but I am getting nowhere with this. I have now replaced my test with the simplest sort code I can think of just to prove it works and am still getting either no sorting (using `.sort`) or undefined method (using `.sort!`). I will edit my original question to show the new code. – Zild Jun 23 '15 at 09:05
  • Well, that gets me back to about where I started - the code now runs but barely sorts! (The last entry gets inserted before the first,but from there on the order remains the same!) I will have to play with this a lot more tomorrow... – Zild Jun 23 '15 at 09:37
1

Your method sort! returns a sorted array, but you don't do anything with the result? The naming seems to indicate you do inplace sorting, but your method does not. So either use .sort! inside the method too, or rename the method to just sort and store the result and check that.

Secondly, your edit: temp is a range, to make sure sort works, you have to convert it to an array first. So if you write

temp = (1..7).to_a

it should work.

nathanvda
  • 49,707
  • 13
  • 117
  • 139
  • Thank you for this answer. It has helped a lot, but there is a little more to it than that. I hope you won't mind if I add a more complete solution and mark that as an answer. – Zild Jun 24 '15 at 01:08
0

The other answers have really helped resolve this, but there is a little more to it than that so here is a complete run-through of the solution:

1: .sort! will not work for the collection I am using. It is a collection of child elements (specifically using a has_many association). Attempting to call .sort! gives the error:

NoMethodError: undefined method `sort!' for #<ReputationAlert::ActiveRecord_Associations_CollectionProxy:0x61729b0>

2: Owner.Collection = Owner.Collection.sort { <snip> } will not work. Assigning the result of sorting a collection to the original collection itself results in an unsorted collection in this instance. Once again, I suspect this may be behaviour specific to ActiveRecord associations.

3: new_variable = Owner.Collection.sort { <snip> } works. Taking the result of the sort and assigning it to some other arbitrary variable and using that gives the desired (sorted) result.

In conclusion it seems the problem here is not the sort functionality itself, but specific issues with sort functionality on ActiveRecord associations.

(Of course, this may actually be a redundant concern as the plan is to eventually have the ActiveRecord query handle the sorting for us...)

Zild
  • 43
  • 7
  • The code you showed us had specific bugs, and I explained why they did not work, and offered a solution. The `sort` method on a activerecord relation is a sort on the database (meaning: it is added to the query), you will have to convert the association to array first, by doing a `to_a` for instance. Except: then you loose paging abilities for instance, which might or might not be what you want. Anyway: imho I answered your question, your answer uses detail that was not known to me (not explained in your question). – nathanvda Jun 24 '15 at 07:17
  • Indeed, and I appreciate the help. Apologies for not adding the necessary extra detail in the original question, but at the time of writing the question I did not know there was anything extra to add! To be honest I did not know until you said it just now that the sort method was applied to the query... – Zild Jun 24 '15 at 09:07