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

Prevention of double calls in callbacks

    • Type: Icon: Bug Bug
    • Resolution: Fixed
    • Priority: Icon: Critical - P2 Critical - P2
    • 8.1.0, 9.0.0
    • Affects Version/s: None
    • Component/s: None
    • None

      *Originally from https://github.com/mongodb/mongoid/discussions/5518*

      I was looking at the PR that originally added the run_callbacks method and I noticed it mentions:
      #1058

      • This does not include multi-level nested documents.
      • This does not include prevention of double calls on new child save.
        However mongoid does now include multi-level nested documents so that later got resolved

      The reason I was reading this code is because I noticed that if you have a grandparent relation and you use around_save on the grandchild and cascade callbacks, it will call the around_save twice.

      To add the modern-day code, I believe this is an effect of:

      run_callbacks using cascadable_callbacks which is recursive leading to the grandparent calling hooks on grandchildren, then the parent ALSO calling hooks on grandchild.

      We discovered that this is partially fixed in Mongoid 8 with the introduction of :persist_parent hook that can be used for uniqueness, although it's currently marked private. Can that be backported to Mongoid 7.x and made public?

      Reproduction

      require 'mongoid'
      
      Mongoid.configure do |config|
        config.clients.default = {
          hosts: ['localhost:27017'],
          database: 'my_db',
        }
      
        config.log_level = :warn
      end
      
      $logger = []
      
      class Root
        include Mongoid::Document
        embeds_many :embedded_once, cascade_callbacks: true
        after_save :trace
      
        def trace
          $logger << "A"
        end
      end
      
      class EmbeddedOnce
        include Mongoid::Document
        embeds_many :embedded_twice, cascade_callbacks: true
        embedded_in :root
        after_save :trace
      
        def trace
          $logger << "B"
        end
      end
      
      class EmbeddedTwice
        include Mongoid::Document
        embedded_in :embedded_once
        after_save :trace
      
        def trace
          $logger << "C"
        end
      end
      
      require 'minitest/autorun'
      
      class EmbedHookTest < MiniTest::Unit::TestCase
        def setup
          @root= Root.new(
            embedded_once: [
              EmbeddedOnce.new(
                embedded_twice: [EmbeddedTwice.new]
              )
            ]
          )
        end
      
        def test_hooks
          @root.save
          assert_equal(["A", "B", "C"], $logger)
          # Actual is C, C, B, A
        end
      end
      

            Assignee:
            dmitry.rybakov@mongodb.com Dmitry Rybakov
            Reporter:
            alex.bevilacqua@mongodb.com Alex Bevilacqua
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: