Skip to content

Commit 59f8627

Browse files
committed
Try jsonapi-deserialization
1 parent af5e9d6 commit 59f8627

File tree

2 files changed

+28
-114
lines changed

2 files changed

+28
-114
lines changed

active_model_serializers.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Gem::Specification.new do |spec|
4343
# 'thread_safe'
4444

4545
spec.add_runtime_dependency 'jsonapi-renderer', ['>= 0.1.1.beta1', '< 0.2']
46+
spec.add_runtime_dependency 'jsonapi-deserializable', '~> 0.1.1'
4647
spec.add_runtime_dependency 'case_transform', '>= 0.2'
4748

4849
spec.add_development_dependency 'activerecord', rails_versions

lib/active_model_serializers/adapter/json_api/deserialization.rb

Lines changed: 27 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1+
require 'jsonapi/deserializable/resource'
12
module ActiveModelSerializers
23
module Adapter
34
class JsonApi
4-
# NOTE(Experimental):
5-
# This is an experimental feature. Both the interface and internals could be subject
6-
# to changes.
75
module Deserialization
86
InvalidDocument = Class.new(ArgumentError)
97

@@ -72,72 +70,41 @@ module Deserialization
7270
# # author_type: 'people'
7371
# # }
7472
#
73+
# @example
74+
# def deserialized_params
75+
# ActionController::Parameters.new(
76+
# ActiveModelSerializers::Deserialization.jsonapi_parse!(
77+
# request.request_parameters # no need for request.query_parameters, request.path_parameters
78+
# )
79+
# )
80+
# end
7581
def parse!(document, options = {})
76-
parse(document, options) do |invalid_payload, reason|
77-
fail InvalidDocument, "Invalid payload (#{reason}): #{invalid_payload}"
82+
document = document.dup.permit!.to_h if document.is_a?(ActionController::Parameters)
83+
primary_data = document.slice('data')
84+
hash = JSONAPI::Deserializable::Resource.call(primary_data)
85+
id_keys = hash.keys.select { |key| key.to_s.end_with?('_id') }.map { |key| key.to_s.sub(/_id\z/, '') }
86+
ids_keys = hash.keys.select { |key| key.to_s.end_with?('_ids') }.map { |key| key.to_s.sub(/_ids\z/, '') }
87+
excluded_keys = id_keys.map { |key| "#{key}_type" } .concat(ids_keys.map { |key| "#{key}_types" })
88+
excluded_keys << :type
89+
excluded_keys.map!(&:to_sym)
90+
hash = hash.except(*excluded_keys)
91+
filter_fields(hash, options)
92+
transform_keys(hash, options)
93+
rescue NoMethodError, JSONAPI::Parser::InvalidDocument => e
94+
if block_given?
95+
yield(e)
96+
else
97+
raise InvalidDocument, "Invalid payload (#{e.message}): #{primary_data}"
7898
end
7999
end
80100

81101
# Same as parse!, but returns an empty hash instead of raising InvalidDocument
82102
# on invalid payloads.
83103
def parse(document, options = {})
84-
document = document.dup.permit!.to_h if document.is_a?(ActionController::Parameters)
85-
86-
validate_payload(document) do |invalid_document, reason|
87-
yield invalid_document, reason if block_given?
88-
return {}
89-
end
90-
91-
primary_data = document['data']
92-
attributes = primary_data['attributes'] || {}
93-
attributes['id'] = primary_data['id'] if primary_data['id']
94-
relationships = primary_data['relationships'] || {}
95-
96-
filter_fields(attributes, options)
97-
filter_fields(relationships, options)
98-
99-
hash = {}
100-
hash.merge!(parse_attributes(attributes, options))
101-
hash.merge!(parse_relationships(relationships, options))
102-
103-
hash
104-
end
105-
106-
# Checks whether a payload is compliant with the JSON API spec.
107-
#
108-
# @api private
109-
# rubocop:disable Metrics/CyclomaticComplexity
110-
def validate_payload(payload)
111-
unless payload.is_a?(Hash)
112-
yield payload, 'Expected hash'
113-
return
114-
end
115-
116-
primary_data = payload['data']
117-
unless primary_data.is_a?(Hash)
118-
yield payload, { data: 'Expected hash' }
119-
return
120-
end
121-
122-
attributes = primary_data['attributes'] || {}
123-
unless attributes.is_a?(Hash)
124-
yield payload, { data: { attributes: 'Expected hash or nil' } }
125-
return
126-
end
127-
128-
relationships = primary_data['relationships'] || {}
129-
unless relationships.is_a?(Hash)
130-
yield payload, { data: { relationships: 'Expected hash or nil' } }
131-
return
132-
end
133-
134-
relationships.each do |(key, value)|
135-
unless value.is_a?(Hash) && value.key?('data')
136-
yield payload, { data: { relationships: { key => 'Expected hash with :data key' } } }
137-
end
104+
parse!(document, options) do
105+
{}
138106
end
139107
end
140-
# rubocop:enable Metrics/CyclomaticComplexity
141108

142109
# @api private
143110
def filter_fields(fields, options)
@@ -148,60 +115,6 @@ def filter_fields(fields, options)
148115
end
149116
end
150117

151-
# @api private
152-
def field_key(field, options)
153-
(options[:keys] || {}).fetch(field.to_sym, field).to_sym
154-
end
155-
156-
# @api private
157-
def parse_attributes(attributes, options)
158-
transform_keys(attributes, options)
159-
.map { |(k, v)| { field_key(k, options) => v } }
160-
.reduce({}, :merge)
161-
end
162-
163-
# Given an association name, and a relationship data attribute, build a hash
164-
# mapping the corresponding ActiveRecord attribute to the corresponding value.
165-
#
166-
# @example
167-
# parse_relationship(:comments, [{ 'id' => '1', 'type' => 'comments' },
168-
# { 'id' => '2', 'type' => 'comments' }],
169-
# {})
170-
# # => { :comment_ids => ['1', '2'] }
171-
# parse_relationship(:author, { 'id' => '1', 'type' => 'users' }, {})
172-
# # => { :author_id => '1' }
173-
# parse_relationship(:author, nil, {})
174-
# # => { :author_id => nil }
175-
# @param [Symbol] assoc_name
176-
# @param [Hash] assoc_data
177-
# @param [Hash] options
178-
# @return [Hash{Symbol, Object}]
179-
#
180-
# @api private
181-
def parse_relationship(assoc_name, assoc_data, options)
182-
prefix_key = field_key(assoc_name, options).to_s.singularize
183-
hash =
184-
if assoc_data.is_a?(Array)
185-
{ "#{prefix_key}_ids".to_sym => assoc_data.map { |ri| ri['id'] } }
186-
else
187-
{ "#{prefix_key}_id".to_sym => assoc_data ? assoc_data['id'] : nil }
188-
end
189-
190-
polymorphic = (options[:polymorphic] || []).include?(assoc_name.to_sym)
191-
if polymorphic
192-
hash["#{prefix_key}_type".to_sym] = assoc_data.present? ? assoc_data['type'] : nil
193-
end
194-
195-
hash
196-
end
197-
198-
# @api private
199-
def parse_relationships(relationships, options)
200-
transform_keys(relationships, options)
201-
.map { |(k, v)| parse_relationship(k, v['data'], options) }
202-
.reduce({}, :merge)
203-
end
204-
205118
# @api private
206119
def transform_keys(hash, options)
207120
transform = options[:key_transform] || :underscore

0 commit comments

Comments
 (0)