fabriciofreitag fabriciofreitag - 4 months ago 8
Ruby Question

Order by attribute and randomize recurrent

Given that Venue has a field "featured_level" that could go from 0 to N.

I need to return venues ordered by featured_level, but randomizing the order of the venues with the same featured_level.

UPDATE:

Using the given answer I have this method:

def self.by_featured_level
all.group_by {|v| v.featured_level}.inject([]) { |memo, (level,values)|
memo << { level => values.shuffle }
}.map { |hash| hash.values }.flatten.reverse
end


But it fails the following test (also doesn't work properly with real numbers), I'm still trying to figure it out, test used:

describe "by featured level" do
before do

@venue1 = create(:venue, featured_level: 5)
@venue2 = create(:venue, featured_level: 2)
@venue3 = create(:venue, featured_level: 4)
@venue4 = create(:venue, featured_level: 2)
@venue5 = create(:venue, featured_level: 0)
@venue6 = create(:venue, featured_level: 2)
@venues = Venue.by_featured_level

end

it {
start_with_hightest = @venues.index(@venue1) == 0
expect(start_with_hightest).to be_truthy
}
it {
second_hightest_is_2nd = @venues.index(@venue3) == 1
expect(second_hightest_is_2nd).to be_truthy
}
it {
ends_with_lowest = @venues.last.id == @venue5.id
expect(ends_with_lowest).to be_truthy
}
end

Answer

Let's start by collecting the venues:

require 'ostruct'

@venue1 = OpenStruct.new(id: :a, level: 0)
@venue2 = OpenStruct.new(id: :b, level: 2)
@venue3 = OpenStruct.new(id: :c, level: 2)
@venue4 = OpenStruct.new(id: :d, level: 2)
@venue5 = OpenStruct.new(id: :e, level: 4)
@venue6 = OpenStruct.new(id: :f, level: 5)

@venues = [@venue1, @venue2, @venue3, @venue4, @venue5, @venue6]

We want to randomize per level, so we'll create groups:

@venues.group_by { |v| v.level }

This returns a hash where the venues are indexed by each level.

Now to iterate over the hash and return a new hash where the values have been randomized:

@venues.
  group_by {|v| v.level}.
  inject([]) { |memo, (level,values)|
   memo << { level => values.shuffle }
  }

This hash can now be flattened so the final result is a list of hashes, randomized per level:

@venues.
  group_by {|v| v.level}.
  inject([]) { |memo, (level,values)|
   memo << { level => values.shuffle }
  }.
  map { |hash| hash.values }.
  flatten
Comments