I want to store an array of objects in JSONB column in PostgreSQL. I'm using Rails 5.2. I'm using a custom serializer that ensures the value assigned to a JSONB field is an Array not a Hash. And I'm getting an error when assigning something like [{a: 1}] to the field. Here is the code:
model:
class Printing
serialize :card_faces, CardFacesSerializer
end
serializer:
class CardFacesSerializer
include JSONBArraySerializer
def allowed_attributes
%i[name image]
end
end
serializer concern:
module JSONBArraySerializer
extend ActiveSupport::Concern
def initialize(data)
return [] if data.blank?
if data.is_a?(String)
json = Oj.load(data, symbol_keys: true)
end
raise ArgumentError, "#{json} must be [{},{}], not {}" if json.is_a?(Hash)
# Will only set the properties that are allowed
json.map do |hash|
hash.slice(self.allowed_attributes)
end
end
class_methods do
def load(json)
return [] if json.blank?
self.new(json)
end
def dump(obj)
# Make sure the type is right.
if obj.is_a?(self)
obj.to_json
else
raise StandardError, "Expected #{self}, got #{obj.class}"
end
end
end
end
When evaluating:
pr = Printing.first
pr.card_faces = [{hay: 12}]
pr.save!
I get an error:
StandardError: Expected CardFacesSerializer, got Array
I don't think it's clear for me how dump/load work. Why dump is being called during save? How can I fix my code to work properly?
UPDATE
I managed to make it work with this code of serializer concern:
module JSONBArraySerializer
extend ActiveSupport::Concern
class_methods do
def load(data)
return [] if data.blank?
if data.is_a?(String)
json = Oj.load(data, symbol_keys: true)
end
raise ArgumentError, "#{json} must be [{},{}], not {}" if json.is_a?(Hash)
# Will only set the properties that are allowed
json.map do |hash|
hash.slice(*allowed_attributes)
end
end
def dump(obj)
# Make sure the type is right.
if obj.is_a?(Array)
obj.to_json
else
raise ArgumentError, "Expected Array, got #{obj.class}"
end
end
end
end
loadis called when AR object is instantiating anddumpwhen saved. Sodumpshould await forArray(desired column value) notself(Serializer).JSONBcolumn, you can just check column value by custom validation. api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/…array, why use aJSONBcolumn and not anarraycolumn? Also, why use aconcernand why not include theconcerncode in yourserializer?[{},{}]and I don't want to serialize objects into strings as it's costly and it won't be searchable and indexable.activerecord_json_validator(or underlying gemjson-schema) for extended validation of yourJSONBcolumn.