Skip to content

Commit

Permalink
Move code into Deserialization module. Add support for whitelisting.
Browse files Browse the repository at this point in the history
  • Loading branch information
beauby committed Oct 25, 2015
1 parent d43f8c9 commit 42addfb
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 78 deletions.
36 changes: 1 addition & 35 deletions lib/active_model/serializer/adapter/json_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class JsonApi < Base
extend ActiveSupport::Autoload
autoload :PaginationLinks
autoload :FragmentCache
autoload :Deserialization

# TODO: if we like this abstraction and other API objects to it,
# then extract to its own file and require it.
Expand Down Expand Up @@ -41,41 +42,6 @@ def object
end
end

# Parse a Hash or ActionController::Parameters representing a JSON API document
# into an ActiveRecord-ready hash.
# NOTE(beauby): Currently this does not handle relationships with modified keys.
#
# @param [Hash|ActionController::Parameters] document
# @return [Hash] ActiveRecord-ready hash
#
def self.parse(document)
hash = {}

primary_data = document.fetch('data', {})
hash[:id] = primary_data['id'] if primary_data['id']

if primary_data['attributes']
primary_data['attributes'].each do |name, value|
hash[name.to_sym] = value
end
end

if primary_data['relationships']
primary_data['relationships'].each do |name, value|
data = value['data']
if data.is_a?(Array)
key = "#{name.singularize}_ids".to_sym
hash[key] = data.map { |ri| ri['id'] }
else
key = "#{name.singularize}_id".to_sym
hash[key] = data ? data['id'] : nil
end
end
end

hash
end

def initialize(serializer, options = {})
super
@include_tree = IncludeTree.from_include_args(options[:include])
Expand Down
73 changes: 73 additions & 0 deletions lib/active_model/serializer/adapter/json_api/deserialization.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
module ActiveModel
class Serializer
module Adapter
class JsonApi
module Deserialization
module_function

# Parse a Hash or ActionController::Parameters representing a JSON API document
# into an ActiveRecord-ready hash.
#
# @param [Hash|ActionController::Parameters] document
# @param [Hash] options
# fields: Array of symbols and a Hash. Specify whitelisted fields, optionally
# specifying the attribute name on the model.
# @return [Hash] ActiveRecord-ready hash
#
def parse(document, options = {})
fields = parse_fields(options[:fields])

hash = {}

primary_data = document.fetch('data', {})
hash[:id] = primary_data['id'] if primary_data['id']

hash.merge!(parse_attributes(primary_data['attributes'], fields))
hash.merge!(parse_relationships(primary_data['relationships'], fields))

hash
end

# @api private
def parse_fields(fields)
return nil unless fields.is_a?(Array)
fields.each_with_object({}) do |attr, hash|
if attr.is_a?(Symbol)
hash[attr] = attr
elsif attr.is_a?(Hash)
hash.merge!(attr)
end
end
end

# @api private
def parse_attributes(attributes, fields)
return {} unless attributes
attributes.each_with_object({}) do |(key, value), hash|
attribute_name = fields ? fields[key.to_sym] : key.to_sym
next unless attribute_name
hash[attribute_name] = value
end
end

# @api private
def parse_relationships(relationships, fields)
return {} unless relationships
relationships.each_with_object({}) do |(key, value), hash|
association_name = fields ? fields[key.to_sym] : key.to_sym
next unless association_name
data = value['data']
if data.is_a?(Array)
key = "#{association_name.to_s.singularize.to_sym}_ids".to_sym
hash[key] = data.map { |ri| ri['id'] }
else
key = "#{association_name}_id".to_sym
hash[key] = data ? data['id'] : nil
end
end
end
end
end
end
end
end
98 changes: 55 additions & 43 deletions test/adapter/json_api/parse_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,68 @@ module ActiveModel
class Serializer
module Adapter
class JsonApi
class ParseTest < Minitest::Test
def setup
@hash = {
'data' => {
'type' => 'photos',
'id' => 'zorglub',
'attributes' => {
'title' => 'Ember Hamster',
'src' => 'http://example.com/images/productivity.png'
},
'relationships' => {
'author' => {
'data' => nil
module Deserialization
class ParseTest < Minitest::Test
def setup
@hash = {
'data' => {
'type' => 'photos',
'id' => 'zorglub',
'attributes' => {
'title' => 'Ember Hamster',
'src' => 'http://example.com/images/productivity.png'
},
'photographer' => {
'data' => { 'type' => 'people', 'id' => '9' }
},
'comments' => {
'data' => [
{ 'type' => 'comments', 'id' => '1' },
{ 'type' => 'comments', 'id' => '2' }
]
'relationships' => {
'author' => {
'data' => nil
},
'photographer' => {
'data' => { 'type' => 'people', 'id' => '9' }
},
'comments' => {
'data' => [
{ 'type' => 'comments', 'id' => '1' },
{ 'type' => 'comments', 'id' => '2' }
]
}
}
}
}
}
@expected = {
id: 'zorglub',
title: 'Ember Hamster',
src: 'http://example.com/images/productivity.png',
author_id: nil,
photographer_id: '9',
comment_ids: %w(1 2)
}
end
@expected = {
id: 'zorglub',
title: 'Ember Hamster',
src: 'http://example.com/images/productivity.png',
author_id: nil,
photographer_id: '9',
comment_ids: %w(1 2)
}
end

def test_hash
parsed_hash = ActiveModel::Serializer::Adapter::JsonApi.parse(@hash)
assert_equal(@expected, parsed_hash)
end
def test_hash
parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse(@hash)
assert_equal(@expected, parsed_hash)
end

def test_parameters
parameters = ActionController::Parameters.new(@hash)
parsed_hash = ActiveModel::Serializer::Adapter::JsonApi.parse(parameters)
assert_equal(@expected, parsed_hash)
end
def test_parameters
parameters = ActionController::Parameters.new(@hash)
parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse(parameters)
assert_equal(@expected, parsed_hash)
end

def test_illformed_payload
parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse({})
assert_equal({}, parsed_hash)
end

def test_illformed_payload
parsed_hash = ActiveModel::Serializer::Adapter::JsonApi.parse({})
assert_equal({}, parsed_hash)
def test_filter_fields
parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse(@hash, fields: [:title, author: :user])
expected = {
id: 'zorglub',
title: 'Ember Hamster',
user_id: nil
}
assert_equal(expected, parsed_hash)
end
end
end
end
Expand Down

0 comments on commit 42addfb

Please sign in to comment.