I made a table component using react js, which uses columns to display data (which works fine with other data). For example, column 1 would show the title, column 2 the year, and column 3 the format.
Here is an example of my JSON:
{"movies": [{"title": "Iron Man", "year": "2008", "format": "DVD"}, {"title": "Iron Man 2", "year": "2010", "format": "DVD"}, {"title": "Iron Man 3", "year": "2013", "format": "DVD"}]}
Here is my code to populate the table, but it does not seem to work:
#movieList = #Makes a call to my mock API to get list of movies
#movies = Array.new
#movieList.each do |item|
#movie = Hash.new
#movie[:column1] = item[:title]
#movie[:column2] = item[:year]
#movie[:column3] = item[:format]
#movies << #movie
end
I need some advice to overcome a "no implicit conversion of symbol into integer error" I get. Could anyone offer some advice and point out where I am going wrong?
tl;dr
use #movieList["movies"].each
explanation
The issue here, is that you act as though your #movieList is ann array, when it is actually a hash (assuming #movieList is the JSON you showed).
each works on both arrays and hashes. However, when you use it on a hash, the block is passed |key, val|. Also, assigning block variables is optional. So, when you say #movieList.each do |item|, item is actually the top level key of the hash ("movies").
Strings such as "movies" respond to [] indexing with numbers. That's why you get the error no implicit conversion of symbol into integer ... because you pass a symbol to String#[] and it expects an integer.
Another way to write this code, that is more idiomatic, would be like so:
#movies = #movieList["movies"].map do |movie|
{
column1: movie["title"],
column2: movie["year"],
column3: movie["format"]
}
end
try reassigning
#movieList = #movieList[:movies] this will solve your problem. You're trying to iterate a object instead of an array.
lemme know if it solves your problem.
You need to loop movies using #movieList["movies"] as your JSON is a hash that has a key 'movies' and an array of movies as a value => {'movies': [{...},{...},...]}
As #max pleaner explained assigning block variables is optional, but when you use each on a hash(your JSON in this case) and provide only one block variable (instead of two refering to the keys and values of the hash), your key-value pairs are converted to two-element arrays inside the block where first element is the key and second one is the value of the pair.
Your item looks like this inside your each block -
['movies', [{movie1}, {movie2},..]], hence:
item[0] # 'movies'
item[1] # [{movie1}, {movie2},...]
As arrays expect indexing with integers and you supply symbol (item[:title]), you receive:
TypeError (no implicit conversion of Symbol into Integer)
Related
Perhaps my understanding of how this is supposed to work is wrong, but I seeing strings stored in my DB when I would expect them to be a jsonb array. Here is how I have things setup:
Migration
t.jsonb :variables, array: true
Model
attribute :variables, :variable, array: true
Custom ActiveRecord::Type
ActiveRecord::Type.register(:variable, Variable::Type)
Custom Variable Type
class Variable::Type < ActiveRecord::Type::Json
include ActiveModel::Type::Helpers::Mutable
# Type casts a value from user input (e.g. from a setter). This value may be a string from the form builder, or a ruby object passed to a setter. There is currently no way to differentiate between which source it came from.
# - value: The raw input, as provided to the attribute setter.
def cast(value)
unless value.nil?
value = Variable.new(value) if !value.kind_of?(Variable)
value
end
end
# Converts a value from database input to the appropriate ruby type. The return value of this method will be returned from ActiveRecord::AttributeMethods::Read#read_attribute. The default implementation just calls #cast.
# - value: The raw input, as provided from the database.
def deserialize(value)
unless value.nil?
value = super if value.kind_of?(String)
value = Variable.new(value) if value.kind_of?(Hash)
value
end
end
So this method does work from the application's perspective. I can set the value as variables = [Variable.new, Variable.new] and it correctly stores in the DB, and retrieves back as an array of [Variable, Variable].
What concerns me, and the root of this question, is that in the database, the variable is stored using double escaped strings rather than json objects:
{
"{\"token\": \"a\", \"value\": 1, \"default_value\": 1}",
"{\"token\": \"b\", \"value\": 2, \"default_value\": 2}"
}
I would expect them to be stored something more resembling a json object like this:
{
{"token": "a", "value": 1, "default_value": 1},
{"token": "b", "value": 2, "default_value": 2}
}
The reason for this is that, from my understanding, future querying on this column directly from the DB will be faster/easier if in a json format, rather than a string format. Querying through rails would remain unaffected.
How can I get my Postgres DB to store the array of jsonb properly through rails?
So it turns out that the Rails 5 attribute api is not perfect yet (and not well documented), and the Postgres array support was causing some problems, at least with the way I wanted to use it. I used the same approach to the problem for the solution, but rather than telling rails to use an array of my custom type, I am using a custom type array. Code speaks louder than words:
Migration
t.jsonb :variables, default: []
Model
attribute :variables, :variable_array, default: []
Custom ActiveRecord::Type
ActiveRecord::Type.register(:variable_array, VariableArrayType)
Custom Variable Type
class VariableArrayType < ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Jsonb
def deserialize(value)
value = super # turns raw json string into array of hashes
if value.kind_of? Array
value.map {|h| Variable.new(h)} # turns array of hashes into array of Variables
else
value
end
end
end
And now, as expected, the db entry is no longer stored as a string, but rather as searchable/indexable jsonb. The whole reason for this song and dance is that I can set the variables attribute using plain old ruby objects...
template.variables = [Variable.new(token: "a", default_value: 1), Variable.new(token: "b", default_value: 2)]
...then have it serialized as its jsonb representation in the DB...
[
{"token": "a", "default_value": 1},
{"token": "b", "default_value": 2}
]
...but more importantly, automatically deserialized and rehydrated back into the plain old ruby object, ready for me to interact with it.
Template.find(123).variables = [#<Variable:0x87654321 token: "a", default_value: 1>, #<Variable:0x12345678 token: "b", default_value: 2>]
Using the old serialize api causes a write with every save (intentionally by Rails architectural design), regardless of whether or not the serialized attribute had changed. Doing this all manually by overriding setters/getters is an unnecessary complication due to the numerous ways attributes can be assigned, and is partly the reason for the newer attributes api.
If it helps anyone else, Rails wants you to provide the possible keys to permit in the controller as well if you're using strong params:
def controller_params
params.require(:parent_key)
.permit(
jsonb_field: [:allowed_key1, :allowed_key2, :allowed_key3]
)
end
One solution could be to just parse the variable via JSON.parse, push it inside an empty array, then assign it to the attribute.
variables = []
variable = "{\"token\": \"a\", \"value\": 1, \"default_value\": 1}"
variable.class #String
parsed_variable = JSON.parse(variable) #{"token"=>"a", "value"=>1, "default_value"=>1}
parsed_variable.class #Hash
variables.push parsed_variable
I want to get values from a JSON array which I can assign to a table column, however one of the values I need is in an array within an array, within an array. For example, an array of employees and for each employee there is an array of departments which contains an array of floors.
{"employees": [{"name": "bob", "age": 20, "department": ["location": "head office", "floors":["ground", "basement"]], "grade": "supervisor"}]}
The name and age display as expected but I am unsure how to get the floors. I have tried several ways but I am unable to get the "floors". This is my latest attempt but it says invalid conversion of string to integer on the #employee[:department] line. Can someone advise the best way to get this value? Thanks
#employees.each do |i|
employee[:column1] = i[:name]
employee[:column2] = i[:age]
#employee[:department].each do |d|
employee[:column3] = d[:floors]
end
end
The problem is that you're iterating over #employee[:department] when I think you want to be iterating over i[:department].
You could do
#employees.values.flatten.each do |i|
employee[:column1] = i[:name]
employee[:column2] = i[:age]
i[:department].each do |d|
employee[:column3] = d[:floors]
end
end
You could give this a try:
#employees.each do |e|
employee[:column1] = e[:name]
employee[:column2] = e[:age]
employee[:column3] = e[:department].last[:floors]
end
There's no need to iterate since the employee[:column3] = d[:floors] will always end up with the last element of the array. So, you can just go ahead and use last.
Saves you a couple of lines and some typing.
I'm grabbing a JSON hash from my server and trying to read it in my ruby script. It seems to be returning the wrong value though, unless I'm just loosing my mind:
I'm calling this from a function:
def get_functions(serial_number)
response = HTTParty.get("#{get_host_url}/v1/devices/#{serial_number}.json")
data = response['scan_option']
return data
end
Here is the returned JSON:
{"can_scan"=>true, "can_brute_ssh"=>false, "can_brute_telnet"=>false, "can_brute_voip"=>false, "can_brute_smtp"=>false, "can_brute_pop3"=>false, "can_google_crawl"=>false, "can_scan_external_ip"=>false, "scan_ip_list"=>["10.10.10.1"], "exclude_ip_list"=>[]}
Which is then read into the following code:
data.each do |d|
#can_scan = d['can_scan']
# ....
end
However this is throwing an error:
no implicit conversion of String into Integer
{foo: :bar}.each do |d|
p d
d['meow']
end
# => [:foo, :bar]
#! TypeError: no implicit conversion of String into Integer
Hash#each yields a two element array ([key, value]). You then try to index that array with d["can_scan"], which fails as arrays can only be indexed with integers.
Instead, directly access the value - data['can_scan'].
I you mean that data in your third snippet (where you call data.each) is the hash mentioned just above it, indeed that would be troublesome. Calling each on a hash will itterate over its key, value pairs, giving you an array in the block var d of the data.each (with a [key, value] pair in it).
You might just want to call data['can_scan'].
Note that the return at the end of your method defenition is not needed in Ruby. You can just do:
def get_functions(serial_number)
response = HTTParty.get("#{get_host_url}/v1/devices/#{serial_number}.json")
response["scan_option"]
end
I have a pluck that is turned into a hash and stored in a variable
#keys_values_hash = Hash[CategoryItemValue.where(category_item_id: #category_item.id).pluck(:key, :value)]
If 2 records have the same :key name then only the most recent record is used and they aren't both added to the hash. But if they have the same value and different keys both are added to the hash.
This also occurs if I swap :key and :value around (.pluck(:value, :key)). If they have now the same value it only uses the most recent one and stores that in the hash. But having the same key is now fine.
I'm not sure of this is being caused by pluck or from being sorted in a hash. I'm leaning towards pluck being the culprit.
What is causing this and how can I stop it from happening. I don't want data being skipped if it has the same key name as another record.
I'm not sure why you need convert pluck result into a Hash, because it was an Array original.
Like you have three CategoryItemValue like below:
id, key, value
1, foo, bar1
2, foo, bar2
3, baz, bar3
when you pluck them directly, you will get a array like:
[ ['foo', 'bar1'], ['foo', 'bar2'], ['baz', 'bar3'] ]
but when you convert it into a hash, you will get:
{'foo' => 'bar2', 'baz' => 'bar3' }
because new hash value will override the old one if key ( foo in the example above) exists.
Or you could try Enumerable#group_by method:
CategoryItemValue.where(...).pluck(:key, :value).group_by { |arr| arr[0] }
I am new to Ruby, and I am having some problems with hashes.
I have XML returned from the YouTube API that I converted into a hash. Here is the hash returned by Hash.from_xml(): http://pastebin.com/9xxE6iXU
I am trying to grab specific elements from the hash for each result, such as the title, link, author, etc. Whenever I try to loop through the hash or grab a specific element, I receive a "can't convert String into Integer" error.
Here is the code I am using for the loop:
#data["feed"]["entry"]["title"].each do |key, value|
"<p>"+key+" "+value+"</p>"
end
I have also tried grabbing specific elements, such as #data["feed"]["entry"]["title"][0].
How do I loop through the hash and grab specific elements out?
That's happening because #data["feed"]["entry"] is array of hashes:
puts #data["feed"]["entry"].class # => Array
Each element-hash inside this array has "id", "category", "title" etc. values.
For grabbing each title try to use following snippet:
#data["feed"]["entry"].each do |entry|
puts entry["title"]
end
# => "TABE test adult basic education"
"WhatCollegesHopeYouWon'tFindOutAboutACTSATTestPrep..."
....