marcosdsanchez
marcosdsanchez

Reputation: 2609

Datamapper assignment behaviour

I have a script that creates a soccer fixture.

Each club can play just one game with the other clubs and it could be at home or away.

This is my actual code which is not working with 20 clubs (It's creating 145 matches instead of 190 and the clubs are not playing the same amount of games)

  clubs_to_play = all_clubs = Club.all #problematic line

  all_clubs.each do |club|
    home = true
    clubs_to_play.delete(club)
    clubs_to_play.each do |club_to_play|
      f = Fixture.new
      if (home)
        f.home = club
        f.away = club_to_play
        home = false
      else
        f.home = club_to_play
        f.away = club
        home = true
      end
      f.save
    end
  end
end

But if I change the first line to be:

clubs_to_play = Club.all
all_clubs = Club.all

The script works and generates 190 matches. Why is that?

Upvotes: 0

Views: 74

Answers (1)

d11wtq
d11wtq

Reputation: 35298

When you do this:

clubs_to_play = all_clubs = Club.all

What you're basically doing is this:

all_clubs = Club.all
clubs_to_play = all_clubs

They are the same object (which you will see if you look at their object_ids).

DataMapper does not materialize record sets until a kicker method is invoked. That basically means no SQL is executed until you start iterating. Of course, since these variables both point to the same object, when you start iterating all_clubs, you pull all the records from the database in both all_clubs and clubs_to_play, since they are the exact same object.

Conversely, when you do this:

all_clubs = Club.all
clubs_to_play = Club.all

Here all_clubs and clubs_to_play reference different objects. That is to say, they have different object_ids and when you iterate all_clubs all of its records are materialized, but clubs_to_play remains unaffected until you iterate it (and delete values from the other collection).

Since your algorithm is assuming that all_clubs and clubs_to_play can be modified independently of one other, you are getting undesired behaviour when they are the same object.

Try something like this (slightly more functional) approach:

Club.all.combination(2).each_with_index do |(club1, club2), idx|
  Fixture.create(
    :home => idx.even? ? club1 : club2,
    :away => idx.even? ? club2 : club1
  )
end

There are technically more functional ways to maintain the alternation between home and away, but they are more convoluted. Enumerable#combination returns all possible (unique) combinations of values in the collection.

Upvotes: 1

Related Questions