postgresql will not save array of empty arrays - ruby-on-rails

A user model has a goal field that is an array:
t.integer "goal", default: [], array: true
A valid entry for goal is [[],[],[]]. If I do
Match.create(goal: [[],[],[]])
I get an exception:
ActiveRecord::StatementInvalid: PG::InvalidTextRepresentation: ERROR: malformed array literal: "{{},{},{}}"
DETAIL: Unexpected "}" character.
I know that I can work around this by saving it as goal: [], but I lose information about the number of sub-arrays. Is there a way to fix this?

You’ve gone from trying to store an array of numbers to an array of fixed length which includes null values or even sub arrays. Therefore, a t.integer field is no good. You need to use a t.jsonb or t.json field.
See the Postgres docs on JSON datatypes

Related

Rails PostgreSql store multidimensional array

Is it possible to store a multidimensional array in a column.
I have tried the following and received the error below coming from creating the records column.
migration_file.rb
create_table :balance_sheets_details do |t|
t.string :headers, array: true, default: []
t.string :records, array: true, default: [[]]
t.timestamps
end
Raised error
PG::InvalidTextRepresentation: ERROR: malformed array literal: "{{}}"
From the docs on arrays (emphasis added):
The syntax for CREATE TABLE allows the exact size of arrays to be specified, for example:
CREATE TABLE tictactoe (
squares integer[3][3]
);
However, the current implementation ignores any supplied array size limits, i.e., the behavior is the same as for arrays of unspecified length.
The current implementation does not enforce the declared number of dimensions either. Arrays of a particular element type are all considered to be of the same type, regardless of size or number of dimensions. So, declaring the array size or number of dimensions in CREATE TABLE is simply documentation; it does not affect run-time behavior.
Thus, there isn't really a multidimensional array type. To fix your issue, just change the default from {{}} to {}.
This means a varchar[][] is the same type as a varchar[]:
db=# select pg_typeof(a), pg_typeof(b) from (values ('{{hello},{world}}'::varchar[][], '{foo}'::varchar[])) x(a, b);
pg_typeof | pg_typeof
---------------------+---------------------
character varying[] | character varying[]
(1 row)
You will still be able to store multidimensional data, though.
A one and two dimensional array are not the same:
db=# select '{{foo}}'::varchar[] = '{foo}'::varchar[];
?column?
----------
f
(1 row)

Rails migration - change column from varchar to jsonb

I'm trying to convert an existing column of type varchar to jsonb. The column contains strings like "black white orange" and want to convert it into jsonb format such that it will be converted to ["black", "white", "orange"].
class AlterColorsDatatype < ActiveRecord::Migration[5.0]
def change
change_column :quotes, :colors, :jsonb, default: '[]', using: 'colors::jsonb'
end
end
I expected to this to convert the column type to jsonb and the using: part would convert existing data to jsonb as well.
Instead, I get this error:
ActiveRecord::StatementInvalid: PG::InvalidTextRepresentation: ERROR: invalid input syntax for type json
DETAIL: Token "Indigo" is invalid.
CONTEXT: JSON data, line 1: Indigo
: ALTER TABLE "quotes" ALTER COLUMN "colors" TYPE jsonb USING colors::jsonb
I have tried other syntax but still ends up with the same error. I am thinking I would have to convert the whole column attribute by attribute with something like to_json but am not sure how to approach solving this error. After many google searches, other people with the same error did not seem to find a solution.
You can't get from a space-delimited string to a JSON with a simple cast. An easy way is to first break the string up to get a PostgreSQL array (text[]):
regexp_split_to_array(colors, E'\\s+')
and then convert that array to JSON:
to_json(regexp_split_to_array(colors, E'\\s+'))
You have to be careful with the quoting and backslashes to get that bit of SQL through Ruby and into the database so you'd say:
using: %q{to_json(regexp_split_to_array(colors, E'\\\\s+'))}
The %q{...} is like a single quoted string but lets you avoid having to escape the single quotes in the SQL string literal, then double up your backslashes to keep them from being interpreted by %q{...}.

Rails Amounts in Thousands Are Truncated

In my Rails 5 app, I read in a feed for products. In the JSON, when the price is over $1,000, it the JSON has a comma, like 1,000.
My code seems to be truncating it, so it's storing as 1 instead of 1,000.
All other fields are storing correctly. Can someone please tell me what I'm doing wrong?
In this example, the reg_price saves as 2, instead of 2590.
json sample (for reg_price field):
[
{
"reg_price": "2,590"
}
]
schema
create_table "products", force: :cascade do |t|
t.decimal "reg_price", precision: 10, scale: 2
end
model
response = open_url(url_string).to_s
products = JSON.parse(response)
products.each do |product|
product = Product.new(
reg_price: item['reg_price']
)
product.save
end
You are not doing anything wrong. Decimals don't work with comma separator. I'm not sure there is a nice way to fix the thing. But as an option you could define a virtual attribute:
def reg_price=(reg_price)
self[:reg_price] = reg_price.gsub(',', '')
end
The reason this is happening has nothing to do with Rails.
JSON is a pretty simple document structure and doesn't have any support for number separators. The values in your JSON document are strings.
When you receive a String as input and you want to store it as an Integer, you need to cast it to the appropriate type.
Ruby has built in support for this, and Rails is using it: "1".to_s #=> 1
The particular heuristic Ruby uses to convert a string to an integer is to take any number up to a non-numerical character and cast it as an integer. Commas are non-numeric, at least by default, in Ruby.
The solution is to convert the string value in your JSON to an integer using another method. You can do this any of these ways:
Cast the string to an integer before sending it to your ActiveRecord model.
Alter the string in such a way that the default Ruby casting will cast the string into the expected value.
Use a custom caster to handle the casting for this particular attribute (inside of ActiveRecord and ActiveModel).
The solution proposed by #Danil follows #2 above, and it has some shortcomings (as #tadman pointed out).
A more robust way of handling this without getting down in the mud is to use a library like Delocalize, which will automatically handle numeric string parsing and casting with consideration for separators used by the active locale. See this excellent answer by Benoit Garret for more information.

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 [...]

ActiveRecord: search by array field element inside another array

I have an object with an array field, called emails
user.emails = ['a#b.c','d#e.f']
I want to find all the users from a list of emails:
emails_to_find = ['a#b.c','x#y.z']
I tried running
User.where(emails: emails_to_find)
but I get
ActiveRecord::StatementInvalid: PG::InvalidTextRepresentation: ERROR: array value must start with "{" or dimension information
How do I do that? What the error means?
If the column is of type Array and in the migration that created it you have something like t.string 'emails', array: true, try using:
User.where("emails #> ARRAY[?]::varchar[]", ['a#b.c','d#e.f'])
User.where("'a#b.c' = ANY (emails)")

Resources