Turning SQL Korma results into json - 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))

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?

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

Activerecord query with group on multiple columns returning a hash with array as a key

I wrote an ActiveRecord query to fetch count of some data after grouping by two columns col_a and col_b
result = Sample.where(through: ['col_a', 'col_b'], status: [1, 5]).where("created_at > ?", 1.month.ago).group(:status, :through).count
This returns:
{[1, "col_a"]=>7, [1, "col_b"]=>7, [5, "col_a"]=>4, [5, "col_b"]=>1}
Now my question is, how do I access the values in this hash?
Doing something like results[1, "col_a"] throws an error (wrong no. of arguments).
I know I can do this by writing a loop and extracting the values one by one.
However I want to know if there is a more idiomatic way to access the values, something similar to results[1], maybe?
results[[1, "col_a"]]
# => 7
Four possible ways (I'm sure there are others):
# fetch one value at a time
results[[1, "col_a"]]
# => 7
# fetch all the values
results.values
# => [7, 7, 4, 1]
# loop through keys and values
results.each do |key, value|
puts key
puts value
end
# => [1, "col_a"], 7....
# convert results into a more usable hash
results.map! { |k,v| { k.join("_") => v } }.reduce({}, :merge)
results['1_col_a']
# => 7
Another heavier option, especially if this is a query you will do often, is to wrap the results into a new Ruby object. Then you can parse and use the results in a more idiomatic way and define an accessor simpler than [1,'col_a'].
class SampleGroupResult
attr_reader key, value
def initialize(key, value)
#key = key
#value = value
end
end
results.map { |k,v| SampleGroupResult.new(k,v) }

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.

ArgumentError wrong number of arguments (1 for 3..4)

I get an ArgumentError for line #3 in my views/-/new.html.erb file that states:
"wrong number of arguments (1 for 3..4)"
<div class='form-group'>
<%= form.label :category %>
<%= form.select "category", options_from_collection_for_select([{1 => 'Food'}, {2 => 'Entertainment'}]) %>
</div>
The Application trace states:
app/views/events/new.html.erb:14:in block in _app_views_events_new_html_erb__1569841425540097418_70204987081640'
app/views/events/new.html.erb:5:in_app_views_events_new_html_erb__1569841425540097418_70204987081640'
erb:14 is line #3 above, and erb:5 is
<%= form_for #event do |form| %>
The error message is being thrown by options_from_collection_for_select([{1 => 'Food'}, {2 => 'Entertainment'}])
[{1 => 'Food'}, {2 => 'Entertainment'}] is an array being passed as one argument to options_from_collection_for_select method; hence the error message.
The correct form for calling the options_from_collection_for_select helper method is
options_from_collection_for_select(collection, value_method, text_method, selected = nil)
See more details and usage examples at http://apidock.com/rails/ActionView/Helpers/FormOptionsHelper/options_from_collection_for_select
I think you'd be better off using options_for_select, which will work with the arguments you've provided and just replace the options_from_collection_for_select in your code. The documentation for which can be found here.
You need to decide whether you want the number (1, 2 etc.) or the word (Food, Entertainment etc.) to be the "value" which reaches your back-end. For example:
options_for_select({'Food' => 1, 'Entertainment' => 2})
The output of the above in HTML would be as follows:
<option value="1">Food</option>
<option value="2">Entertainment</option>
The options_from_collection_for_select method requires 3 arguments. You've only provided one: [{1 => 'Food'}, {2 => 'Entertainment'}]. Your second argument should be a method/attribute on each object in the collection which represents the "value", the third should be the label for that value.