In my rails app I send data to the react front end in JSON API standard format via the jsonapi-serializer
gem. This works well for data coming from our database, but when we have failing form data, which is sent in the form of rails nested resources, sometimes this data needs to be returned with errors before the records are actually saved.
When new model data has failing validations, I need those errors to come back to my front end and have the associations intact. I've been trying to run them through jsonapi-serializer
as well, but the records that don't have ids, don't make it into the relationships
key. The problem is that failing new records don't have an id, and they come back to the front end un-associated.
My only solution to this so far is to manually shim fake temporary ids in there. I'm not sure if I'm missing something obvious. That id is removed on the front end for re-submission. My solution isn't perfect and has limitations and issues.
Is there a built-in way to do this or am I stuck with my own implementation? Is there some kind of alternate key I could use for this association?
For what it's worth, this is my implementation so far, just to clarify what I'm doing. It's far from perfect and a lot of code for what seems like it should be a problem with a built-in solution. It's based on trying to iterate over the object tree being serialized and seeing if a "fake" id is needed. This still chokes on some things that can be passed via include: []
class BaseSerializer
module ShimIdConcerns
extend ActiveSupport::Concern
private
# Very Important! Controls relationship of json api assocs in front end
# The id that is returned to the front end needs to be smarter than simply
# the actual id or nil. The id is used to associate jsonapi objects to their
# main resource whether they have an id yet or not. Sometimes these objects
# are not persisted, but have errors and need to be associated properly in the UI.
# Works in conjuncton with attribute :is_persisted
def shim_id_on_failing_new_associations(resource, options)
shim_id_on_failing_new_associations_recursive resource, options[:include].to_a
end
# dot_assocs: expects assocs in 'packages.package_services.service' format
def shim_id_on_failing_new_associations_recursive(resource, dot_assocs)
assocs = simplify_assocs(dot_assocs)
assocs.each do |assoc_path|
next if assoc_path.blank?
segments = assoc_path.split('.')
method = segments.shift
next unless resource.respond_to?(method)
assoc = resource.send(method)
if assoc.is_a? ActiveRecord::Base
shim_id(resource)
shim_id_on_failing_new_associations_recursive assoc, segments.join('.')
elsif assoc.is_a?(ActiveRecord::Associations::CollectionProxy) || assoc.is_a?(Array)
assoc.each do |one_of_many|
shim_id_on_failing_new_associations_recursive one_of_many, segments.join('.')
end
end
end
end
# Gives a temp id on failing or new resources so they can be associated in react
# in jsonapi/react. Ensure this id is not re-submitted, however
def shim_id(resource)
resource.id = rand(1000000000000) if resource.id.nil? && resource.new_record?
end
# turns
# [ :reservation, :'reservation.client', :'reservation.client.credit_card' ]
# into
# [ "reservation.client.credit_card" ]
# to avoid duplicate processing
def simplify_assocs(dot_assocs)
ap dot_assocs
all = [dot_assocs].flatten.map(&:to_s)
simp = []
# yes quadratic, but will be little data
all.each do |needle|
matches = 0
all.each do |hay|
matches += 1 if hay.index(needle) === 0
end
simp << needle if matches === 1
end
simp
end
end
end