TAGS :Viewed: 6 - Published at: a few seconds ago

[ stack too deep from callback method ]

I have four models in Sport, Gold, Silver, and Bronze with a one to one relationship between sport and the other three. Every instance of sport must have an instance of Gold, Silver and Bronze; and three are to be created using a callback function in the Sports model. These callback function is one throwing up the error that i have been able to trace as the rails error stack produced is just a single line.
My Code

MODELS

Sport

class Sport < ActiveRecord::Base
    validates :sportname, presence: true, 
                         uniqueness: { case_sensitive: false }

   has_one :gold, inverse_of: :sport, :dependent => :destroy
   has_one :silver, inverse_of: :sport, :dependent => :destroy
   has_one :bronze, inverse_of: :sport, :dependent => :destroy

   accepts_nested_attributes_for :gold
   accepts_nested_attributes_for :silver
   accepts_nested_attributes_for :bronze


   after_validation :build_default_medals, on: :create

   def build_default_medals
    self.build_gold
    self.build_silver
    self.build_bronze
  end
end

Gold

class Gold < ActiveRecord::Base

  belongs_to :sport    #, inverse_of: :gold
  validates_associated :sport, presence: true

  belongs_to :team, inverse_of: :golds, counter_cache: true 
  validates_associated :team,  :if => :create, allow_nil: true  

  accepts_nested_attributes_for :team
  accepts_nested_attributes_for :sport
end

Silver

class Silver < ActiveRecord::Base

  belongs_to :sport   #, inverse_of: :silver   
  validates_associated :sport, presence: true

  belongs_to :team, inverse_of: :silvers, counter_cache: true 
  validates_associated :team, :if => :create, allow_nil: true    

  accepts_nested_attributes_for :team
  accepts_nested_attributes_for :sport
end

Bronze

class Bronze < ActiveRecord::Base
  belongs_to :sport   #, inverse_of: :bronze
  validates_associated :sport, presence: true

  belongs_to :team, inverse_of: :bronzes, counter_cache: true 
  validates_associated :team,  :if => :create, allow_nil: true 

  accepts_nested_attributes_for :team
  accepts_nested_attributes_for :sport
 end

The callback after_validation :build_default_medals, on: :create is where the error is, producing this in my log

 Started POST "/admin/sports" for 127.0.0.1 at 2016-07-14 14:52:22 -0400
 Processing by Admin::SportsController#create as HTML
 Parameters:{"utf8"=>"√",authenticity_token"=>"jss1O4bSJd3hcqxuSpu/KAxaowB7d
g5pLZw55oGDf1M=", "sport"=>{"sportname"=>"boxing"}, "commit"=>"Create Sport"}
User Load (1.0ms)  SELECT  `users`.* FROM `users`  WHERE `users`.`id` = 1       LIMIT 1  (0.0ms)  BEGIN
Sport Exists (1.0ms)  SELECT  1 AS one FROM `sports`  WHERE `sports`.`sportname` = 'boxing' LIMIT 1
SQL (1.0ms)  INSERT INTO `sports` (`created_at`, `sportname`, `updated_at`) VALUES ('2016-07-14 18:52:22', 'boxing', '2016-07-14 18:52:22')
SQL (15.1ms)  UPDATE `sports` SET `created_at` = '2016-07-14 18:52:22', `id` = 13, `sportname` = 'boxing', `updated_at` = '2016-07-14 18:52:22' WHERE `sports`.`id` = 13
SQL (1.0ms)  INSERT INTO `golds` (`created_at`, `sport_id`, `updated_at`) VALUES ('2016-07-14 18:52:22', 13, '2016-07-14 18:52:22')
SQL (1.0ms)  UPDATE `sports` SET `created_at` = '2016-07-14 18:52:22', `id` =13, `sportname` = 'boxing', `updated_at` = '2016-07-14 18:52:22' WHERE `sports`.`id` = 13
SQL (1.0ms)  INSERT INTO `silvers` (`created_at`, `sport_id`, `updated_at`) VALUES ('2016-07-14 18:52:22', 13, '2016-07-14 18:52:22')
SQL (1.0ms)  UPDATE `sports` SET `created_at` = '2016-07-14 18:52:22', `id` =13, `sportname` = 'boxing', `updated_at` = '2016-07-14 18:52:22' WHERE `sports`.`id` = 13
SQL (1.0ms)  INSERT INTO `bronzes` (`created_at`, `sport_id`, `updated_at`) VALUES ('2016-07-14 18:52:22', 13, '2016-07-14 18:52:22')  (72.2ms)  
ROLLBACK
Completed 500 Internal Server Error in 213ms

SystemStackError (stack level too deep):  actionpack (4.1.8) lib/action_dispatch/middleware/reloader.rb:79


Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/actionpack-4.1.8/lib/action_dispatch/middleware/templates/rescues/_source.erb (2.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/actionpack-4.1.8/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (4.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/actionpack-4.1.8/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (7.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/actionpack-4.1.8/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (145.1ms)

I am curious as to why the callback triggers update after inserting and what exactly is triggering the ROLLBACK and infinite loop for Stack Level Too Deep. Any insight is appreciated. Thanks.

EDIT

Using after_create :build_default_medals in place of after_validation :build_default_medals, on: :creation produces

  Started POST "/admin/sports" for 127.0.0.1 at 2016-07-14 16:19:24 -0400
  Processing by Admin::SportsController#create as HTML
  Parameters: {"utf8"=>"√","authenticity_token"=>"3LrwB6+nD9PJ9EwxEgIGhN3rVHP3UPLOHUz9MXWRJ4Y=", "sport"=>{"sportname"=>"dancing"}, "commit"=>"Create Sport"}
  User Load (1.0ms)  SELECT  `users`.* FROM `users`  WHERE `users`.`id` = 1 LIMIT 1   (0.0ms)  BEGIN
  Sport Exists (1.0ms)  SELECT  1 AS one FROM `sports`  WHERE `sports`.`sportname` = 'dancing' LIMIT 1
  SQL (1.0ms)  INSERT INTO `sports` (`created_at`, `sportname`,`updated_at`) VALUES ('2016-07-14 20:19:24', 'dancing', '2016-07-14 20:19:24') 
 Gold Load (2.0ms)  SELECT  `golds`.* FROM `golds`  WHERE `golds`.`sport_id` =8 LIMIT 1
 Silver Load (1.0ms)  SELECT  `silvers`.* FROM `silvers`  WHERE `silvers`.`sport_id` = 8 LIMIT 1
 Bronze Load (1.0ms)  SELECT  `bronzes`.* FROM `bronzes`  WHERE `bronzes`.`sport_id` = 8 LIMIT 1
 (45.5ms)  COMMIT

EDIT FOR undefined method 'create' for #<Gold:0x63bf820>

def build_default_medals
  #Gold.create(sport: self, team: nil)
  #Silver.create(sport: self, team: nil)
  #Bronze.create(sport: self, team: nil)
  self.create_gold(team: nil)
  self.create_silver(team: nil)
  self.create_bronze(team: nil)
end

Answer 1


replace

after_validation :build_default_medals, on: :create

with

after_create :build_default_medals

You are trying to build associated models while sport is still a new record and not a persisted one. validates_associated :sport, presence: true causes a circular dependency and will give error.

I reproduced and tested with this change. It will work fine.

For saved object, use self.create_gold instead of self.build_gold, like this

  def build_default_medals
    self.create_gold
    self.create_silver
    self.create_bronze
  end

Also remove accepts_nested_attributes_for :sport from Gold, Silver and Bronze model. Refer - https://github.com/rails/rails/issues/7809