LIKE Query on Postgres JSON array field in Rails - ruby-on-rails

I have a table called messages which has a jsonB column called headers to store message headers. It looks like this:
[
{
"name":"Cc",
"field":{
"name":"Cc",
"value":"\"abc#gmail.com\" <abc#gmail.com>",
"length":null,
"charset":"UTF-8",
"element":null,
"address_list":{
"addresses":[
{
"data":{
"raw":"\"abc#gmail.com\" <abc#gmail.com>",
"error":null,
"group":null,
"local":"abc",
"domain":"gmail.com",
"comments":[
],
"display_name":"abc#gmail.com",
"obs_domain_list":null
},
"parsed":true
}
],
"group_names":[
]
}
},
"charset":"UTF-8",
"field_order_id":14,
"unparsed_value":"\"abc#gmail.com\" <abc#gmail.com>"
},
{
"name":"Message-ID",
"field":{
"name":"Message-ID",
"uniq":1,
"value":"<sdasdasd+tkiCVQ#mail.gmail.com>",
"length":null,
"charset":"UTF-8",
"element":{
"message_ids":[
"sdasdasd+tkiCVQ#mail.gmail.com"
]
}
},
"charset":"UTF-8",
"field_order_id":16,
"unparsed_value":"<sdasdasd+tkiCVQ#mail.gmail.com>"
},
{
"name":"Subject",
"field":{
"name":"Subject",
"value":"Re: test email",
"errors":[
],
"length":null,
"charset":"UTF-8",
"element":null
},
"charset":"UTF-8",
"field_order_id":19,
"unparsed_value":"Re: test email"
}
]
I want search records where 'name' = 'Subject' and 'unparsed_value' like %test% and return the result in Rails 6.0.2?
I'm trying the below mention code:
messages.where("headers #> '[{\"name\": \"Subject\"}, {\"unparsed_value\" LIKE \"%test%\"}]'")
But it's throwing error!

You can do a sub query to get the elements you need to compare and then use them in the where clause:
Message
.from(
Message.select("
id,
headers,
jsonb_array_elements(headers)->>'unparsed_value' AS unparsed_value,
jsonb_array_elements(headers)->>'name' AS name
"), :t
)
.select('t.*')
.where("t.name = 'Subject' AND t.unparsed_value LIKE '%test%'")

The query you need looks like this:
SELECT * FROM messages WHERE
(SELECT true FROM jsonb_to_recordset(messages.headers)
AS x(name text, field jsonb)
WHERE name = 'Subject'
AND field->>'unparsed_value' LIKE '%test%');
Does this give you the result you're looking for?

Related

How to take keep parts of an array and form a new array?

I am building a Rails 5 app.
In this app I have connected to the Google Calendar API.
The connection works fine and I get a list of calendars back.
What I need to do is to get the Id and Summary of this JSON object that I get back from Google.
This is what I get
[{
"kind": "calendar#calendarListEntry",
"etag": "\"1483552200690000\"",
"id": "xxx.com_asae#group.calendar.google.com",
"summary": "My office calendar",
"description": "For office meeting",
"location": "344 Common st",
"colorId": "8",
"backgroundColor": "#16a765",
"foregroundColor": "#000000",
"accessRole": "owner",
"defaultReminders": [],
"conferenceProperties": {
"allowedConferenceSolutionTypes": [
"hangoutsMeet"
]
}
},
{
"kind": "calendar#calendarListEntry",
"etag": "\"1483552200690000\"",
"id": "xxx.com_asae#group.calendar.google.com",
"summary": "My office calendar",
"description": "For office meeting",
"location": "344 Common st",
"colorId": "8",
"backgroundColor": "#16a765",
"foregroundColor": "#000000",
"accessRole": "owner",
"defaultReminders": [],
"conferenceProperties": {
"allowedConferenceSolutionTypes": [
"hangoutsMeet"
]
}
}]
This is what I want to end up with
[{
"id": "xxx.com_asae#group.calendar.google.com",
"title": "My office calendar",
}]
The purpose of this is that I want to populate a selectbox using Selectize plugin
Another way to achieve removing of certain keys in your hash is by using Hash#reject method:
response = { your_json_response }
expected = [response[0].reject {|k| k != :id && k != :summary}]
The original response remains unchanged while a mutated copy of the original response is returned.
You can filter the desierd keys with the select method:
responde = {your_json_response}
expected = [response[0].select{|k,v| ['id','title'].include?(k)}]
response[0] retrieves the hash, and the select compares each key with the ones you want and returns a hash with only those key: value pairs.
EDIT: I missed that you don't have a "title" key on the original response, I would do this then:
response = {your_json_response}
h = response[0]
expected = [{'id' => h['id'], 'title' => h['summary']}]
EDIT 2: Sorry, the first example was not clear that there would be multiple hashes
expected = response.map{|h| {'id' => h['id'], 'title' => h['summary']}}
map iterates over each element of response and returns the result of the block applied for each iteration as an array, so the blocks is apllied to each h and it generates a new hash from it
I suggest this approach.
expected = response.each { |h| h.keep_if { |k, _| k == :id || k == :summary } }
It returns just the required pairs:
# => [{:id=>"xxx.com_asae#group.calendar.google.com", :summary=>"My office calendar"}, {:id=>"xxx.com_asae#group.calendar.google.com", :summary=>"My office calendar"}]
To remove duplicates, just do expected.uniq
If you need to change the key name :summary to :title do:
expected = expected.each { |h| h[:title] = h.delete(:summary) }
One liner
expected = response.each { |h| h.keep_if { |k, _| k == :id || k == :summary } }.each { |h| h[:title] = h.delete(:summary) }.uniq
Of course, maybe it is better to move .uniq as first method expected = response.uniq.each { .....

split multi-dimensional array in ruby

I am trying to parse json data in ruby my desired output is:
var events = { '01-01-2018' :
[ {content: 'Psalm 2', allDay: true},
{content: 'by ToddWagner', allDay: true}
],
'01-02-2018' :
[ {content: 'Psalm 2', allDay: true},
{content: 'by ToddWagner', allDay: true}
]
}
what I get is
var events = [
{"2017-11-03":
[ {"content":"Romans 14:5-12","allDay":true},
{"content":"by Micah Leiss","allDay":true}
]
},
{"2017-11-06":
[{"content":"Romans 14:13","allDay":true},
{"content":"by Sarah Thomas","allDay":true}
]
}
]
I tried something like
data = []
raw_data['entries'].each do |entry|
data << {entry_date => [
{
"content" => entry.title,
"allDay" => true,
},
{
"content" => entry.writer,
"allDay" => true,
},
]
}
end
data.to_json
but I didn't get desired results, I have also tried data.pop data.shift.
Ruby implementation would look like:
data = raw_data['entries'].map do |entry|
[entry.date, [entry.title, entry.writer].map do |content|
{content: content, allDay: true}
end]
end.to_h
First of all, are adding fields to your array data, as I can see from your desired output, you need a hash.
You have to create the hash, not the array:
data = {}
and then in your loop
raw_data['entries'].each do |entry|
add it like that
data[entry_date] = [
{
"content" => entry.title,
"allDay" => true,
},
{
"content" => entry.writer,
"allDay" => true,
},
]
(I am not where do you declare entry_date in your example so it might be entry.date)
I can't tell from your example if entry date is unique or not(and I think it's not) make sure you add to hash, because you might overwrite it.
You can do something like this if entry date isn't unique
data[entry.date] ||= []
data[entry.date] << {hash_you_need}

Searching nested hash

These is sample response of hashes in ruby.
Eg:-
find abcd1234
should give me
i was able to find by but it's not sufficent
I have response of sth like these and list keep on going different value but same structure
[
{
"addon_service": {
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "heroku-postgresql"
},
"config_vars": [
"FOO",
"BAZ"
],
"created_at": "2012-01-01T12:00:00Z",
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "acme-inc-primary-database",
"plan": {
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "heroku-postgresql:dev"
},
"app": {
"id"=>"342uo23iu4io23u4oi2u34",
"name"=>"heroku-staging"},
},
"provider_id": "abcd1234",
"updated_at": "2012-01-01T12:00:00Z",
"web_url": "https://postgres.heroku.com/databases/01234567-89ab-cdef-0123-456789abcdef"
} .........
]
can anyone know how to grab those?
You can iterate all array element (a hash) and display its content if the hash meet your requirement:
element_found = 0
YOUR_DATA.each do |element|
if element["provider_id"].match(/abcd1234/)
element_found += 1
puts "addon_service: #{element['addon_service']['name']}"
puts "app: #{element['app']['name']}"
end
end
if element_found == 0 puts "Sorry match didn't found"
Since the elements of the array are hashes you can select the appropriate ones by matching the desired key.
select {|app| app[:provider_id] == "abcd1234"}
Do you know what to do with the element once you select it?
I think you want some of the items from the hash, but not all of them.
That might look like:
select {|app| app[:provider_id] == "abcd1234"}.map {|app| app.select {|key, v| [:addon_service, :app].include?(key) } }

How do I pretty print a hash to a Rails view?

I have something like:
{"a":"1","b":"2","c":"3","asefw":"dfsef"}
I want to print it out in a view. What's the best way to do that?
I tried parsing it as a JSON object and using JSON.stringify, but it seems to mess up indentation.
Any advice? I don't mind a JavaScript solution.
How about:
require 'json'
hash = JSON['{"a":"1","b":"2","c":"3","asefw":"dfsef"}']
puts JSON.pretty_generate(hash)
Which outputs:
{
"a": "1",
"b": "2",
"c": "3",
"asefw": "dfsef"
}
JSON.pretty_generate is more of a debugging tool than something I'd rely on when actually generating JSON to be sent to a browser. The "pretty" aspect also means "bloated" and "slower" because of the added whitespace, but it is good for diagnosing and comprehending what is in the structure so it might work well for your needs.
One thing to remember is that HTML, when rendered by a browser, has whitespace gobbled up, so whitespace runs disappear. To avoid that you have to wrap the JSON output in a <pre> block to preserve the whitespace and line-breaks. Something like this should work:
<pre>
{
"a": "1",
"b": "2",
"c": "3",
"asefw": "dfsef"
}
</pre>
irb(main)> puts queried_object.pretty_inspect
From PrettyPrint, so may need to require 'pp' first for this to work.
This also works great for e.g. Rails.logger output.
<%= raw JSON.pretty_generate(hash).gsub(" "," ") %>
If you (like I) find that the pretty_generate option built into Ruby's JSON library is not "pretty" enough, I recommend my own NeatJSON gem for your formatting.
To use it gem install neatjson and then use JSON.neat_generate instead of JSON.pretty_generate.
Like Ruby's pp it will keep objects and arrays on one line when they fit, but wrap to multiple as needed. For example:
{
"navigation.createroute.poi":[
{"text":"Lay in a course to the Hilton","params":{"poi":"Hilton"}},
{"text":"Take me to the airport","params":{"poi":"airport"}},
{"text":"Let's go to IHOP","params":{"poi":"IHOP"}},
{"text":"Show me how to get to The Med","params":{"poi":"The Med"}},
{"text":"Create a route to Arby's","params":{"poi":"Arby's"}},
{
"text":"Go to the Hilton by the Airport",
"params":{"poi":"Hilton","location":"Airport"}
},
{
"text":"Take me to the Fry's in Fresno",
"params":{"poi":"Fry's","location":"Fresno"}
}
],
"navigation.eta":[
{"text":"When will we get there?"},
{"text":"When will I arrive?"},
{"text":"What time will I get to the destination?"},
{"text":"What time will I reach the destination?"},
{"text":"What time will it be when I arrive?"}
]
}
It also supports a variety of formatting options to further customize your output. For example, how many spaces before/after colons? Before/after commas? Inside the brackets of arrays and objects? Do you want to sort the keys of your object? Do you want the colons to all be lined up?
For example, using your example Hash, you can get these different outputs, depending on what you want:
// JSON.neat_generate(o, wrap:true)
{
"a":"1",
"b":"2",
"c":"3",
"asefw":"dfsef"
}
// JSON.neat_generate o, wrap:true, aligned:true
{
"a" :"1",
"b" :"2",
"c" :"3",
"asefw":"dfsef"
}
// JSON.neat_generate o, wrap:true, aligned:true, around_colon:1
{
"a" : "1",
"b" : "2",
"c" : "3",
"asefw" : "dfsef"
}
You can try the gem awesome_print works very well, and in your view write
<%= ap(your_hash, plain: true, indent: 0).html_safe %>
also, you can change the values for config the styles to hash view
The given response is works fine, but if you want to have prettier and more custom pretty hash, use awesome_print
require 'awesome_print'
hash = JSON['{"a":"1","b":"2","c":"3","asefw":"dfsef"}']
ap hash
Cheers!
Pretty Print Hash using pure Ruby (no gems)
I came across this thread trying to solve this problem for myself.
I had a large Hash that I wanted to make pretty, but I needed to stay in ruby hash notation instead of JSON.
This is the code + examples
Use pretty_generate to get a nice formatted JSON string.
Replace all the JSON keys with symbol: equivalent
puts JSON.pretty_generate(result)
.gsub(/(?:\"|\')(?<key>[^"]*)(?:\"|\')(?=:)(?:\:)/) { |_|
"#{Regexp.last_match(:key)}:"
}
Sample JSON
{
"extensions": {
"heading": "extensions",
"take": "all",
"array_columns": [
"name"
]
},
"tables": {
"heading": "tables",
"take": "all",
"array_columns": [
"name"
]
},
"foreign_keys": {
"heading": "foreign_keys",
"take": "all",
"array_columns": [
"name"
]
},
"all_indexes": {
"heading": "all_indexes",
"take": "all",
"array_columns": [
"name"
]
},
"keys": {
"heading": "keys",
"take": "all",
"array_columns": [
"name"
]
}
}
Sample Ruby Hash
{
extensions: {
heading: "extensions",
take: "all",
array_columns: [
"name"
]
},
tables: {
heading: "tables",
take: "all",
array_columns: [
"name"
]
},
foreign_keys: {
heading: "foreign_keys",
take: "all",
array_columns: [
"name"
]
},
all_indexes: {
heading: "all_indexes",
take: "all",
array_columns: [
"name"
]
},
keys: {
heading: "keys",
take: "all",
array_columns: [
"name"
]
}
}

How to have two pieces of data output as json in rabl

New to rabl and not sure how to do this with two different arrays returned in a single hash like this:
#data={:locations => [location1, location2], :items => [item1,item2]}
In my rabl file, I'd like to do something like the following:
#data[:locations]
extends "api/location_show"
#data[:items]
extends "api/item_show"
to output this:
{
"locations": [
{
"id": 156,
"name": "Location 1"
},
{
"id": 158,
"name": "Location 2"
}
],
"items": [
{
"global_id": 3189,
"header": "pistachio 1"
},
{
"global_id": 3189,
"header": "pistachio 2"
}
]
}
but it just doesn't seem to be working. Is there a way to get this to work?
thx
Your rabl file should look something like:
object false
child (:locations) { attributes :id, :name }
child (:items) { attributes :global_id, :header }
By setting object to false, you essentially tell rabl that you want to construct your nodes on your own. Then you can go ahead and invoke the child and node methods as you wish.

Resources