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

Criteria#any_of with multiple arguments incorrectly adds conditions to existing top-level $or if one is already present

    • Type: Icon: Bug Bug
    • Resolution: Fixed
    • Priority: Icon: Major - P3 Major - P3
    • 8.0.1
    • Affects Version/s: None
    • Component/s: None
    • None
    • Major Change

      Currently, when any_of is used with multiple arguments (which causes the arguments to be combined with $or), and the criteria object already has a top-level $or, the new arguments will be erroneously added to the existing $or instead of getting their own $or condition:

      irb(main):004:0> Band.any_of({a:1}, {aa:2}).any_of({b:1}, {bb:2})
      => 
      #<Mongoid::Criteria
        selector: {"$or"=>[{"a"=>1}, {"aa"=>2}, {"b"=>1}, {"bb"=>2}]}
        options:  {}
        class:    Band
        embedded: false>
      
      

      Expected behavior:

      
      
      irb(main):001:0> Band.any_of({a:1}, {aa:2}).any_of({b:1}, {bb:2})
      => 
      #<Mongoid::Criteria
        selector: {"$or"=>[{"a"=>1}, {"aa"=>2}], "$and"=>[{"$or"=>[{"b"=>1}, {"bb"=>2}]}]}
        options:  {}
        class:    Band
        embedded: false>
      
      

      When any_of has only one argument it works correctly:

      
      irb(main):005:0> Band.any_of({a:1}, {aa:2}).any_of({b:1})
      => 
      #<Mongoid::Criteria
        selector: {"$or"=>[{"a"=>1}, {"aa"=>2}], "b"=>1}
        options:  {}
        class:    Band
        embedded: false>
      
      
      

      When there is no existing $or, any_of also works correctly:

      
      irb(main):006:0> Band.any_of({a:1}).any_of({b:1}, {bb:2})
      => 
      #<Mongoid::Criteria
        selector: {"a"=>1, "$or"=>[{"b"=>1}, {"bb"=>2}]}
        options:  {}
        class:    Band
        embedded: false>
      
      

      ---------------------------

      Original report:

      Currently, a Criteria two chained calls to the `#or` method will merge them into one big combined "OR":

       

      Dog.or([color: :red, size: :big]).or([breed: "Chihuahua"])
      #=> Dog '$or' => [color: :red, size: :big, breed: "Chihuahua"]
      

      I guess this is related to ActiveRecord's behavior, and it makes some sense to behave this way if you read it as a sentence in English: "X .or Y". If I'm not mistaken this was introduced in Mongoid 7.0?

       

       

      `#any_of` is an alias for `#or`, so this happens:

      Dog.any_of([color: :red, size: :big]).any_of([breed: "Chihuahua"])
      #=> Dog '$or' => [color: :red, size: :big, breed: "Chihuahua"]
      

      This makes a lot less sense in English; I read it as "any_of X AND any_of Y", and I was surprised when the criteria were combined. I propose that we should intentionally make `#any_of` chain as an AND:

      Dog.any_of([color: :red, size: :big]).any_of([breed: "Chihuahua"])
      #=> Dog. '$and' => [ '$or' => [color: :red, size: :big], '$or' => [breed: "Chihuahua"] ]
      

      We can keep the `#or` behavior the same, so users can choose the appropriate method for their preferences.

            Assignee:
            oleg.pudeyev@mongodb.com Oleg Pudeyev (Inactive)
            Reporter:
            shields@tablecheck.com Johnny Shields
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: