Skip to content

Commit 10b4850

Browse files
committed
begin playing with using JSON API for deserialization
extract key_transfor chaneg to case_transform gem fix tests pass add empty array test upgrade to jsonapi beta5, improve tests to be valid resource requests use github links in gemfile for now, due to unreleased code use github links in gemfile for now, due to unreleased code some clenaup deserialization would work now if it didn't depend on the existance of certain objects progress on implementing new deserializer -- just have some behavior differences with relationships tests pass address rubocop issue -- update dependencies... wonder if we need to move @beauby's jsonapi gems to rails-api, in order to faster turnaround time reduce to minimum dependencies. Now we need beauby to merge my jsonapi-rails branch :-)
1 parent 0422a1e commit 10b4850

File tree

6 files changed

+34
-373
lines changed

6 files changed

+34
-373
lines changed

Gemfile

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ eval_gemfile local_gemfile if File.readable?(local_gemfile)
77
# Specify your gem's dependencies in active_model_serializers.gemspec
88
gemspec
99

10+
gem 'jsonapi-rails', github: 'beauby/jsonapi-rails', branch: 'initial-implementation'
11+
1012
version = ENV['RAILS_VERSION'] || '4.2'
1113

1214
if version == 'master'

active_model_serializers.gemspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Gem::Specification.new do |spec|
4242
# 'minitest'
4343
# 'thread_safe'
4444

45-
spec.add_runtime_dependency 'jsonapi', '0.1.1.beta2'
45+
spec.add_runtime_dependency 'jsonapi-deserializable', '~> 0.1.1.beta3'
4646
spec.add_runtime_dependency 'case_transform', '>= 0.2'
4747

4848
spec.add_development_dependency 'activerecord', rails_versions

lib/active_model_serializers/adapter/json_api/deserialization.rb

+28-120
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1+
require 'jsonapi/parser'
2+
require 'jsonapi/rails'
3+
14
module ActiveModelSerializers
25
module Adapter
36
class JsonApi
47
# NOTE(Experimental):
58
# This is an experimental feature. Both the interface and internals could be subject
69
# to changes.
710
module Deserialization
8-
InvalidDocument = Class.new(ArgumentError)
9-
1011
module_function
1112

1213
# Transform a JSON API document, containing a single data object,
@@ -73,140 +74,47 @@ module Deserialization
7374
# # }
7475
#
7576
def parse!(document, options = {})
76-
parse(document, options) do |invalid_payload, reason|
77-
fail InvalidDocument, "Invalid payload (#{reason}): #{invalid_payload}"
77+
parse(document, options) do |exception|
78+
fail exception
7879
end
7980
end
8081

8182
# Same as parse!, but returns an empty hash instead of raising InvalidDocument
8283
# on invalid payloads.
8384
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
138-
end
85+
# TODO: change to jsonapi-ralis to have default conversion to flat hashes
86+
result = JSONAPI::Deserializable::ActiveRecord.new(document, options: options).to_hash
87+
result = apply_options(result, options)
88+
result
89+
rescue JSONAPI::Parser::InvalidDocument => e
90+
return {} unless block_given?
91+
yield e
13992
end
140-
# rubocop:enable Metrics/CyclomaticComplexity
141-
142-
# @api private
143-
def filter_fields(fields, options)
144-
if (only = options[:only])
145-
fields.slice!(*Array(only).map(&:to_s))
146-
elsif (except = options[:except])
147-
fields.except!(*Array(except).map(&:to_s))
148-
end
149-
end
150-
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
19493

94+
def apply_options(hash, options)
95+
hash = transform_keys(hash, options) if options[:key_transform]
96+
hash = hash.deep_symbolize_keys
97+
hash = rename_fields(hash, options)
19598
hash
19699
end
197100

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-
101+
# TODO: transform the keys after parsing
205102
# @api private
206103
def transform_keys(hash, options)
207104
transform = options[:key_transform] || :underscore
208105
CaseTransform.send(transform, hash)
209106
end
107+
108+
def rename_fields(hash, options)
109+
return hash unless options[:keys]
110+
111+
keys = options[:keys]
112+
hash.each_with_object({}) do |(k, v), h|
113+
k = keys.fetch(k, k)
114+
h[k] = v
115+
h
116+
end
117+
end
210118
end
211119
end
212120
end

test/action_controller/json_api/deserialization_test.rb

-112
This file was deleted.

test/action_controller/json_api/transform_test.rb

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
module ActionController
44
module Serialization
55
class JsonApi
6-
class KeyTransformTest < ActionController::TestCase
7-
class KeyTransformTestController < ActionController::Base
6+
class CaseTransformTest < ActionController::TestCase
7+
class CaseTransformTestController < ActionController::Base
88
class Post < ::Model
99
attributes :title, :body, :publish_at
1010
associations :author, :top_comments
@@ -77,7 +77,7 @@ def render_resource_with_transform_with_global_config
7777
end
7878
end
7979

80-
tests KeyTransformTestController
80+
tests CaseTransformTestController
8181

8282
def test_render_resource_with_transform
8383
get :render_resource_with_transform

0 commit comments

Comments
 (0)