Uploaded image for project: 'Mongoid'
  1. Mongoid
  2. MONGOID-5093

Call #save once per model instance when associations autosave each other recursively

    • Type: Icon: Improvement Improvement
    • Resolution: Unresolved
    • Priority: Icon: Minor - P4 Minor - P4
    • None
    • Affects Version/s: 7.0.12, 7.2.2
    • Component/s: Associations, Persistence
    • None

      When we have two models and they have both autosave: true in their relations, calling save on a record calls save too many times on other relations. Here is an example :

      #!/usr/bin/env ruby
      
      require 'bundler/inline'
      
      gemfile do
        source 'https://rubygems.org'
        gem 'mongoid', '7.0.12'
        gem 'pry-byebug'
      end
      
      require 'mongoid'
      
      Mongoid.configure { |c| c.clients.default = { hosts: ['localhost:27017'], database: 'test' } }
      
      puts Mongoid::VERSION
      puts Mongo::VERSION
      
      Mongoid.logger.level = 4
      Mongo::Logger.logger.level = 4
      
      class Article
        include Mongoid::Document
        has_many :comments, autosave: true
        before_save :log_save
      
        def log_save
          pp "Article#save"
        end
      end
      
      class Comment
        include Mongoid::Document
        belongs_to :article, autosave: true
        field :body, type: String
        before_save :log_save
      
        def log_save
          pp "Comment#save"
        end
      end
      
      article = Article.new
      article.comments << Comment.new
      
      article.save
      
      puts "################################################################################"
      
      article.comments << Comment.new
      article.comments << Comment.new
      
      article.save
      
      puts "################################################################################"
      article.comments.last.save
      

      And here is the output :

      "Article#save"
      "Comment#save"
      "Article#save"
      ################################################################################
      "Comment#save"
      "Article#save"
      "Comment#save"
      "Article#save"
      "Comment#save"
      "Comment#save"
      "Article#save"
      "Comment#save"
      "Article#save"
      "Comment#save"
      "Article#save"
      "Comment#save"
      "Article#save"
      "Comment#save"
      "Article#save"
      "Comment#save"
      "Article#save"
      "Comment#save"
      "Article#save"
      ################################################################################
      "Comment#save"
      "Article#save"
      "Comment#save"
      "Article#save"
      "Comment#save"
      "Article#save"
      "Comment#save"
      

      As you can see, callbacks, validations and saves are trigger a lot too much.

      It happens because since this commit records are saved even if there is no change.

      In addition, to me, usage of autosave is reasonable here. If we remove autosave we have :

      class Article
        include Mongoid::Document
        has_many :comments
        before_save :log_save
      
        def log_save
          pp "Article#save"
        end
      end
      
      class Comment
        include Mongoid::Document
        belongs_to :article
        field :body, type: String
        before_save :log_save
      
        def log_save
          pp "Comment#save"
        end
      end
      
      article = Article.new
      article.comments << Comment.new
      
      article.save
      pp article.reload.comments # Gives an empty array
      
      comment = Comment.create(article: Article.new)
      pp comment.reload.article # Gives nil
      

      Here we can see the behavior of autosave: true is useful and doesn't seem too crazy.

            Assignee:
            Unassigned Unassigned
            Reporter:
            guirec.corbel@gmail.com Guirec Corbel
            Votes:
            1 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated: