mongodbclojuremonger

How to update / insert a MongoDB sub document with Monger?


I'm using Clojure's Monger library to connect to a MongeoDB database.

I want to update, insert & remove subdocuments in my Mongo database. MongoDB's $push modifier lets me do this on the root of the searched document. But I want to be able to $push onto a sub-collection. Looking at Monger's tests, it looks possible. But I want to be sure I can push to the child-collection of the 3rd parent. Can Monger do something like this?


    (mgcol/update mycollection { :my-criteria-key "my-criteria-value" } { $push { "parent.3.child-collection" "fubar" }} ) 

Even better would be the ability to have a $where clause in my $push. Is something like this possible?


    (mgcol/update mycollection 
          { :doc-criteria-key "doc-criteria-value" } 
          { $push 
              { { $where { parent.child.lastname: 'Smith' } } 
              "fubar" } } 
    )

But even on a basic level, when I try the following command in my repl, I get the below error.

  1. The "fubar" database definitely exists

  2. I'm definitely connected to the DB

  3. The { :owner "fubar@gmail.com" } criteria is definitely valid; and

  4. I tried both "content.1.content" and "content.$.content":


    repl => (mc/update "fubar" { :owner "fubar@gmail.com" } { $push { "content.1.content" { "fu" "bar" } } } ) 
    ClassCastException clojure.lang.Var$Unbound cannot be cast to com.mongodb.DB  monger.collection/update (collection.clj:310)
    repl => 
    repl => 
    repl => (clojure.repl/pst *e)
    ClassCastException clojure.lang.Var$Unbound cannot be cast to com.mongodb.DB
            monger.collection/update (collection.clj:310)
            bkell.run.run-ring/eval2254 (NO_SOURCE_FILE:46)
            clojure.lang.Compiler.eval (Compiler.java:6406)
            clojure.lang.Compiler.eval (Compiler.java:6372)
            clojure.core/eval (core.clj:2745)
            clojure.main/repl/read-eval-print--6016 (main.clj:244)
            clojure.main/repl/fn--6021 (main.clj:265)
            clojure.main/repl (main.clj:265)
            user/eval27/acc--3869--auto----30/fn--32 (NO_SOURCE_FILE:1)
            java.lang.Thread.run (Thread.java:619)

Had anyone come across this and solved it?

Thanks


Solution

  • You have a three part question, with some inconsistencies and holes in the description. So here is my best guess, hope that it is close.

    I can get all three to work given schema matched to your update requests, see test/core.clj below for complete details.

    First part: Yes, you can push to the child-collection of the 3rd parent, exactly as you have written.

    Second part: You want to move your "$where" clause into the criteria, and use $ in the objNew.

    Third part: Yes, your basic update works for me below, exactly as you have written.

    The output of "lein test" follows at the bottom. All the best to you in your endeavors.

    test/core.clj

    (ns free-11749-clojure-subdoc.test.core
      (:use [free-11749-clojure-subdoc.core])
      (:use [clojure.test])
      (:require [monger.core :as mg] [monger.collection :as mgcol] [monger.query])
      (:use [monger.operators])
      (:import [org.bson.types ObjectId] [com.mongodb DB WriteConcern]))
    
    (deftest monger-sub-document
    
        (mg/connect!)
        (mg/set-db! (mg/get-db "test"))
    
        (def mycollection "free11749")
    
        ;; first part
        (mgcol/remove mycollection)
        (is (= 0 (mgcol/count mycollection)))
    
        (def doc1 {
            :my-criteria-key "my-criteria-value"
            :parent [
                    { :child-collection [ "cc0" ] }
                    { :child-collection [ "cc1" ] }
                    { :child-collection [ "cc2" ] }
                    { :child-collection [ "cc3" ] }
                    { :child-collection [ "cc4" ] }
                ]
            }
        )
    
        (mgcol/insert mycollection doc1)
        (is (= 1 (mgcol/count mycollection)))
    
        (mgcol/update mycollection { :my-criteria-key "my-criteria-value" } { $push { "parent.3.child-collection" "fubar" }} )
    
        (def mymap1 (first (mgcol/find-maps mycollection { :my-criteria-key "my-criteria-value" })))
        (is (= "fubar" (peek (:child-collection (get (:parent mymap1) 3)))))
    
        (prn (mgcol/find-maps mycollection { :my-criteria-key "my-criteria-value" }))
    
    
        ;; second part
        (mgcol/remove mycollection)
        (is (= 0 (mgcol/count mycollection)))
    
        (def doc2 {
            :doc-criteria-key "doc-criteria-value"
            :parent [
                        { :child  { :lastname [ "Alias" ] } }
                        { :child  { :lastname [ "Smith" ] } }
                        { :child  { :lastname [ "Jones" ] } }
                    ]
            }
        )
    
        (mgcol/insert mycollection doc2)
        (is (= 1 (mgcol/count mycollection)))
    
        (mgcol/update mycollection { :doc-criteria-key "doc-criteria-value" "parent.child.lastname" "Smith"} { $push { :parent.$.child.lastname "fubar" } } )
    
        (def mymap2 (first (mgcol/find-maps mycollection { :doc-criteria-key "doc-criteria-value" })))
        (is (= "fubar" (peek (:lastname (:child (get (:parent mymap2) 1))))))
    
        (prn (mgcol/find-maps mycollection { :doc-criteria-key "doc-criteria-value" }))
    
        ;; third part
        (mgcol/remove "fubar")
        (is (= 0 (mgcol/count "fubar")))
    
        (def doc3 {
                :owner "fubar@gmail.com"
                :content [
                        { :content [ "cc0" ] }
                        { :content [ "cc1" ] }
                        { :content [ "cc2" ] }
                ]
            }
        )
    
        (mgcol/insert "fubar" doc3)
        (is (= 1 (mgcol/count "fubar")))
    
        (mgcol/update "fubar" { :owner "fubar@gmail.com" } { $push { "content.1.content" { "fu" "bar" } } } )
    
        (def mymap3 (first (mgcol/find-maps "fubar" { :owner "fubar@gmail.com" })))
        (is (= { :fu "bar" } (peek (:content (get (:content mymap3) 1)))))
    
        (prn (mgcol/find-maps "fubar" { :owner "fubar@gmail.com" }))
    )
    

    lein test

    Testing free-11749-clojure-subdoc.test.core
    ({:_id #<ObjectId 4fb3e98447281968f7d42cac>, :my-criteria-key "my-criteria-value", :parent [{:child-collection ["cc0"]} {:child-collection ["cc1"]} {:child-collection ["cc2"]} {:child-collection ["cc3" "fubar"]} {:child-collection ["cc4"]}]})
    ({:_id #<ObjectId 4fb3e98447281968f7d42cad>, :doc-criteria-key "doc-criteria-value", :parent [{:child {:lastname ["Alias"]}} {:child {:lastname ["Smith" "fubar"]}} {:child {:lastname ["Jones"]}}]})
    ({:_id #<ObjectId 4fb3e98447281968f7d42cae>, :content [{:content ["cc0"]} {:content ["cc1" {:fu "bar"}]} {:content ["cc2"]}], :owner "fubar@gmail.com"})
    
    Ran 1 tests containing 9 assertions.
    0 failures, 0 errors.