Search code examples
ruby-on-railsrandomgeneratoroffset

Rails 4: using offset on a randomized list produces unexpected results


...or maybe, i just don't get it.

TL;DR - i get totally randomized results with duplicates and such. No idea why.

Here's the problem - i'm trying to make a rails app generating tournament brackets. The user can select whether he'd like to randomize the team list when generating the bracket, or later assign the teams by himself.

If i choose the latter option and generate the bracket as the ids go, everything is fine. If i choose to randomize the list, the list itself is fine, but later when assigning teams to particular matches (in a loop), the resulst are completely unorganized and unexpected (at least to me) eg.

instead of
Match 1: Team 1 vs Team 2
Match 2: Team 3 vs Team 4
etc. (as in the normally generated list)

i get something completely random with frequent duplicates like:
Match 1: Team 1 vs Team 1
Match 2: Team 1 vs Team 9

if @seed = 'random'
  @team_list = @tournament.teams.order("RAND()")
else
  @team_list = @tournament.teams.order(:id)
end

Here's how i assign the teams. The thing that screws everything up seems to be the "offset" part, and i don't get it, why.

@match.team_a = @team_list.offset((2*match_number)-2).limit(1).first               
@match.team_b = @team_list.offset((2*match_number)-1).limit(1).first

EDIT:

A sample of @team_list data as requested. That's the randomized one.

#<ActiveRecord::AssociationRelation 
[#<Team id: 2, team_name: "test_team_a", reputation: 0, created_at: "2016-08-25 09:29:20", updated_at: "2016-08-26 11:08:21", team_leader_id: 2>, 
#<Team id: 9, team_name: "test_team_b", reputation: 0, created_at: "2016-08-30 23:01:17", updated_at: "2016-08-30 23:01:17", team_leader_id: 2>, 
#<Team id: 3, team_name: "test_team_c", reputation: 0, created_at: "2016-08-30 22:59:16", updated_at: "2016-08-30 22:59:16", team_leader_id: 2>, 
#<Team id: 7, team_name: "test_team_d", reputation: 0, created_at: "2016-08-30 23:00:35", updated_at: "2016-08-30 23:00:35", team_leader_id: 2>, 
#<Team id: 5, team_name: "test_team_e", reputation: 0, created_at: "2016-08-30 23:00:07", updated_at: "2016-08-30 23:00:07", team_leader_id: 2>, 
#<Team id: 6, team_name: "test_team_f", reputation: 0, created_at: "2016-08-30 23:00:23", updated_at: "2016-08-30 23:00:23", team_leader_id: 2>, 
#<Team id: 4, team_name: "test_team_g", reputation: 0, created_at: "2016-08-30 22:59:41", updated_at: "2016-08-30 22:59:41", team_leader_id: 2>, 
#<Team id: 8, team_name: "test_team_h", reputation: 0, created_at: "2016-08-30 23:00:46", updated_at: "2016-08-30 23:00:46", team_leader_id: 2>]>

Solution

  • Just to shed some more light on the above answer, when you do @team_list = @tournaments.teams.order("RAND()"), that does this query (presumably in SQL):

    SELECT teams.* FROM teams ORDER BY RAND()
    

    (I know teams belongs to tournaments, but you get my point.)

    And then when you do @team_list.offset(x).limit(1), that does this query:

    SELECT teams.* FROM teams ORDER BY RAND() LIMIT 1 OFFSET x
    

    Because of ActiveRecord relations, each time you do a new thing, you're doing a query. You can test this out in rails console - it'll tell you the queries it does.

    EDIT:

    If you want to use your code as is, just convert the result of the first query to an array and don't use offset/limit. So:

    @team_list = @tournaments.teams.order("RAND()").to_a
    ...
    @match.team_a = @team_list[2 * match_number - 2]
    @match.team_b = @team_list[2 * match_number - 1]
    

    This means that you will be loading all of the teams at once (I think) and not lazily loading them as ActiveRecord relations allow you to do.