Position key of path for grouped data vector map structure - clojurescript

We have some example grouped data:
(def grouped-data [{:id 1
:title "Classic music"
:asks [{
:id 1
:title "In which year did it sound of song of Chopin - Nocturnes, Op. 9: No. 2 in E-flat major?"
:answers [{:id 1 :name "1900"}
{:id 2 :name "1800"}]}
{:id 2
:title "In what century did the song of Mozart - Rondò in D major, K. 485?"
:answers [{:id 3 :name "XI"}
{:id 4 :name "XII"}]}]}
{:id 2
:title "Modern music"
:asks [{
:id 3
:title "In what year was Shakira's waka waka song known?"
:answers [{:id 5 :name "2010"}
{:id 6 :name "2009"}]}
{:id 4
:title "In what year was Backstreet Boy's 'I want it that way song' known?"
:answers [{:id 7 :name "1988"}
{:id 8 :name "1999"}]}]}])
We go through the first level with the groups to show the title:
(defn grouped-asks [form]
[:<>
(doall
(for [[pos {:keys [id
title
asks]}]
(map-indexed vector #grouped-data)]
[:div.pt-10 {:key id}
[:h2 title]
[asks-items form asks]])) ;; 0, 1 groups
])
We show the questions of each group, but I need to calculate a result to be sent based on the chosen answer [:answers pos :result], for this a form with an indexed path is needed, but the index or position of each item is restarted since it changes group therefore it is repeat:
(defn asks-items [form items]
[:<>
(doall
(for [[pos {:keys [id
title
answers
] :as ask}]
(map-indexed vector (:asks items))]
(let [_ (swap! form assoc-in [:answers pos :ask] id)]
[:div.row {:key id}
[:h3 title]
[answers-options-items answers]
[form [:answers pos :result] ;; [:answers pos :result] I need the position for path
{:label "Result" ;; First time pos are 0 and 1 when change the next group pos was 0 and 1
:type :number}] ;; When the path should be: 0, 1, 2, 3
;;
;; I tried to create a atom counter and inc in each iteration, this not
;; work for react. And used r/with-let, but not inc the second iteration
])))])
I tried to create an atom and increase it but with react it doesn't work, enters an infinite loop.
(def position (r/atom -1)
....
(let [_ (swap! position inc)]
...
[form [:answers #position :result])
I also tried not to use atom, using a mutable variable using set!
Could there be any solution?

Related

Passing data between reagent components

I have the following reagent components
(defn comments-component [arg-id]
[:p arg-id])
(defn arguments-component [list-of-args]
[:ul.arguments
(for [{:keys [comment upvotes id]} #list-of-args]
^{:key id}
[:li
[:p comment]
[:p upvotes]
[:p id
[modal-button :argument
;title
"Comments"
;body
[:div
[:p "Comments"]
[comments-component id]]
;footer
[:p "OpinionNeeded"] ]]])])
(defn debate []
(let [debate-topic #(rf/subscribe [:debate/topic])
debate-affirmatives (rf/subscribe [:debate/affirmatives])
debate-negatives (rf/subscribe [:debate/negatives])]
(fn []
[:div
[:a {:href "/"} "Return home"]
[:p "This page is being worked on"]
[:div
[:h2 (get debate-topic :title)]
[:p (get debate-topic :description)]]
[:div.columns
[:div.column.is-half
[:p "Agree"]
[arguments-component debate-affirmatives]]
[:div.column.is-half
[:p "Disagree"]
[arguments-component debate-negatives]]]])))
The problem I'm encountering is that the modal button is supposed to create a modal popup with the id for each specific argument, (which I can then use to fetch the comments for that specific argument.)
But instead, I'm getting this bug whereby all the modals for different arguments show the same id.
I cannot figure out why this is happening, but it seems that all the modal-button functions are getting called with the id of the last or first argument to be rendered.
This is what the relevant portion of app-db looks like on this page
:affirmatives [{:id 1, :comment "", :upvotes 0, :topic_id 2, :affirm true} {...}]
:negatives [{:id 2, :comment "", :upvotes 0, :topic_id 2, :affirm true} {...}]
:comments [{:id 1, :comment "", :upvotes 0, :argument_id 1, :topic_id 2} {...}]
The :argument_id in each comment is a reference to the :id in the affirmatives/negatives
And here's the code that generates the modals.
(rf/reg-event-db
:app/show-modal
(fn [db [_ modal-id]]
(assoc-in db [:app/active-modals modal-id] true)))
(rf/reg-event-db
:app/hide-modal
(fn [db [_ modal-id]]
(update db :app/active-modals dissoc modal-id)))
(rf/reg-sub
:app/active-modals
(fn [db _]
(:app/active-modals db {})))
(rf/reg-sub
:app/modal-showing?
:<- [:app/active-modals]
(fn [modals [_ modal-id]]
(get modals modal-id false)))
(defn modal-card [id title body footer]
[:div.modal
{:class (when #(rf/subscribe [:app/modal-showing? id]) "is-active")}
[:div.modal-background
{:on-click #(rf/dispatch [:app/hide-modal id])}]
[:div.modal-card
[:header.modal-card-head
[:p.modal-card-title title]
[:button.delete
{:on-click #(rf/dispatch [:app/hide-modal id])}]]
[:section.modal-card-body
body]
[:footer.modal-card-foot
footer]]])
(defn modal-button [id title body footer]
[:div
[:button.button.is-primary
{:on-click #(rf/dispatch [:app/show-modal id])}
title]
[modal-card id title body footer]])
All your modals are passed the same id :argument. Thus, you always show the same modal, since the db lookup in your subscription is for :argument.
A minor tip: Following kebap-case is the recommended style in Clojure. So your app-db attributes should probably be argument-id and topic-id.
Furthermore, I would recommend subscribing debate-affirmatives and debate-negatives inside the components you pass them to. This would make them more self-containing.

Clojure : treverse through json and return true or false, depending on the contion check result for each key:value pair in given document

I have tried to return true or false depending on the value present in the given hash-map. I have tried using reduced-kv but it really doesn't work.
for eg :
{:address {:zip 411045, :city "pune"}, :coupans ["abc" "def"], :cost 200, :items [{:category "partywear", :name "shirt", :price 50.26} {:category "partywear", :name "trouser", :price 10.26}]}
I want to write a function such that if "items.price" = 50.26
and "items.name" = "shirt",, should return true but "items.price = 10.26 and "items.name" = "shirt" should return false .
I am first flattening the array and then changing the key to regex key
(def compare_str_regex (clojure.string/replace compare_str #"\[\]" "\\\\[\\[0-9\\]+\\\\]"))
for eg : items[].price -- > items[[0-9]+].price
Then I use reduce-kv to iterate, but the problem is it will check all the doc
it should use the and condition between the two key sent
(reduce-kv (fn [m k v]
(if (and (re-find (re-pattern compare_str_regex ) k)
(op value v))
(reduced true)
false)) {} flat_pl_map)
If the rule is "return true if there are any entries in the items
collection with :name "shirt" and :price 50.26", then I would write that function like this:
If that's correct, then I would write that function like this:
(fn [{:keys [items]}]
(some (fn [{:keys [name price]}]
(and (= "shirt" name) (= 50.26 price)))
items))

Resolving foreign keys in Om Next

I'm trying to understand the normalization, identity and querying concepts in Om Next. Every time I think I have it, I run into a new situation that seems to baffle me.
I have a remote that returns the following data:
{:users [
{:id 1,
:email "foo#example.com",
:role 1},
{:id 3,
:email "bar#floyhamilton.nl",
:role 1},
{:id 4,
:email "baz#example.com",
:role 1},
{:id 6,
:email "nom#example.com",
:role 1}}]
:roles [
{:id 1, :name "admin"}]}
I've structured my components like this:
(defui Root
static om/IQuery
(query [this]
`[{:users ~(om/get-query User)}
{:roles ~(om/get-query Role)}]))
(defui Role
static om/Ident
(ident [this {:keys [id]}]
[:role/by-id id])
static om/IQuery
(query [this]
[:id :name]))
(defui User
static om/Ident
(ident [this {:keys [id]}]
[:user/by-id id])
static om/IQuery
(query [this]
[:id :email :role]))
This results in the reconciler normalizing my data like this:
{:users
[[:user/by-id 1] [:user/by-id 3] [:user/by-id 4] [:user/by-id 6]],
:roles [[:role/by-id 1]],
:user/by-id
{1
{:id 1,
:email "foo#example.com",
:role 1},
3
{:id 3,
:email "bar#floyhamilton.nl",
:role 1},
4
{:id 4,
:email "baz#example.com",
:role 1},
6
{:id 6,
:email "nom#example.com",
:role 1}},
:role/by-id {1 {:id 1, :name "admin"}}}
Now, as to my question. I'd like to access a user's role's name inside the component. I could simply do something like (get-in #app-state [:role/by-id role :name]) within the render function of the User component, but that doesn't seem like the idiomatic thing to do.
The Om Next document on "Thinking with links" seems to mention my solution: idents. I've tried to use this within the User query (like [:id :email [:role/by-id 1]]) and that works! However, I can't seem to insert the actual role id into the ident! I've tried very complicated things with query parameters, inserting it into the indent like that, but it all seems horribly contrived.
This seems like an incredibly common situation for which a decent solution should exist, but I can't seem to get it!
*EDIT Added the Root component
Your query syntax for User ought to be something like this:
[:id :email {:user-role (om/get-query Role)}]
Then the table state for a user might be:
{:id 6,
:email "nom#example.com",
:user-role [:role/by-id 1]}
It would help if you showed your defuis in the question, especially for Root. One thing with Om.Next is that all the joins have to be done on the Root component in order to get the app state properly normalized.

Om Next read multi-fn not being called in second level join, Query AST not parsed fully, therefore component only receiving idents

I'm having trouble getting a second level join to work correctly. I've elided some things here for brevities sake.
My root component is:
(defui RootView
static om/IQuery
(query [this]
`[{:list/events ~(om/get-query Event)}])
Object
(render [this]
(let [{:keys [list/events]} (om/props this)]
(events/event-list events))))
My queries compose correctly and the initial data is normalised correctly. I won't show the normalised data and there's more to the total query.
(prn (om/get-query RootView)) =>
[{:list/events
[:id
{:body [:id :text :headline]}
{:media [:id :url :caption :credit]}
{:start-date [:id :year :month :day]}]}]
If I run a query containing the joins through a parser I get:
(prn (parser {:state (atom norm-data)}
'[{:list/events
[:id
{:body [:id :text :headline]}
{:media [:id :url :caption :credit]}
{:start-date [:id :year :month :day]}]}])) =>
{:list/events
[{:id 1,
:media [:media/by-id 1],
:start-date [:start-date/by-id 1],
:body [:body/by-id 1]}
{:id 17,
:media [:media/by-id 17],
:start-date [:start-date/by-id 17],
:body [:body/by-id 17]}]}
So the read function for :list/events is called and returns it's data, though all the second joins for :body, :media and :start-date are not.
My read functions are as follows, the second one is the one that is not called. I've left out the multi-methods on :media and :start-date, they also are not called. I'm not sure what this is a symptom of though.
(defmulti read om/dispatch)
(defmethod read :list/events
[{:keys [state] :as env} key params]
(let [st #state]
{:value (into [] (map #(get-in st %)) (get st key))}))
(defmethod read :body
[{:keys [state query]} key _]
(println "This is never printed")
{:value :doesnt-matter})
The join is correctly identified in the AST (so I assume the query grammar is correct) and the dispatch key matches that of the multi-method.
(prn (om/query->ast (om/get-query RootView))) =>
{:type :root,
:children
[{:type :join,
:dispatch-key :list/events,
:key :list/events,
:query
[:id
{:body [:id :text :headline]}
{:media [:id :url :caption :credit]}
{:start-date [:id :year :month :day]}],
:component timeline.components.events/Event,
:children
[{:type :prop, :dispatch-key :id, :key :id}
{:type :join,
:dispatch-key :body,
:key :body,
:query [:id :text :headline],
:component timeline.components.events/EventBody,
:children
[{:type :prop, :dispatch-key :id, :key :id}
{:type :prop, :dispatch-key :text, :key :text}
{:type :prop, :dispatch-key :headline, :key :headline}]}]}]}
I can't understand why the parser or something (?) stops at the second join? As far as my limited understanding goes, the multi-method on :body should at least be called?
So the issue I'm having is one of understanding I think, António Monteiro in the Om Slack channel suggested I use the db->tree function. Using this in the :list/events multi-method let's it return the whole tree of de-normalised data.
You have to do the recursion from within the reads yourself i.e. invoke the parser on the query that is within the key being examined. db->tree does this for you. In fact it is not unusual for every read to call db->tree and so look pretty much the same. In fact because of this Untangled does away with these reads altogether. In which case you really don't have to do the recursion yourself!
There's no recursion here:
(into [] (map #(get-in st %)) (get st key))
Any get on a key is to the refs part of the default db formatted data (app data). So here a sequence of idents will be returned by (get st key). Any get-in is to the tables part of the app data, and so returns real data values. (map #(get-in st %)) is the transducer that does this for every ident. But the tables part of the data is a recursive data structure - has to be for a lack of repetition - so any data that is not 'leaf' data is represented by an ident. So that's what you are getting back - anything that's one level deep and idents otherwise.
This answer is going to make next to no sense without an understanding of the default database format - the refs and tables parts. The best explanation I've found so far is here
This data (st) is in default db format:
{ :list/people [[:people/by-id 1] [:people/by-id 2] ... ]
:people/by-id { 1 { :db/id 1 :person/name "Joe" :person/mate [:people/by-id 2]}
2 { :db/id 2 :person/name "Sally" :person/mate [:people/by-id 1]}}}
Wherever you see by-id that's a give away that the key is in a tables mapentry. As you can see by the structure (get-in st [:people/by-id 1]) will retrieve for you a map that is the real data, of course only to one level deep.
Here :list/people is a key where the associated value is a vector of idents. (get st :list/people) will give you this vector. This mapentry is the refs part of st.

Turning SQL Korma results into json

I am using SQL Korma to run some simple examples on a DB and am trying to convert this into JSON using Cheshire.
This works well when I have only 1 record returned but throws an error when I have more than 1 result.
Here are the 2 functions:
(defn get-room [id]
(first (select room
(where {:id id})
(limit 1))))
(defn get-rooms []
(select room))
and data:
(def x get-rooms)
(def y (get-room 1))
X is of type testproj.models.db:
(x)
=> [{:created_on "2014-04-05 13:19:47", :id 1, :description "Room 1"} {:created_on "2014-04-05 13:20:17", :id 2, :description "Room 2"} {:created_on "2014-04-05 13:20:20", :id 3, :description "Room 3"}]
Because y is a Hashmap:
(pr-str y)
=> "{:created_on \"2014-04-05 13:19:47\", :id 1, :description \"Room 1\"}"
Trying to convert to Json:
(cheshire.core/generate-string x)
JsonGenerationException Cannot JSON encode object of class: class testproj.models.db$get_rooms: testproj.models.db$get_rooms#507501ff cheshire.generate/generate (generate.clj:147)
(cheshire.core/generate-string y)
=> "{\"created_on\":\"2014-04-05 13:19:47\",\"id\":1,\"description\":\"Room 1\"}"
Why is korma returning different types based on the amount of records (this would help me understand this better) and secondly - how should I go about this?
It seems you're missing a function call. Try this:
(cheshire.core/generate-string (x))