Add data in Firestore with Clojure - clojure

I am new to the Firestore and Clojure.
Below is my code, Get returns the correct data, however, Set is not successful, without any exception or response.
(defn database-instance
[]
(FirestoreClient/getFirestore))
(def user (java.util.HashMap. {"age" 50
"name" "Josh"
"user_id" "user02"}))
(defn get-credit-detail!
[msg]
(def result
(-> (database-instance)
(.collection "credits")
(.document "document")
(.get)
(.get)
))
(println (.getData result))
;; this Set does not work
(-> (database-instance)
(.collection "credits")
(.document "document1")
(.set user)
)
)
Can you help me with the Set to be able to add new data to firestore?
Thanks!

A little late to answer this but what you have should work. I tried it through the REPL and verified the set worked from the firebase console. Note that you can use a Clojure map with String keys as input without having to resort to creating a java.util.Map.
(-> (database-instance)
(.collection "credits")
(.document "document1")
(.set {"age" 50 "name" "Josh" "user_id" "user02"}))

Related

Can't extract key from Map

I'm hitting an API that returns some json that I am trying to parse. I look a the keys of the returned parsed values and it says that it has a key "id", but when I try to get the id from the map, I get nothing back.
E.g.:
(require '[clj-http.client :as client]
'[cheshire.core :refer :all])
(def app-url "http://myapp.com/api/endpoint")
(defn get-application-ids [] (parse-string (:body (client/get app-url))))
(defn -main [] (println (map :id (get-application-ids))))
This returns (nil nil nil). Which, AFAIKT, it shouldn't - instead it should return the value of the :id field, which is not null.
Helpful facts:
When I run (map keys (get-application-ids)) to find the keys of the returned structure, I get ((id name attempts) (id name attempts) (id name attempts)).
(type (get-application-ids)) returns clojure.lang.LazySeq
(map type (get-application-ids)) returns (clojure.lang.PersistentArrayMap clojure.lang.PersistentArrayMap clojure.lang.PersistentArrayMap)
(println (get-application-ids)) returns (one example of the three returned):
({id application_1595586907236_1211, name myname, attempts [{appSparkVersion 2.4.0-cdh6.1.0, lastUpdated 2020-07-26T20:18:47.088GMT, completed true, lastUpdatedEpoch 1595794727088, sparkUser super, endTimeEpoch 1595794726804, startTime 2020-07-26T20:04:05.998GMT, attemptId 1, duration 880806, endTime 2020-07-26T20:18:46.804GMT, startTimeEpoch 1595793845998}]})
Everything about this tells me that (map :id (get-application-ids)) should return the value of the id field, but it doesn't. What am I missing?
It seems you are using cheshire.core/parse-string. This will return string keys, not keywords. See this example.
So, it appears that your key is the string "id", not the keyword :id. To verify this theory, try putting in the debugging statement:
(prn (:body (client/get app-url)))
To ask Cheshire to convert map keys from strings to keywords, use the form
(parse-string <json-src> true) ; `true` => output keyword keys
See also this list of documentation sources. Especially study the Clojure CheatSheet daily.

Checkbox in Luminus

I started to learn Clojure this week, specifically I'm learning web development with Luminus. Since I want to understand the CRUD process, I setup a function to save my post into the DB:
(defn save-post! [{:keys [params]}]
(if-let [errors (validate-post params)]
(-> (response/found "/posts")
(assoc :flash (assoc params :errors errors)))
(do
(db/save-post!
(assoc params :created_at (java.util.Date.)))
(response/found "/posts"))))
The query is pretty basic:
-- :name save-post! :! :n
-- :doc creates a new post record
INSERT INTO posts
(title, body, active, created_at)
VALUES (:title, :body, :active, :created_at)
but the HTML form has a checkbox field:
<input type="checkbox" name="active" value="1">Published<br />
and when it is not selected, the field is not send and the SQL insert query sends the error message "No active field". How can I check if the element "active" is set and add it to "params" as true or false?
Something like:
(assoc params :active (if (nil? params/active) false true))
after the ":created_at (java.util.Date.)" line.
How can I check if the element "active" is set and add it to "params" as true or false?
Looks like your code isn't far from working. You'll need to check the params map to see if it has the checkbox's value. If (:active params) is equal to "1" when the checkbox is checked, then you might do something like this:
(assoc params :active (= "1" (:active params)))
But what this is really trying to do is update a particular value in the map, which can be done more idiomatically:
(update params :active #(= "1" %))
Where the final argument is a function that takes any current value of the keyword and returns the new value.
Another potential gotcha: you may not want to use the params map as direct input to your DB query, because it could very easily contain keys/values that you don't want or expect. It'd be safer to pull only the values you need from it explicitly e.g. (select-keys params [:title :body :active]).
(def params {:active "1", :admin true}) ;; wouldn't want admin to leak through!
(-> params
(select-keys [:title :body :active])
(assoc :created_at (java.util.Date.))
(update :active #(= "1" %)))
;;=> {:active true, :created_at #inst "2017-10-09T20:16:06.167-00:00"}

How to iterate over a result set and extract one particular value in clojure?

Below is my attempt to iterate over a result set and get its values
(sql/with-connection db
(sql/with-query-results rs ["select * from user where UserID=?" 10000]
(doseq [rec rs
s rec]
(println (val s))
)))
But how do you extract one particular value from it; i need only the user name field.
Can anyone please demonstarte how to do this?
The result set is a sequence of maps, so if you wanted to obtain one field (e.g. one called name) then:
(sql/with-connection db
(sql/with-query-results rs ["select * from user where UserID=?" 10000]
(doseq [rec rs]
(let [name (:name rec)]
(println "User name:" name)
(println "Full record (including name):" rec)))))
But as mentioned in the comments, if you only want name, then select name from would be the more efficient option. The code above is useful when you need the full row for something else.
The with-connection / with-query-results syntax is deprecated as of clojure.java.jdbc 3.0. Filtering results can be done much easier with the new query syntax and additional :row-fn and :result-set-fn parameters.
(query db ["select * from user"]
:row-fn :name
:result-set-fn #(doall (take 1000 (drop 10000 %))))
Be sure to make the result-set-fn realize all values, it shouldn't return a lazy sequence (hence the doall in this example).

Clojure - return value from db query

I want to created post as return value just after execute my db query function. Here is one example from my db functions:
(defn add-post-record [post]
(sql/with-connection
db
(sql/insert-record :post post )))
and what i need in my route is something like:
(def post (db/add-post-record {:title title
:body body
:owner user
:isdraft isdraft}))
Then i am gonna use this like: (:id post)
I am so new in clojure. This may be a very simple problem but i am stuck.
thank you.
I can not test this right now, but reading the documentation of insert-record and with-connection, I think something like:
(defn add-post-record [post]
(let [keys (sql/with-connection db
(sql/insert-record :post post ))]
(merge post keys))
It is not very clear to me what exactly the map returned by insert-record contains, try it out.

Clojure: Dynamically create functions from a map -- Time for a Macro?

I have a function that begins like this:
(defn data-one [suser]
(def suser-first-name
(select db/firstNames
(fields :firstname)
(where {:username suser})))
(def suser-middle-name
(select db/middleNames
(fields :middlename)
(where {:username suser})))
(def suser-last-name
(select db/middleNames
(fields :lastname)
(where {:username suser})))
;; And it just continues on and on...
)
Of course, I don't like this at all. I have this pattern repeating in many areas in my code-base and I'd like to generalize this.
So, I came up with the following to start:
(def data-input {:one '[suser-first-name db/firstNames :firstname]
'[suser-middle-name db/middleNames :middlename]
'[suser-last-name db/lastNames :lastname]})
(defpartial data-build [data-item suser]
;; data-item takes the arg :one in this case
`(def (data-input data-item)
(select (data-input data-item)
(fields (data-input data-item))
(where {:username suser}))))
There's really a few questions here:
-- How can I deconstruct the data-input so that it creates x functions when x is unknown, ie. that the values of :one is unknown, and that the quantities of keys in data-input is unknown.
-- I'm thinking that this is a time to create a macro, but I've never built one before, so I am hesitant on the idea.
And to give a little context, the functions must return values to be deconstructed, but I think once I get this piece solved, generalizing all of this will be doable:
(defpage "/page-one" []
(let [suser (sesh/get :username)]
(data-one suser)
[:p "Firat Name: "
[:i (let [[{fname :firstname}] suser-first-name]
(format "%s" fname))]
[:p "Middle Name: "
[:i (let [[{mname :emptype}] suser-middle-name]
(format "%s" mname))]
[:p "Last Name: "
[:i (let [[{lname :months}] suser-last-name]
(format "%s" lname))]]))
Some suggestions:
def inside a function is really nasty - you are altering the global environment, and it can cause all kinds of issues with concurrency. I would suggest storing the results in a map instead.
You don't need a macro here - all of the data fetches can be done relatively easily within a function
I would therefore suggest something like:
(def data-input [[:suser-first-name db/firstNames :firstname]
[:suser-middle-name db/middleNames :middlename]
[:suser-last-name db/lastNames :lastname]])
(def data-build [data-input suser]
(loop [output {}
items (seq data-input)]
(if items
(recur
(let [[kw db fieldname] (first items)]
(assoc output kw (select db (fields fieldname) (where {:username suser}))))
(next items))
output)))
Not tested as I don't have your database setup - but hopefully that gives you an idea of how to do this without either macros or mutable globals!
Nice question. First of all here's the macro that you asked for:
(defmacro defquery [fname table fields ]
(let [arg-name (symbol 'user-name)
fname (symbol fname)]
`(defn ~fname [~arg-name]
(print ~arg-name (str ~# fields)))))
You can call it like that:
(defquery suser-first-name db/firstNames [:firstname])
or if you prefer to keep all your configurations in a map, then it will accept string as the first argument instead of a symbol:
(defquery "suser-first-name" db/firstNames [:firstname])
Now, if you don't mind me recommending another solution, I would probably chose to use a single function closed around configuration. Something like that:
(defn make-reader [query-configurations]
(fn [query-type user-name]
(let [{table :table field-names :fields}
(get query-configurations query-type)]
(select table
(apply fields field-names)
(where {:username suser})))))
(def data-input {:firstname {:table db/firstNames :fields :firstname}
:middlename {:table db/middleNames :fields :middlename}
:lastname {:table db/lastNames :fields :lastname}})
(def query-function (make-reader data-input))
;; Example of executing a query
(query-function :firstname "tom")
By the way there's another way to use Korma:
;; This creates a template select from the table
(def table-select (select* db/firstNames))
;; This creates new select query for a specific field
(def first-name-select (fields table-select :firstname))
;; Creating yet another query that filters results by :username
(defn mkselect-for-user [suser query]
(where query {:username suser}))
;; Running the query for username "tom"
;; I fully specified exec function name only to show where it comes from.
(korma.core/exec (mkselect-for-user "tom" first-name-select))
For more information I highly recommend looking at Korma sources.