简体   繁体   中英

Ruby on Rails — Stack level too deep

model:

after_save :set_correct_post_type

def set_correct_post_type
  if self.document.present?
    if (find_mime_type(self.document.original_filename) == "application/vnd.openxmlformats-officedocument.presentationml.presentation") || (find_mime_type(self.document.original_filename) == "application/vnd.ms-powerpoint")
      self.update_attributes(:post_type => 3)
    elsif (find_mime_type(self.document.original_filename) == "application/vnd.openxmlformats-officedocument.wordprocessingml.document" ) || (find_mime_type(self.document.original_filename) == "application/msword") || (find_mime_type(self.document.original_filename) == "application/pdf")
      self.update_attributes(:post_type => 2)
    elsif (MIME::Types.type_for(self.document.original_filename).first.content_type == "image/png") || (MIME::Types.type_for(self.document.original_filename).first.content_type =="image/jpeg") || (MIME::Types.type_for(self.document.original_filename).first.content_type == "image/jpg")
      self.update_attributes(:post_type => 5)
    end
  end #line17
end

Logs:

  (0.3ms)  rollback transaction
Completed 500 Internal Server Error in 1939ms (ActiveRecord: 37.5ms)

SystemStackError (stack level too deep):
  app/models/teacher_post.rb:17:in `set_correct_post_type'
  app/models/teacher_post.rb:17:in `set_correct_post_type'
  app/models/teacher_post.rb:17:in `set_correct_post_type'
  app/models/teacher_post.rb:17:in `set_correct_post_type'
  app/models/teacher_post.rb:17:in `set_correct_post_type'
  app/models/teacher_post.rb:17:in `set_correct_post_type'
  app/models/teacher_post.rb:17:in `set_correct_post_type'

And then it repeats the last line again and again until I stop it manually. Can anyone tell me what I'm doing wrong?

The reason for "stack level too deep error" is because you are calling update_attributes inside after_save callback, which will invoke the callback after_save again, calling update_attributes again, and so on...

Change it to following:

before_save :set_correct_post_type

def set_correct_post_type
  if self.document.present?
    if (find_mime_type(self.document.original_filename) == "application/vnd.openxmlformats-officedocument.presentationml.presentation") || (find_mime_type(self.document.original_filename) == "application/vnd.ms-powerpoint")
      self.post_type = 3
    elsif (find_mime_type(self.document.original_filename) == "application/vnd.openxmlformats-officedocument.wordprocessingml.document" ) || (find_mime_type(self.document.original_filename) == "application/msword") || (find_mime_type(self.document.original_filename) == "application/pdf")
      self.post_type = 2
    elsif (MIME::Types.type_for(self.document.original_filename).first.content_type == "image/png") || (MIME::Types.type_for(self.document.original_filename).first.content_type =="image/jpeg") || (MIME::Types.type_for(self.document.original_filename).first.content_type == "image/jpg")
      self.post_type = 5
    end
  end
end

When you call update_attributes, the callbacks are called, as your method set_correct_post_type is set as "after_save", you end with an loop.

You call save on you method that triggers the set_correct_post_type method that call update_attributes that triggers the set_correct_post_type method that call update_attributes...

If you don't want to trigger the callbacks in your method, look at update_columns instead of update_attributes. Of consider setting attribute with a before_save instead of a after save.

If you have your custom code, without any gems etc, avoid using rails callbacks. I'd recommend use Serice Object ex. CreateTeacherPost which will create a post, do magic with params, types etc all in one Transaction. This way you will avoid problems like below and you will always know whats going on without callbacks magic.

But if you really want to use this pattern it's going into an infinite loop because each update_attributes is calling after_save! callback method. You could use update_column method or before_save callback and set attribute directly using self.post_type=number . But first will call SQL update the second time, there is no reason to do this.

One more :) if you have to/want to use after callback better use after_commit callback. It's much safer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM