lib/json_apiable/params_parser.rb
# frozen_string_literal: true
module JsonApiable
class ParamsParser
class DataParams
def self.build(params, attributes, relationships)
params.require(:data).permit(:id, :type,
attributes: attributes,
relationships: relationships)
end
end
# Convert JsonAPI request body into Rails params hash
def self.parse_body_params(request, params, allowed_attributes, excluded_attributes,
allowed_relationships, excluded_relationships)
permitted_params = validate_data_params!(params,
allowed_attributes,
hashify(allowed_relationships))
attributes_hash = build_attributes_hash(permitted_params.dig(:attributes), excluded_attributes)
relationships_hash = build_relationships_hash(permitted_params.dig(:relationships), excluded_relationships, request)
attributes_hash.merge(relationships_hash)
rescue ArgumentError, ActionController::UnpermittedParameters
raise
rescue StandardError => e
raise Errors::MalformedRequestError, e.message
end
def self.validate_data_params!(params, attributes, relationships)
DataParams.build(params, attributes, relationships)
end
def self.build_attributes_hash(attributes, excluded_attributes)
attrs_hash = {}
attributes&.each do |key, value|
next if excluded_attributes&.include?(key.to_sym)
new_key = nested_attributes?(value) ? "#{key}_attributes" : key
attrs_hash[new_key] = value.is_a?(ActionController::Parameters) ? value.to_h : value
end
attrs_hash
end
def self.build_relationships_hash(relationships, excluded_relationships, request)
attr_hash = {}
ids_array = []
ids_key = nil
relationships&.each_pair do |key, data_hash|
next if excluded_relationships&.include?(key.to_sym)
if ActiveSupport::Inflector.pluralize(key) == key
new_key = "#{key}_attributes"
new_value = build_relationship_attribute_hash(data_hash)
ids_key = "#{ActiveSupport::Inflector.singularize(key)}_ids"
ids_array = data_hash['data']&.map { |h| h['id'] }
else
new_key = "#{key}_id"
new_value = data_hash['data']['id']
end
attr_hash[new_key] = new_value
# ids array is needed when creating a new AR object which accepts_nested_attributes for existing AR object(s)
# as in the case of a new Message which accepts existing attachments
# https://stackoverflow.com/a/25943832/1983833
attr_hash[ids_key] = ids_array if ids_array.present? && (request.post? || request.patch?)
end
attr_hash
end
def self.build_relationship_attribute_hash(data_hash)
attr_hash = {}
data_hash['data'].each_with_index do |arr_item, i|
item_hash = { 'id' => arr_item['id'], '_destroy' => 'false' }
attr_hash[i.to_s] = item_hash
end
attr_hash
end
def self.hashify(allowed_relationships)
allowed_relationships.map { |rel| { rel => {} } }
end
def self.nested_attributes?(value)
value.is_a?(ActionController::Parameters) || value.is_a?(Array)
end
end
end