Dynamic Forms with HStore and rails 4 - ruby-on-rails

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.

Related

ActiveRecord erroneously escapes JSON string

I have a jsonb-type column called 'payloads' for my Tweet model in my Rails 6.1.1 app. I use a store to access various fields on this attribute directly:
class Tweet < ApplicationRecord
store :payload, accessors: [:lang, :text, :entities], coder: JSON
end
(Note that the part coder: JSON is necessary to change the serialization from the YAML default to JSON – otherwise you end up with YAML in your jsonb column.)
When I create a new tweet, I see that ActiveRecord erroneously escapes the JSON string during the insertion:
Tweet.create payload: {foo: 'bar'}
TRANSACTION (0.7ms) BEGIN
Tweet Create (2.0ms) INSERT INTO "tweets" ("payload", ...) VALUES ($1, ...) RETURNING "id" [["payload", "\"{\\\"foo\\\":\\\"bar\\\"}\""], ...]
I'm referring to the part "\"{\\\"foo\\\":\\\"bar\\\"}\"". It looks like it's been double escaped. This results in a sort of 'stringception' where a string within a string is stored in the payloads column, rendering postgres unable to perform any searches on fields using the arrow -> syntax. Postgres can't recognize this value as JSON. (Seemingly miraculously, however, Rails is able to deserialize the field properly on read operations.)
Another SO user has illustrated the issue here: https://dbfiddle.uk/gcwTQOUm
When I do not use store, I cannot reproduce the issue:
Tweet.create payload: {foo: 'bar'}
TRANSACTION (0.2ms) BEGIN
Tweet Create (1.6ms) INSERT INTO "tweets" ("payload", ...) VALUES ($1, ...) RETURNING "id" [["payload", "{\"foo\":\"bar\"}"], ...]
"{\"foo\":\"bar\"}" is the desired string.
This has led me to believe that I'm using store wrong.
I checked the docs:
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.
In other words, because I was already using a jsonb-type column, Rails already knew to serialize the hash – applying another serialization resulted in a double escape.
So, instead of doing:
store :payload, accessors: [:lang, :text, :entities], coder: JSON
I know do:
store_accessor :payload, :lang, :text, :entities
And it works.

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

rails rspec duplicate key value violates unique constraint after adding unique index

I am working on a rails app, and I added a table. On that table, I wanted an index so to add one, I made a migration for adding a unique index. My migration looks like
class AddIndexToTable < ActiveRecord::Migration
def change
add_index :table, :user_id, unique: true
end
end
everything seemed fine, but now when I run my specs against my migration changes I now get the error
ActiveRecord::RecordNotUnique:
PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_table_name_on_user_id"
DETAIL: Key (user_id)=(15) already exists.
: INSERT INTO "table_name" ("user_id", "day", "last_visited_on", "daily_rewards", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id"
I have tried dropping the DB's, re-migrating, re-setting the pk_sequence with ActiveRecord::Base.connection.reset_pk_sequence!(table_name) and other fixes I could find, but I am still stuck on the same error.
Could anyone help me understand why I keep getting this error, and how it can be fixed? I can post more code if needed
You've got either a factory or a test that's trying to create multiple table records for the same user. Either that's going to be obvious looking in the test that's causing this, or perhaps it's an integration test and you don't have your test environment set up properly to clean out the DB between test cases.
For me it was the error that was I allowing the id in the controller params and that I had and let! create ... run at every test. So it tried to create again with the same id.
Removing id from allowed params fixed it.

Replacing Ckeditor with ActionText in a Rails 6 app

So I replaced CKEditor with ActionText in my rails 6 application, the upgrade to rails 6 and installation of action text was smooth.
I want to ask how I can be able to migrate the data from my model attribute to the newly established action text association (well not exactly migrate, I want to be able to display the old data and even be able to edit/update it).
For example, I have a description attribute in my model that was used with CKEditor before, now I have changed that field to a rich_text field like this: has_rich_text :description
So now all references to description simply query its rich_text association.
If I wanted to do something like this in my view, how can I achieve that?
#model.description (display if rich_text data is present) || #model.description (or try this if rich_text data is blank, also display nothing if both is blank)
I'd like to achieve this for show, edit, update and delete actions. Any idea how to make this work?
Why not just migrate the Ckeditor columns to the the new action_text table?
class ConvertCkeditorToActionText < ActiveRecord::Migration[6.0]
def up
action_text_rich_text_statement = ActiveRecord::Base.connection.raw_connection.prepare('action_text_rich_text_statement', <<-SQL)
INSERT INTO action_text_rich_texts (
name, body, record_type, record_id, created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, $6)
SQL
Rails.application.eager_load!
transaction do
Post.all.each do |post|
ActiveRecord::Base.connection.raw_connection.exec_prepared(
'action_text_rich_text_statement', [
'body',
post.body,
"Post",
post.id,
post.created_at,
post.updated_at
])
end
end
end
def down
raise ActiveRecord::IrreversibleMigration
end
end

rails 4, postgres and arrays

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

Resources