Appending to a JSON Array in a loop - ruby-on-rails

Need to submit an HTTP POST request of a json string.
Have all of the RESTful parts working, and verified correctly.
This works if I hard code the elements inside the array. I am wanting to loop through one of the arrays in the JSON string, the "Attachments Array".
The loop would look something like:
#days = Daily.all
#days[0..11] do |dailys|
#loop that builds json to post
end
#RESTful HTTP POST request
The only problem is, I don't know how to implement a loop inside of a JSON string for only one array.
My code so far for testing out the HTTP POST
#!/usr/bin/ruby
require 'net/http'
require 'uri'
require 'json'
require 'jbuilder'
uri = URI.parse("<POST URL>")
header = {'Content-Type' => 'text/json'}
payload = {
channel: "<channel>",
username: "<username>",
# Wish to loop through the "Attachments" Array
attachments: [
{
param: "Text",
text: "Text",
pretext: "Optional Text",
color: 'good',
fields: [
{
Title: "Title field",
value: "First Value",
short: false
},
{
title: "Field Title",
value: "Field Value",
short: true
},
{
title: "Second Field Title",
value: "Second Field Value",
short: true
}
]
}
]
}
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
req = Net::HTTP::Post.new(uri.request_uri, header)
req.body = payload.to_json
resp = http.request(req)
puts resp

Your code is looping through a bunch of Daily objects (though you're calling each one 'dailys' inside the loop which is confusing). You don't really explain what you want to do, but if you wanted to make a json object using some of the attributes of the Daily objects you have, you could do it like this.
hash = Daily.find(:all, :limit => 12).map{|daily| {:foo => daily.bar, :chunky => daily.chicken}}.to_json
If you give some details of the json string you want to make from your daily objects then i could give you some less silly code, but the approach is correct: build a ruby data structure then convert it to json with to_json
EDIT: following on from your comment, i would do this.
hash = Daily.find(:all, :limit => 12, :order => "id desc").map{|daily| {param: daily.title, fields: [{title: "Task Completed?", value: daily.completed, short: true}]} }
json = hash.to_json
Couple of notes:
1) Daily is a class, not an object (while classes are also objects in Ruby, i think that in rails it's better to refer to it as the class.
2) By the last 12 elements i assume you mean the most recently saved records: this is what the :order option is for.

Related

How can i convert string to array of object json in Rails

i just create a migration like this
def change
add_column :articles, : information, :json,
default: '[{ "type:c":"12", "temperature":12 }]', null: false
end
i just migrated that table
right now, to create a new article, i am sending this in my request with postman, i am sending properly the 'information' as string?
{
"section":"139",
"information":"[{ type:c, temperature:9 }]" //string
}
so far every thing is ok
now i want to get the param information as a array not string
I want to convert information in an array of json object, how can i do that?
This can be achieved in two ways.
require 'yaml'
input_params = {"section":"139","information":"[{ type: c, temperature: 9 }]"}
#=> {"section"=>"139", "information"=>"[{ type: c, temperature: 9 }]"}
YAML.load(input_params['information'])
#=> [{"type"=>"c", "temperature"=>9}]
or If you want to use JSON.parse then input_params should be as below:
require 'json'
input_params = {"section"=> "139", "information" =>'[{ "type":"c", "temperature":"9" }]'}
#=> {"section"=>"139", "information"=>"[{ \"type\":\"c\", \"temperature\":\"9\" }]"}
JSON.parse(input_params['information'])
#=> [{"type"=>"c", "temperature"=>9}]
I hope this will help you

Why am I getting "no implicit conversion of String into Integer" when trying to get Nested JSON attribute?

My Rails app is reading in JSON from a Bing API, and creating a record for each result. However, when I try to save one of the nested JSON attributes, I'm getting Resource creation error: no implicit conversion of String into Integer.
The JSON looks like this:
{
"Demo": {
"_type": "News",
"readLink": "https://api.cognitive.microsoft.com/api/v7/news/search?q=european+football",
"totalEstimatedMatches": 2750000,
"value": [
{
"provider": [
{
"_type": "Organization",
"name": "Tuko on MSN.com"
}
],
"name": "Hope for football fans as top European club resume training despite coronavirus threat",
"url": "https://www.msn.com/en-xl/news/other/hope-for-football-fans-as-top-european-club-resume-training-despite-coronavirus-threat/ar-BB12eC6Q",
"description": "Bayern have returned to training days after leaving camp following the outbreak of coronavirus. The Bundesliga is among top European competitions suspended."
}
}
The attribute I'm having trouble with is [:provider][:name].
Here's my code:
def handle_bing
#terms = get_terms
#terms.each do |t|
news = get_news(t)
news['value'].each do |n|
create_resource(n)
end
end
end
def get_terms
term = ["European football"]
end
def get_news(term)
accessKey = "foobar"
uri = "https://api.cognitive.microsoft.com"
path = "/bing/v7.0/news/search"
uri = URI(uri + path + "?q=" + URI.escape(term))
request = Net::HTTP::Get.new(uri)
request['Ocp-Apim-Subscription-Key'] = accessKey
response = Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
http.request(request)
end
response.each_header do |key, value|
# header names are coerced to lowercase
if key.start_with?("bingapis-") or key.start_with?("x-msedge-") then
puts key + ": " + value
end
end
return JSON(response.body)
end
def create_resource(news)
Resource.create(
name: news['name'],
url: news['url'],
description: news['description'],
publisher: news['provider']['name']
)
end
I looked at these questions, but they didn't help me:
Extract specific field from JSON nested hashes
No implicit conversion of String into Integer (TypeError)?
Why do I get "no implicit conversion of String into Integer (TypeError)"?
UPDATE:
I also tried updating the code to:
publisher: news['provider'][0]['name'], but I received the same error.
because "provider" is an array.
it should be accessed with index.
[:value][0][:provider][0][:name]
same goes with "value".

Stringify/Parse request data through rspec

I'm submitting requests on the frontend where I stringify my data (array of objects) and then parse it in the backend.
When I run my specs, I'm getting the error no implicit conversion of Array into String
How can I stringify my data in my spec so that it's consistent with what I'm doing in the frontend? Or is there another way where I don't have to stringify/parse my data to handle all of this?
This is how my frontend data structure looks like:
"categories_and_years": JSON.stringify(
[
{"category_id": 1, "year_ids":[1, 2, 3]},
{"category_id": 2, "year_ids":[2, 3]},
]
)
In my controller, I'm validating the data is an array first:
def validate_categories_and_years_array
#cats_and_yrs = JSON.parse(params[:categories_and_years])
return unless #cats_and_yrs
if !#cats_and_yrs.is_a?(Array)
render_response(:unprocessable_entity, { description_detailed: "categories_and_years must be an array of objects"})
end
end
In my specs, I'm setting my params like this:
context "when all categories and years are valid" do
let(:params) do
{
school_id: school.id,
id: standard_group.id,
categories_and_years: [
{ category_id: category_1.id, year_ids: [ year_1.id ] }
]
}
end
it "adds standards from specific categories and years to the school" do
post :add, params: params, as: :json
expect(school.achievement_standards).to contain_exactly( std_1 )
end
end
This post explains the difference between a regular ruby hash which you have in your spec and a HashWithIndifferentAccess.
Can you also try to_json?
Params hash keys as symbols vs strings
params = ActiveSupport::HashWithIndifferentAccess.new()
params['school_id'] = school.id
params['id'] = standard_group.id
params['categories_and_years'] = [
{ category_id: category_1.id, year_ids: [ year_1.id ] }
]
params = params.to_json
let(:params) { params }

Ruby - Access value from json array

I am creating an array of fields
def create_fields fields
fields_list = []
fields.each do |field|
# puts "adding_field to array: #{field}"
field_def = { field: field, data: { type: 'Text', description: '' } }
fields_list.push field_def
end
fields_list
end
The fields_list is being set to a jsonb field.
Lets say I pass in
create_fields ['Ford', 'BMW', 'Fiat']
Json result is an array:
{"field"=>"Ford", "data"=>{"type"=>"Text", "description"=>""}}
{"field"=>"BMW", "data"=>{"type"=>"Text", "description"=>""}}
{"field"=>"Fiat", "data"=>{"type"=>"Text", "description"=>""}}
How can I access the 'Ford' from the json array? Am i creating the array incorrectly? Is there a better way to create this array so I can access the field i want?
This assertion passes assert_equal(3, fields.count)
However i want to get 'Ford' and check it's properties, e.g. type = 'Text', type could equal 'Number' or whatever.
The result of your create_fields method with the specified parameters is the following:
[
{:field=>"Ford", :data=>{:type=>"Text", :description=>""}},
{:field=>"BMW", :data=>{:type=>"Text", :description=>""}},
{:field=>"Fiat", :data=>{:type=>"Text", :description=>""}}
]
It means that if you want to access the line belonging to "Ford", you need to search for it like:
2.3.1 :019 > arr.select{|e| e[:field] == "Ford" }
=> [{:field=>"Ford", :data=>{:type=>"Text", :description=>""}}]
2.3.1 :020 > arr.select{|e| e[:field] == "Ford" }[0][:data][:type]
=> "Text"
This is not optimal, because you need to search an array O(n) instead of using the pros of a hash. If there are e.g.: 2 "Ford" lines, you'll get an array which contains 2 elements, harder to handle collisions in field value.
It would be better if you created the array like:
def create_fields fields
fields_list = []
fields.each do |field|
# puts "adding_field to array: #{field}"
field_def = [field, { type: 'Text', description: '' } ]
fields_list.push field_def
end
Hash[fields_list]
end
If you choose this version, you can access the members like:
2.3.1 :072 > arr = create_fields ['Ford', 'BMW', 'Fiat']
=> {"Ford"=>{:type=>"Text", :description=>""}, "BMW"=>{:type=>"Text", :description=>""}, "Fiat"=>{:type=>"Text", :description=>""}}
2.3.1 :073 > arr["Ford"]
=> {:type=>"Text", :description=>""}
2.3.1 :074 > arr["Ford"][:type]
=> "Text"
Both of the above examples are Ruby dictionaries / Hashes.
If you want to create a JSON from this, you will need to convert it:
2.3.1 :077 > require 'json'
=> true
2.3.1 :078 > arr.to_json
=> "{\"Ford\":{\"type\":\"Text\",\"description\":\"\"},\"BMW\":{\"type\":\"Text\",\"description\":\"\"},\"Fiat\":{\"type\":\"Text\",\"description\":\"\"}}"
This is a structure that makes more sense to me for accessing values based on known keys:
def create_fields fields
fields_hash = {}
fields.each do |field|
fields_hash[field] = {type: 'Text', description: ''}
end
fields_hash
end
# The hash for fields_hash will look something like this:
{
Ford: {
type: "Text",
description: ""
},
BMW: {...},
Fiat: {...}
}
This will allow you to access the values like so: fields[:Ford][:type] in ruby and fields.Ford.type in JSON. Sounds like it would be easier to return an Object rather than an Array. You can access the values based on the keys more easily this way, and still have the option of looping through the object if you want.
Obviously, there are several ways of creating or accessing your data, but I'd always lean towards the developer picking a data structure best suited for your application.
In your case currently, in order to access the Ford hash, you could use the Ruby Array#detect method as such:
ford = fields_list.detect{|field_hash| field_hash['field'] == 'Ford' }
ford['data'] # => {"type"=>"Text", "description"=>""}
ford['data']['type'] # => 'Text'
So, you have result of your method:
result =
[
{"field"=>"Ford", "data"=>{"type"=>"Text", "description"=>""}},
{"field"=>"BMW", "data"=>{"type"=>"Text", "description"=>""}},
{"field"=>"Fiat", "data"=>{"type"=>"Text", "description"=>""}}
]
to get 'Ford' from it you can use simple method detect
result.detect { |obj| obj['field'] == 'Ford' }
#=> { "field"=>"Ford", "data"=>{"type"=>"Text", "description"=>""}
Also I recommend you to edit your method to make it more readable:
def create_fields(fields)
fields.map do |field|
{
field: field,
data: {
type: 'Text',
description: ''
}
}
end
end

Using result of `JSON.decode` in Rails

So I have an action in my controller that does a get_response to an API:
def memeapi
require "net/http"
require "uri"
#meme = Meme.new(params[:meme])
url = "http://version1.api.memegenerator.net/Instance_Create?username=apigen&password=SECRET&languageCode=en&generatorID=#{#meme.memeid}&imageID=#{#meme.imgid}&text0=#{#meme.text0}&text1=#{#meme.text1}"
resp = Net::HTTP.get_response(URI.parse(url))
data = resp.body
# I want to convert it to the Rails data structure - a hash
result = ActiveSupport::JSON.decode(data)
end
Ok but now I want to get back the information, use it to create another object, but I cant even format the information I am getting, can anyone tell me what I am doing wrong, what am I missing?
I want to be able to access the information from the get_response...
Thank you.
This is the JSON structure
{"success":true,"result":{"generatorID":45,"displayName":"Insanity Wolf","urlName":"Insanity-Wolf","totalVotesScore":0,"imageUrl":"/cache/images/400x/0/0/20.jpg","instanceID":13226270,"text0":"push","text1":null,"instanceImageUrl":"/cache/instances/400x/12/12916/13226270.jpg","instanceUrl":"http://memegenerator.net/instance/13226270"}}
I dont want to save all the fields btw...
result will look something like this after JSON.decode in your code.
{
'success' => true,
'result' => {
"generatorID" => 45,
"displayName" => "Insanity Wolf",
"urlName" => "Insanity-Wolf",
"totalVotesScore" => 0,
"imageUrl" => "/cache/images/400x/0/0/20.jpg",
"instanceID" => 13226270,
"text0" => "push",
"text1" => nil,
"instanceImageUrl" => "/cache/instances/400x/12/12916/13226270.jpg",
"instanceUrl" => "http://memegenerator.net/instance/13226270"
}
}
You have a nested hash (hash as part of a hash) here, which you can access like this:
image_url = result['result']['imageUrl'] # => "/cache/images/400x/0/0/20.jpg"
What you actually want to do with this information, I cannot guess. Maybe you want to update the Meme object you created?
#meme.url = image_url

Resources