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

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))

Related

Position key of path for grouped data vector map structure

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?

How to add an element into an array of a Serialize Field using Ruby on Rails

I have a field call query_data defined as text in my MySQL database.
In my model I defined this field as serialize :query_data, JSON.
The JSON format I would like to save and retrieve look like that:
{:items => [
{:id => 1},
{:id => 2},
{:id => 3}
]}
I have a collection (in that case, called items) that contain an array of objects.
I was wondering, what's the best way to add or delete an Item.
Ex: remove {:id => 2} from my items list and add `{:id => 4} to it
Ruby on Rails has some nice methods to move seamlessly between JSON and Ruby.
thing = {:items => [
{:id => 1},
{:id => 2},
{:id => 3}
]}
thing.to_json # "{\"items\":[{\"id\":1},{\"id\":2},{\"id\":3}]}"
thing.to_json is essentially what's happening in the serializer. If you want them back to Ruby, you can just do:
#items = #thing.query_data
JSON.parse(#items) # "items"=>[{"id"=>1}, {"id"=>2}, {"id"=>3}]}
Now that we can easily move between the two, lets just use Ruby syntax to deal with adding and deleting keys.
thing = {:items => [
{:id => 1},
{:id => 2},
{:id => 3}
]}
thing[:items] = thing[:items].append({:id => 4}) # adding a new item
thing[:items] = thing[:items].select { |item| item[:id] != 2 } # removing an item
First: The second argument to serialize should be the class of object you're storing in the field. You should have serialize :query_data, Hash instead.
Besides that, there aren't really any established best practices for working with serialized data. It really just depends too much on the structure of your data. You might as well ask, "what's the best way to add or delete an item from a hash?"
But since this is a hash you should make sure to keep dirty attributes in mind. If you were to do something like:
items = my_model.query_data[:items]
items.reject! {|item| item[:id] == 2}
items += {id: 4}
then the model wouldn't know that query_data changed and should be updated on save.
my_model.changed?
# => false
my_model.save
# Won't actually save changes to db.
To avoid this, you can:
A) Make sure you only ever set my_model.query_data directly
B) Explicitly call my_model.query_data_will_change! after changing that field so that it will be properly updated on save.
Base on #veridian-dynamics (thanks for your help!) Here what I did.
Model:
class MyModel < ApplicationRecord
serialize :item_data, JSON
end
Controller:
class ItemController < ApplicationController
before_action :authenticate_user!
def add_item
begin
mymodel = MyModel.find_or_create_by(id: param[:model_id])
if mymodel .item_data.blank?
item = {:items => []}
else
item = mymodel.item_data.deep_symbolize_keys
end
bookmark_exist = item[:items].any? {|i| i[:id] == params[:id]}
if !bookmark_exist
item[:items] = item[:items ].append({id: params[:id]}) # adding a new item
end
mymodel.item_data = item
mymodel.save
return render :json => item, :status=> 200
rescue Exception => e
return render :json =>{:errors=>e.message}, :status=> 400
puts "ERROR: #{e.message}"
end
end
def delete_item
begin
mymodel = MyModel.find_by(id: params[:model_id])
if mymodel.present? && mymodel.item_data.present?
item = mymodel.item_data.deep_symbolize_keys
item[:items] = (item[:items].select { |itm| itm[:id] != params[:id] }) # remove an item
mymodel.item_data = item
mymodel.save
return render :json => item, :status=> 200
end
rescue Exception => e
return render :json =>{:errors=>e.message}, :status=> 400
puts "ERROR: #{e.message}"
end
end
end

clojurescript equivalent of re-matcher and re-groups

I'm looking to implement seperate-humps in clojurescript but there is no re-matcher or re-groups. Is there an alternative method?
(defn re-sub
[^String value pattern sub-func]
(loop [matcher (re-matcher pattern value)
result []
last-end 0]
(if (.find matcher)
(recur matcher
(conj result
(.substring value last-end (.start matcher))
(sub-func (re-groups matcher)))
(.end matcher))
(apply str (conj result (.substring value last-end))))))
(defonce +hump-pattern+ #"[a-z0-9][A-Z]")
(defn separate-humps
[^String value]
(re-sub value +hump-pattern+ #(string/join " " (seq %))))
(separate-humps "aTaTa")
;;=> "a Ta Ta"
I wrote a function that reproduces the general functionality of re-matcher:
(defn grouper
"Uses js/RegExp to find matching groups. Note that the JS value
returned by `:last-index` is the index of the first char in the
input string *after* the current match."
[re input-str]
(let [re-src re.source] ; the source string from the regexp arg
(loop [groups []
regexp (js/RegExp. re-src "g")] ; 'g' => global search
(let [res (.exec regexp input-str)
res-clj (js->clj res)]
(if (nil? res)
groups
(recur
(conj groups {:groups res-clj :match (get res-clj 0)
:index res.index :input res.input
:last-index regexp.lastIndex})
regexp))))))
with output:
(grouper #"[a-z0-9][A-Z]" "aTaTa") =>
[ {:groups ["aT"] :match "aT" :index 0 :last-index 2 :input "aTaTa" }
{:groups ["aT"] :match "aT" :index 2 :last-index 4 :input "aTaTa" } ]
(grouper #"((\d+)-(\d+))" "672-345-456-3212") =>
[ {:groups ["672-345" "672-345" "672" "345" ] :match "672-345" :index 0 :last-index 7 :input "672-345-456-3212" }
{:groups ["456-3212" "456-3212" "456" "3212"] :match "456-3212" :index 8 :last-index 16 :input "672-345-456-3212" } ]
You can find the docs here.

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))