yellowreign
yellowreign

Reputation: 3638

Rails Infinite Loop - Stack Level too Deep

I'm running into a stack level too deep and have an infinite loop, but I can't figure out why, so I'm hoping someone else can spot it.

I have this method in my Game model:

def self.record_win_or_tie(game_id)
  game = Game.find(game_id)

  if game.home_team_score > game.away_team_score 
    game.home_team_won = true
    game.save
  end

end

When I run it from the console for a game where the conditional is true (ie game.home_team_score is greater than game.away_team_score) if keeps running the same query over and over again.

SELECT `games`.* FROM `games` WHERE `games`.`id` = 1 LIMIT 1

If I run the code for a game_id where the conditional is false, the query looking for the game only happens once and there is no infinite loop.

* UPDATE *

I figured out that the problem was that I was calling the method from my GameObserver:

class GameObserver < ActiveRecord::Observer
  def after_save(game)
    Game.record_win_or_tie(game.id)
  end
end

However, I don't know how to adjust my code. The requirement is to automatically update either game.home_team_won or game.away_team_won after someone has updated game.home_team_score or game.away_team_score.

It seems like I can't use an observer to do this.

Upvotes: 1

Views: 3691

Answers (4)

PinnyM
PinnyM

Reputation: 35531

Use an instance variable to ensure it only gets saved once. However, because this is a class method, it will not be thread safe. Instead make this an instance method like so:

def record_win_or_tie
  return if @inside_callback
  @inside_callback = true

  if home_team_score > away_team_score 
    update_attributes(:home_team_won => true)
  end      
end

Now you can have your observer trigger the instance_method like this:

class GameObserver < ActiveRecord::Observer
  observe :game

  def after_save(game)
    game.record_win_or_tie
  end
end

Note that you can also avoid all this if you perform this logic in a before_save callback (without actually saving inside the callback) instead of after_save:

class Game < ActiveRecord::Base
  def record_win_or_tie
    self.home_team_won = true if home_team_score > away_team_score 
  end
end

class GameObserver < ActiveRecord::Observer
  observe :game

  def before_save(game)
    game.record_win_or_tie
  end
end

Upvotes: 5

Pavling
Pavling

Reputation: 3963

If for some reason it has to be in an after_save, instead of saving the current instance and triggering the after save, or adding spurious instance variables, call update on the db directly.

if game.home_team_score > game.away_team_direct
  Game.update_all({:home_team_won => true}, :id => id)
end
# Check the syntax, as I wrote it off the top of my head

But personally, if possible I'd move it to a before_save as mentioned in another answer.

Upvotes: 0

Valery Kvon
Valery Kvon

Reputation: 4496

class Game < ActiveRecord::Base
  # def self.record_win_or_tie(game_id) # deprecated
end

class GameObserver < ActiveRecord::Observer
  def after_save(game)
    if (game.home_team_score > game.away_team_score) && game.home_team_won != true
      game.home_team_won = true
      game.save
    end
  end
end

Upvotes: 0

boulder
boulder

Reputation: 3266

Could it be that you have defined an after_save callback that calls Game.record_win_or_tie again? That would explain the infinite recursion.

Otherwise we'd need to see the entire Game model

Upvotes: 3

Related Questions