rails 4, postgres and arrays - ruby-on-rails

I've been following a few guides surrounding storing arrays with rails 4 and postgres, but I keep getting snagged with this error:
PG::DatatypeMismatch: ERROR: column "run_times" is of type character varying[] but expression is of type character varying at character 35
HINT: You will need to rewrite or cast the expression.
Here is the SQL that is being executed by rails:
UPDATE "models" SET "run_times" = $1, "updated_at" = $2 WHERE "models"."id" = $3 [["run_times", "{0,2}"], ["updated_at", "2015-07-07 17:44:47.480675"], ["id", 1]]
Which is generated by the controller using (as an example):
#model.run_times = ["0", "2"]
Is there something I'm missing?
--
Interestingly enough, this seems to work in rails console:
#model = Model.find(1)
#model.run_times = ["123", "456"]
#model.save
--
Here's the migration I used:
class AddRunTimesToModel < ActiveRecord::Migration
def change
add_column :models, :run_times, :text, array: true, default: '{}'
end
end

Since you are passing in strings, just to double check is your run_times column set like this in your schema for that model's table?
t.string :run_times, array: true, default: '{}'

I believe that the usual advice is to define string-based arrays as text, not string, in PostgreSQL. I would "down" the migration, and change the type to a text array.

Alright so here's what I needed to do:
instead of permit(:run_times) it needed to be permit(run_times: [])
type in rails migration is string, not text
restart the server after migrations

Related

typeerror: no implicit conversion of hash into string when calling model.save

I am using
ruby version 2.2.6
rails version 4.1.16
postgresql 11.10
gem json-1.8.6
Currently I am getting the error "typeerror: no implicit conversion of hash into string" when I am trying to save a model. This only happen for param that is using json datatype.
The database schema for the table:
create_table "jasons", force: :cascade do |t|
t.string "name"
t.json "description"
end
I already entered accessors for using json in the Jason.rb model like this.
class Jason < ApplicationRecord
:description, accessors: [:key1, :key2, :watapak]
end
and also whitelisted the params in the controller.
def jason_params
params.require(:jason).permit(:name, :description, :key1, :key2, :watapak)
end
and I can call new on the model successfully with
jason = Jason.new(name: "Jason John", key1: "abcd", key2: "efgh")
=> #<Jason id: nil, name: "Jason John", description: {"key1"=>"abcd", "ke...
then when I'm trying to call jason.save, it return's the error "typeerror: no implicit conversion of hash into string".
I tested the code on another environment, using the latest ruby 3.0 and rails 6.1 and everything works great without any error.
One thing that I notice is that the SQL command on both environment is different.
Rails 6.1
Jason Create (0.5ms) INSERT INTO "jasons" ("name", "description") VALUES (?, ?) [["name", "Jason John"], ["description", "\"--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\\nkey1: abcd\\nkey2: efgh\\n\""]]
Rails 4.1.6
SQL (6.6ms) INSERT INTO "device_models" ("created_at", “name", "description", “updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["“created at", "2021-08-09 07:13:04.157826"], ["name", “Jason John"], ["description", "{\"key1\":\"abcd\",\"key2\":\"efgh\"}"], ["updated at", "2021-08-09 07:13:04.157826"]]
The full error is as in this image. Also it differs a bit with the question but the root of the problem is the same.
Do tell me if more information is needed. Also sorry if my question is gibberish as this is my first time posting question, I don't really know how to structure it to make it simpler to understand.
Please help T.T
I suspect that you may have intend to write store :description, accessors: [:key1, :key2, :watapak] which is a hack to store serialized data in a VARCHAR/TEXT columns and does not work with native JSON types. Both serialize and store are practically obsolete.
NOTE: If you are using structured database data types (e.g. PostgreSQL
hstore/json, or MySQL 5.7+ json) there is no need for the
serialization provided by .store. Simply use .store_accessor instead
to generate the accessor methods. Be aware that these columns use a
string keyed hash and do not allow access using a symbol.
class Jason < ApplicationRecord
store_accessor :description, :key1, :key2, :watapak
end

PostgreSQL can't cast type json to character varying[]

In my Rails 5.0.5 app I need to convert json column into string, array: true.
The values in my json columns are like:
[ "200px-RR5219-0015R.png", "2017_03_25_2235.doc", "137555.jpg" ]
I tried this migration:
class ChangeTaskAttachmentsTypeToString < ActiveRecord::Migration[5.0]
def change
change_column :tasks, :attachments, :string, array: true
end
end
and got this error:
ActiveRecord::StatementInvalid: PG::DatatypeMismatch: ERROR: column "attachments" cannot be cast automatically to type character varying[]
HINT: You might need to specify "USING attachments::character varying[]".
: ALTER TABLE "tasks" ALTER COLUMN "attachments" TYPE character varying[]
Then I edited migration:
class ChangeTaskAttachmentsTypeToString < ActiveRecord::Migration[5.0]
def change
change_column :tasks, :attachments, 'character varying[] USING attachments::character varying[]'
end
end
And finally got this error:
PG::CannotCoerce: ERROR: cannot cast type json to character varying[]
: ALTER TABLE "tasks" ALTER COLUMN "attachments" TYPE character varying[] USING attachments::character varying[]
How can I do this migration?
I guess the array elements are filenames. If so, then you can remove all the characters []" and spaces and split the result to array, like this:
with my_table(attachments) as (
values
('[ "200px-RR5219-0015R.png", "2017_03_25_2235.doc", "137555.jpg" ]'::json)
)
select string_to_array(translate(attachments::text, '[] "', ''), ',')::varchar[]
from my_table;
string_to_array
---------------------------------------------------------
{200px-RR5219-0015R.png,2017_03_25_2235.doc,137555.jpg}
(1 row)
so use:
... USING string_to_array(translate(attachments::text, '[] "', ''), ',')::varchar[]
A more formal (and general) solution would require a custom function, e.g.:
create or replace function json_to_text_array(json)
returns text[] language sql immutable as $$
select array_agg(value)
from json_array_elements_text($1)
$$;
that could be used in
alter table tasks alter column attachments type text[]
using json_to_text_array(attachments);
Note, I have used text[] as a more natural choice for Postgres but you can replace it with varchar[] if it matters.
Test it in Db<>fiddle.

Matching array values in PostgreSQL with ActiveRecord

In my Rails 4 app I have a goal to see all contacts, where field visible_to in contacts table equal to 1. My visible_to is :integer, array: true.
However, I get the following exception:
PG::UndefinedFunction: ERROR: operator does not exist: integer[] = integer
LINE 1: ....* FROM "contacts" WHERE "contacts"."visible_to" IN (1) OR...
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.: SELECT "contacts".* FROM "contacts" WHERE "contacts"."visible_to" IN (1) ORDER BY created_at DESC
I searched for answers and as far as I see there is an issue with a type of visible_to. However, I couldn't find the solution. I also tried to get benefit from casts hint, but in vain.
My migration:
class AddVisibleToToContacts < ActiveRecord::Migration
def change
add_column :contacts, :visible_to, :integer, array: true, default: [], using: 'gin'
end
end
Relevant variable from Controller:
#contacts_all_user_sorted = Contact.all.where(visible_to: [1]).order("created_at DESC")
From these two websites:
http://blog.relatabase.com/rails-postgres-arrays
http://adamsanderson.github.io/railsconf_2013/#11
It seems that this syntax should work:
#contacts_all_user_sorted = Contact.all.where("visible_to #> ARRAY[?]", [1])
Does it work?
P.S: As #Quertie pointed out in the comments, you may want to cast the value in the case of a String array, by replacing ARRAY[?] by ARRAY[?]::varchar[]
your migration seems pretty straight forward and correct.
can you please try this:
Contact.where('visible_to IN ?', ['1', '2'])

Dynamic Forms with HStore and rails 4

I was following this railscast, and finished the tutorial. Everything was working fine. Then I decided to use hstore instead of a serialized hash, and after setting up hstore, ran into a error:
PG::Error: ERROR: Syntax error near '!' at position 4 : INSERT INTO "products" ("product_type_id", "created_at", "properties", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"
I googled, and found a similar SO question, but I'm using Rails 4, which supposedly doesn't need to use that gem anymore.
Here's my code:
The relevant portion of my form.html.haml looks like this
= f.fields_for :properties, OpenStruct.new(#product.properties) do |builder|
- #product.product_type.products.each do |product|
= render "products/fields/#{product.field_type}", field: field, f: builder
My Product model looks like this:
class Product < ActiveRecord::Base
belongs_to :product_type
serialize :properties
end
I can post more code if it will help. Thanks!
The Rails4 PostgreSQL driver for ActiveRecord is supposed to have native support for PostgreSQL's hstore type so you shouldn't need to use serialize at all. Try ditching the serialize.
BTW, a ! will appear in a YAML string when you attempt to serialize some objects to YAML:
"--- !ruby/object:SomeClassName ..."
and that ! could cause some problems if PostgreSQL was expecting to see an hstore string.

Rails Migration Error w/ Postgres when pushing to Heroku

I'm trying to perform the following up migration to change the column "number" in the "tweet" model's table
class ChangeDataTypeForTweetsNumber < ActiveRecord::Migration
def up
change_column :tweets do |t|
t.change :number, :integer
end
end
def down
change_table :tweets do |t|
t.change :number, :string
end
end
end
Upon performing the the following up migration to heroku....
heroku rake db:migrate:up VERSION=20120925211232
I get the following error
PG::Error: ERROR: column "number" cannot be cast to type integer
: ALTER TABLE "tweets" ALTER COLUMN "number" TYPE integer
Any thoughts you have would be very much appreciated.
Thanks everyone.
Same as above but a little bit more concise:
change_column :yourtable, :column_to_change, 'integer USING CAST("column_to_change" AS integer)'
From the fine manual:
[ALTER TABLE ... ALTER COLUMN ...]
The optional USING clause specifies how to compute the new column value from the old; if omitted, the default conversion is the same as an assignment cast from old data type to new. A USING clause must be provided if there is no implicit or assignment cast from old to new type.
There is no implicit conversion from varchar to int in PostgreSQL so it complains that column "number" cannot be cast to type integer and the ALTER TABLE fails. You need to tell PostgreSQL how to convert the old strings to numbers to match the new column type and that means that you need to get a USING clause into your ALTER TABLE. I don't know of any way to make Rails do that for you but you can do it by hand easily enough:
def up
connection.execute(%q{
alter table tweets
alter column number
type integer using cast(number as integer)
})
end
You'll want to watch out for values that can't be cast to integers, PostgreSQL will let you know if there are problems and you'll have to fix them before the migration will succeed.
Your existing down-migration should be fine, converting integer to varchar should be handled automatically.

Resources