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
CodePudding user response:
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.7json
) 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.