Joining the values of keys in array - Ruby - ruby-on-rails

I am noob in ruby and have an array of hashes as per below. Say my array is #fruits_list:
[
{:key_1=>15, :key_2=>"Apple"},
{:key_1=>16, :key_2 =>"Orange"},
{:key_1=>17, :key_2 =>"Cherry"}
]
What I am looking for is to join the names of the fruits. I did #fruits_list[:key_2].join('|')
and I am getting the error as "TypeError:no implicit conversion of Symbol into Integer"
Please suggest.

Use Array#collect first to collect names of fruits, then join them with a pipe | using Array#join
#fruits_list = [
{:key_1=>15, :key_2=>"Apple"},
{:key_1=>16, :key_2 =>"Orange"},
{:key_1=>17, :key_2 =>"Cherry"}
]
#fruits_list.collect { |hsh| hsh[:key_2] }.join("|")
# => "Apple|Orange|Cherry"
#fruits_list is an array of hash(s). Elements of an array can be accessed via the integer indexes only. But you tried to access it with a symbol :key_2, thus it raised an error as "TypeError:no implicit conversion of Symbol into Integer".

Related

Query jsonb array for integer member

Background: We use PaperTrail to keep the history of our changing models. Now I want to query for a Item, which belonged to a certain customer. PaperTrail optionally stores the object_changes and I need to query this field to understand, when something was created with this ID or changed to this ID.
My table looks simplified like this:
item_type | object_changes
----------|----------------------------------------------------------
"Item" | {"customer_id": [null, 5], "other": [null, "change"]}
"Item" | {"customer_id": [4, 5], "other": ["unrelated", "change"]}
"Item" | {"customer_id": [5, 6], "other": ["asht", "asht"]}
How do I query for elements changed from or to ID 5 (so all rows above)? I tried:
SELECT * FROM versions WHERE object_changes->'customer_id' ? 5;
Which got me:
ERROR: operator does not exist: jsonb ? integer
LINE 1: ...T * FROM versions WHERE object_changes->'customer_id' ? 5;
^
HINT: No operator matches the given name and argument type(s).
You might need to add explicit type casts.
For jsonb the contains operator #> does what you ask for:
Get all rows where the number 5 is an element of the "customer_id" array:
SELECT *
FROM versions
WHERE object_changes->'customer_id' #> '5';
The #> operator expects jsonb as right operand - or a string literal that is valid for jsonb (while ? expects text). The numeric literal without single quotes you provided in your example (5) cannot be coerced to jsonb (nor text), it defaults to integer. Hence the error message. Related:
No function matches the given name and argument types
PostgreSQL ERROR: function to_tsvector(character varying, unknown) does not exist
This can be supported with different index styles. For my query suggested above, use an expression index (specialized, small and fast):
CREATE INDEX versions_object_changes_customer_id_gin_idx ON versions
USING gin ((object_changes->'customer_id'));
This alternative query works, too:
SELECT * FROM versions WHERE object_changes #> '{"customer_id": [5]}';
And can be supported with a general index (more versatile, bigger, slower):
CREATE INDEX versions_object_changes_gin_idx ON versions
USING gin (object_changes jsonb_path_ops);
Related:
Index for finding an element in a JSON array
Query for array elements inside JSON type
According to the manual, the operator ? searches for any top-level key within the JSON value. Testing indicates that strings in arrays are considered "top-level keys", but numbers are not (keys have to be strings after all). So while this query would work:
SELECT * FROM versions WHERE object_changes->'other' ? 'asht';
Your query looking for a number in an array will not (even when you quote the input string literal properly). It would only find the (quoted!) string "5", classified as key, but not the (unquoted) number 5, classified as value.
Aside: Standard JSON only knows 4 primitives: string, number, boolean and null. There is no integer primitive (even if I have heard of software adding that), integer is a just a subset of number, which is implemented as numeric in Postgres:
https://www.postgresql.org/docs/current/static/datatype-json.html#JSON-TYPE-MAPPING-TABLE
So your question title is slightly misleading as there are no "integer" members, strictly speaking.
Use a lateral join and the jsonb_array_elements_text function to process each row's object_changes:
SELECT DISTINCT v.* FROM versions v
JOIN LATERAL jsonb_array_elements_text(v.object_changes->'customer_id') ids ON TRUE
WHERE ids.value::int = 5;
The DISTINCT is only necessary if the customer_id you're looking for could appear multiple times in the array (if a different field changed but customer_id is tracked anyway).

Rails -PG::InvalidTextRepresentation: ERROR: malformed array literal

add_column :ssr_service_markets, :origin, :string, array: true, default: []
and when i want to
SSRService::Market.where(origin: "*", destination: "*").first
I have got
PG::InvalidTextRepresentation: ERROR: malformed array literal: "*"
LINE 1: ...service_markets" WHERE "ssr_service_markets"."origin" = '*'
DETAIL: Array value must start with "{" or dimension information
How to fix it?
In Postgres, to check if an array contains given element, you can use #> array operator.
Read more: Array Operators
:origin is an array field in your ssr_service_markets table, which means that it can contain multiple values.
In your example, assuming that destination is an array field as well, you could try to do the searching this way:
Model.where(["origin #> ? AND destination #> ?", '{*}', '{*}')"])
Remember about using curly braces when working with array values.
To write an array value as a literal constant, enclose the element
values within curly braces [...]

Ruby Calling Join on an Array versus String

I have a script that creates an array , then adds items to the array depending on certain circumstances. In most cases, the array will end up with several values inside of it. Occasionally, the array will only hold one value inside of it.
After preparing this array, I usually call .join(",") to create a comma-separated string of all the array values:
tags.join(",")
It works fine when the array has multiple values, but when it only has one value it throws an error:
NoMethodError: undefined method 'join' for "Whatever the array value": String
Any idea why this is? What is the easiest way to resolve this? Do I need to do an if statement to check if the variable is an array or string? Seems a bit silly...let me know if I am missing something here.
If obj is your object, you can write
[*obj].join
For example
arr = ["Fa", "bu", "lo", "us!"]
[*arr].join #=> "Fabulous!"
str = "Whoa!"
[*str].join #=> "Whoa!"
This works because
[*arr] #=> ["Fa", "bu", "lo", "us!"] == arr
[*str] #=> ["Whoa!"]
Similarly,
[*[1,2,3]].join #=> "123"
[*7].join #=> "7"
You can use join on an array as following way :
#array = ["this","is","join","method","example"]
#array.join(" ")
"this is join method example"
#array.join("_")
"this_is_join_method_example"
In the case of a single element (say, 'Hello'), you should be calling join on an array, not the string itself; for example, ['Hello'].join(",") rather than 'Hello'.join(","). Of course, if there's only one element join doesn't actually do anything, so you could just use a conditional if to skip it... but that's kinda ugly. Most of the time, I'd use the construction Array(tags).join(","). If passed a single string, that'll wrap it in an array; if passed an array, it's a noop, returning the array as-is.

rails 3.2 / postgres - how cast SUM() to return an integer not string?

Postgres aggregates like SUM(foo) return a string, even if foo is an integer.
How do I cast the sum to an integer so the resulting array of relations contains an integer not a string?
I tried to cast it using ::integer
Widget.select("SUM(points) as totalpoints::integer, agent, company")
but postgres throws error PG::Error: ERROR: syntax error at or near "::"
It seems there should be some way to tell rails - short of iterating through each returned array element - that the SUM of an INT is an INT?
This might just be the ActiveRecord adapter. I'm not entirely sure about that, though. But I think when you go down to the selection-level APIs it just returns strings, because that's what it actually reads back from the database. When you use the higher-level APIs I think it knows how to transform it back into the Ruby types because it's aware of your schema there.
I'm having trouble reproducing your query above, but I did the following:
Widget.connection.select_rows("SELECT sum(id) FROM widgets")
This returned:
[
[0] [
[0] "887"
]
]
And if I use a higher-level API:
2.0.0-p353 :034 > Widget.sum('id')
D, [2014-06-24T15:34:52.644852 #95176] DEBUG -- : (0.5ms) SELECT SUM("widgets."."id") AS sum_id FROM "widgets"
887
At this point it's giving me an Integer type in my console. So I suspect there's nothing wrong with Postgres, but that you may need to convert to native types yourself when you're using ActiveRecord select APIs.

How to get a value in a multidimensional array?

I am using Rails 3.1.0 and I would like to get a particular value from a multidimensional array. That is, I have the following
array = [ ['Text1', 's1'], ['Text2', 's2'], ['Text3', 's3'] ]
and, for example, I would like to search in the above array the string s3 so to get the corresponding value Text3. The same for s1 so to get the Text1 and for s2 so to get the Text2.
How can I make that?
For smallish arrays and infrequent lookups you can keep the array:
array = [ ['Text1', 's1'], ['Text2', 's2'], ['Text3', 's3'] ]
p array.rassoc('s3').first #=> 'Text3'

Resources