mirror of
https://github.com/ditkrg/jsonapi-deserializable.git
synced 2026-01-22 13:56:50 +00:00
Configurable deserialization + reverse mapping (#10)
* Deserialize all fields. * Make blocks return hash instead of using fields method. * Ensure valid payload. * Make reverse mapping available.
This commit is contained in:
parent
707a20a568
commit
538e09c5b3
@ -14,6 +14,8 @@ Gem::Specification.new do |spec|
|
||||
spec.files = Dir['README.md', 'lib/**/*']
|
||||
spec.require_path = 'lib'
|
||||
|
||||
spec.add_dependency 'jsonapi-parser', '0.1.1.beta3'
|
||||
|
||||
spec.add_development_dependency 'rake', '>=0.9'
|
||||
spec.add_development_dependency 'rspec', '~>3.4'
|
||||
spec.add_development_dependency 'codecov', '~> 0.1'
|
||||
|
||||
@ -1,15 +1,17 @@
|
||||
require 'jsonapi/deserializable/relationship_dsl'
|
||||
require 'jsonapi/deserializable/relationship/dsl'
|
||||
require 'jsonapi/parser/relationship'
|
||||
|
||||
module JSONAPI
|
||||
module Deserializable
|
||||
class Relationship
|
||||
include RelationshipDSL
|
||||
extend DSL
|
||||
|
||||
class << self
|
||||
attr_accessor :has_one_block, :has_many_block
|
||||
end
|
||||
|
||||
def self.inherited(klass)
|
||||
super
|
||||
klass.has_one_block = has_one_block
|
||||
klass.has_many_block = has_many_block
|
||||
end
|
||||
@ -19,9 +21,11 @@ module JSONAPI
|
||||
end
|
||||
|
||||
def initialize(payload)
|
||||
Parser::Relationship.parse!(payload)
|
||||
@document = payload
|
||||
@data = payload['data']
|
||||
deserialize!
|
||||
freeze
|
||||
end
|
||||
|
||||
def to_hash
|
||||
@ -32,31 +36,32 @@ module JSONAPI
|
||||
private
|
||||
|
||||
def deserialize!
|
||||
@hash = {}
|
||||
return unless @document.key?('data')
|
||||
if @data.is_a?(Array)
|
||||
deserialize_has_many!
|
||||
elsif @data.nil? || @data.is_a?(Hash)
|
||||
deserialize_has_one!
|
||||
@hash =
|
||||
if @data.is_a?(Array)
|
||||
deserialize_has_many
|
||||
elsif @data.nil? || @data.is_a?(Hash)
|
||||
deserialize_has_one
|
||||
end
|
||||
end
|
||||
|
||||
def deserialize_has_one
|
||||
id = @data && @data['id']
|
||||
type = @data && @data['type']
|
||||
if self.class.has_one_block
|
||||
self.class.has_one_block.call(@document, id, type)
|
||||
else
|
||||
{ id: id, type: type }
|
||||
end
|
||||
end
|
||||
|
||||
def deserialize_has_one!
|
||||
return unless self.class.has_one_block
|
||||
id = @data && @data['id']
|
||||
type = @data && @data['type']
|
||||
instance_exec(@document, id, type, &self.class.has_one_block)
|
||||
end
|
||||
|
||||
def deserialize_has_many!
|
||||
return unless self.class.has_many_block
|
||||
def deserialize_has_many
|
||||
ids = @data.map { |ri| ri['id'] }
|
||||
types = @data.map { |ri| ri['type'] }
|
||||
instance_exec(@document, ids, types, &self.class.has_many_block)
|
||||
end
|
||||
|
||||
def field(hash)
|
||||
@hash.merge!(hash)
|
||||
if self.class.has_many_block
|
||||
self.class.has_many_block.call(@document, ids, types)
|
||||
else
|
||||
{ ids: ids, types: types }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
15
lib/jsonapi/deserializable/relationship/dsl.rb
Normal file
15
lib/jsonapi/deserializable/relationship/dsl.rb
Normal file
@ -0,0 +1,15 @@
|
||||
module JSONAPI
|
||||
module Deserializable
|
||||
class Relationship
|
||||
module DSL
|
||||
def has_one(&block)
|
||||
self.has_one_block = block
|
||||
end
|
||||
|
||||
def has_many(&block)
|
||||
self.has_many_block = block
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,21 +0,0 @@
|
||||
module JSONAPI
|
||||
module Deserializable
|
||||
module RelationshipDSL
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def has_one(&block)
|
||||
block ||= proc { |rel| field relationship: rel }
|
||||
self.has_one_block = block
|
||||
end
|
||||
|
||||
def has_many(&block)
|
||||
block ||= proc { |rel| field relationship: rel }
|
||||
self.has_many_block = block
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,33 +1,49 @@
|
||||
require 'jsonapi/deserializable/resource_dsl'
|
||||
require 'jsonapi/deserializable/resource/configuration'
|
||||
require 'jsonapi/deserializable/resource/dsl'
|
||||
require 'jsonapi/parser/resource'
|
||||
|
||||
module JSONAPI
|
||||
module Deserializable
|
||||
class Resource
|
||||
include ResourceDSL
|
||||
extend DSL
|
||||
|
||||
class << self
|
||||
attr_accessor :type_block, :id_block, :attr_blocks,
|
||||
:has_one_rel_blocks, :has_many_rel_blocks
|
||||
:has_one_rel_blocks, :has_many_rel_blocks,
|
||||
:configuration
|
||||
end
|
||||
|
||||
self.attr_blocks = {}
|
||||
@class_cache = {}
|
||||
|
||||
self.configuration = Configuration.new
|
||||
self.attr_blocks = {}
|
||||
self.has_one_rel_blocks = {}
|
||||
self.has_many_rel_blocks = {}
|
||||
|
||||
def self.inherited(klass)
|
||||
super
|
||||
klass.type_block = type_block
|
||||
klass.id_block = id_block
|
||||
klass.attr_blocks = attr_blocks.dup
|
||||
klass.configuration = configuration.dup
|
||||
klass.type_block = type_block
|
||||
klass.id_block = id_block
|
||||
klass.attr_blocks = attr_blocks.dup
|
||||
klass.has_one_rel_blocks = has_one_rel_blocks.dup
|
||||
klass.has_many_rel_blocks = has_many_rel_blocks.dup
|
||||
end
|
||||
|
||||
def self.configure
|
||||
yield(configuration)
|
||||
end
|
||||
|
||||
def self.[](name)
|
||||
@class_cache[name] ||= Class.new(self)
|
||||
end
|
||||
|
||||
def self.call(payload)
|
||||
new(payload).to_h
|
||||
end
|
||||
|
||||
def initialize(payload)
|
||||
Parser::Resource.parse!(payload)
|
||||
@document = payload
|
||||
@data = @document['data']
|
||||
@type = @data['type']
|
||||
@ -35,6 +51,7 @@ module JSONAPI
|
||||
@attributes = @data['attributes'] || {}
|
||||
@relationships = @data['relationships'] || {}
|
||||
deserialize!
|
||||
freeze
|
||||
end
|
||||
|
||||
def to_hash
|
||||
@ -42,69 +59,97 @@ module JSONAPI
|
||||
end
|
||||
alias to_h to_hash
|
||||
|
||||
attr_reader :reverse_mapping
|
||||
|
||||
private
|
||||
|
||||
def configuration
|
||||
self.class.configuration
|
||||
end
|
||||
|
||||
def register_mappings(keys, path)
|
||||
keys.each do |k|
|
||||
@reverse_mapping[k] = path
|
||||
end
|
||||
end
|
||||
|
||||
def deserialize!
|
||||
@hash = {}
|
||||
deserialize_type!
|
||||
deserialize_id!
|
||||
deserialize_attrs!
|
||||
deserialize_rels!
|
||||
@reverse_mapping = {}
|
||||
hashes = [deserialize_type, deserialize_id,
|
||||
deserialize_attrs, deserialize_rels]
|
||||
@hash = hashes.reduce({}, :merge)
|
||||
end
|
||||
|
||||
def deserialize_type!
|
||||
return unless @type && self.class.type_block
|
||||
instance_exec(@type, &self.class.type_block)
|
||||
def deserialize_type
|
||||
block = self.class.type_block || configuration.default_type
|
||||
hash = block.call(@type)
|
||||
register_mappings(hash.keys, '/data/type')
|
||||
hash
|
||||
end
|
||||
|
||||
def deserialize_id!
|
||||
return unless @id && self.class.id_block
|
||||
instance_exec(@id, &self.class.id_block)
|
||||
def deserialize_id
|
||||
return {} unless @id
|
||||
block = self.class.id_block || configuration.default_id
|
||||
hash = block.call(@id)
|
||||
register_mappings(hash.keys, '/data/id')
|
||||
hash
|
||||
end
|
||||
|
||||
def deserialize_attrs!
|
||||
self.class.attr_blocks.each do |attr, block|
|
||||
next unless @attributes.key?(attr)
|
||||
instance_exec(@attributes[attr], &block)
|
||||
def deserialize_attrs
|
||||
@attributes
|
||||
.map { |key, val| deserialize_attr(key, val) }
|
||||
.reduce({}, :merge)
|
||||
end
|
||||
|
||||
def deserialize_attr(key, val)
|
||||
hash = if self.class.attr_blocks.key?(key)
|
||||
self.class.attr_blocks[key].call(val)
|
||||
else
|
||||
configuration.default_attribute.call(key, val)
|
||||
end
|
||||
register_mappings(hash.keys, "/data/attributes/#{key}")
|
||||
hash
|
||||
end
|
||||
|
||||
def deserialize_rels
|
||||
@relationships
|
||||
.map { |key, val| deserialize_rel(key, val) }
|
||||
.reduce({}, :merge)
|
||||
end
|
||||
|
||||
def deserialize_rel(key, val)
|
||||
hash = if val['data'].is_a?(Array)
|
||||
deserialize_has_many_rel(key, val)
|
||||
else
|
||||
deserialize_has_one_rel(key, val)
|
||||
end
|
||||
register_mappings(hash.keys, "/data/relationships/#{key}")
|
||||
hash
|
||||
end
|
||||
|
||||
# rubocop: disable Metrics/AbcSize
|
||||
def deserialize_has_one_rel(key, val)
|
||||
id = val['data'] && val['data']['id']
|
||||
type = val['data'] && val['data']['type']
|
||||
if self.class.has_one_rel_blocks.key?(key)
|
||||
self.class.has_one_rel_blocks[key].call(val, id, type)
|
||||
else
|
||||
configuration.default_has_one.call(key, val, id, type)
|
||||
end
|
||||
end
|
||||
# rubocop: enable Metrics/AbcSize
|
||||
|
||||
def deserialize_rels!
|
||||
deserialize_has_one_rels!
|
||||
deserialize_has_many_rels!
|
||||
end
|
||||
|
||||
def deserialize_has_one_rels!
|
||||
self.class.has_one_rel_blocks.each do |key, block|
|
||||
rel = @relationships[key]
|
||||
next unless rel && (rel['data'].nil? || rel['data'].is_a?(Hash))
|
||||
deserialize_has_one_rel!(rel, &block)
|
||||
# rubocop: disable Metrics/AbcSize
|
||||
def deserialize_has_many_rel(key, val)
|
||||
ids = val['data'].map { |ri| ri['id'] }
|
||||
types = val['data'].map { |ri| ri['type'] }
|
||||
if self.class.has_many_rel_blocks.key?(key)
|
||||
self.class.has_many_rel_blocks[key].call(val, ids, types)
|
||||
else
|
||||
configuration.default_has_many.call(key, val, ids, types)
|
||||
end
|
||||
end
|
||||
|
||||
def deserialize_has_one_rel!(rel, &block)
|
||||
id = rel['data'] && rel['data']['id']
|
||||
type = rel['data'] && rel['data']['type']
|
||||
instance_exec(rel, id, type, &block)
|
||||
end
|
||||
|
||||
def deserialize_has_many_rels!
|
||||
self.class.has_many_rel_blocks.each do |key, block|
|
||||
rel = @relationships[key]
|
||||
next unless rel && rel['data'].is_a?(Array)
|
||||
deserialize_has_many_rel!(rel, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def deserialize_has_many_rel!(rel, &block)
|
||||
ids = rel['data'].map { |ri| ri['id'] }
|
||||
types = rel['data'].map { |ri| ri['type'] }
|
||||
instance_exec(rel, ids, types, &block)
|
||||
end
|
||||
|
||||
def field(hash)
|
||||
@hash.merge!(hash)
|
||||
end
|
||||
# rubocop: enable Metrics/AbcSize
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
28
lib/jsonapi/deserializable/resource/configuration.rb
Normal file
28
lib/jsonapi/deserializable/resource/configuration.rb
Normal file
@ -0,0 +1,28 @@
|
||||
module JSONAPI
|
||||
module Deserializable
|
||||
class Resource
|
||||
class Configuration
|
||||
DEFAULT_TYPE_BLOCK = proc { |t| { type: t } }
|
||||
DEFAULT_ID_BLOCK = proc { |i| { id: i } }
|
||||
DEFAULT_ATTR_BLOCK = proc { |k, v| { k.to_sym => v } }
|
||||
DEFAULT_HAS_ONE_BLOCK = proc do |k, _, i, t|
|
||||
{ "#{k}_id".to_sym => i, "#{k}_type".to_sym => t }
|
||||
end
|
||||
DEFAULT_HAS_MANY_BLOCK = proc do |k, _, i, t|
|
||||
{ "#{k}_ids".to_sym => i, "#{k}_types".to_sym => t }
|
||||
end
|
||||
|
||||
attr_accessor :default_type, :default_id, :default_attribute,
|
||||
:default_has_one, :default_has_many
|
||||
|
||||
def initialize
|
||||
self.default_type = DEFAULT_TYPE_BLOCK
|
||||
self.default_id = DEFAULT_ID_BLOCK
|
||||
self.default_attribute = DEFAULT_ATTR_BLOCK
|
||||
self.default_has_one = DEFAULT_HAS_ONE_BLOCK
|
||||
self.default_has_many = DEFAULT_HAS_MANY_BLOCK
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
27
lib/jsonapi/deserializable/resource/dsl.rb
Normal file
27
lib/jsonapi/deserializable/resource/dsl.rb
Normal file
@ -0,0 +1,27 @@
|
||||
module JSONAPI
|
||||
module Deserializable
|
||||
class Resource
|
||||
module DSL
|
||||
def type(&block)
|
||||
self.type_block = block
|
||||
end
|
||||
|
||||
def id(&block)
|
||||
self.id_block = block
|
||||
end
|
||||
|
||||
def attribute(key, &block)
|
||||
attr_blocks[key.to_s] = block
|
||||
end
|
||||
|
||||
def has_one(key, &block)
|
||||
has_one_rel_blocks[key.to_s] = block
|
||||
end
|
||||
|
||||
def has_many(key, &block)
|
||||
has_many_rel_blocks[key.to_s] = block
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,39 +0,0 @@
|
||||
module JSONAPI
|
||||
module Deserializable
|
||||
module ResourceDSL
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def type(&block)
|
||||
block ||= proc { |type| field type: type }
|
||||
self.type_block = block
|
||||
end
|
||||
|
||||
def id(&block)
|
||||
block ||= proc { |id| field id: id }
|
||||
self.id_block = block
|
||||
end
|
||||
|
||||
def attribute(key, options = {}, &block)
|
||||
unless block
|
||||
options[:key] ||= key.to_sym
|
||||
block = proc { |attr| field key => attr }
|
||||
end
|
||||
attr_blocks[key.to_s] = block
|
||||
end
|
||||
|
||||
def has_one(key, &block)
|
||||
block ||= proc { |rel| field key.to_sym => rel }
|
||||
has_one_rel_blocks[key.to_s] = block
|
||||
end
|
||||
|
||||
def has_many(key, &block)
|
||||
block ||= proc { |rel| field key.to_sym => rel }
|
||||
has_many_rel_blocks[key.to_s] = block
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -4,9 +4,7 @@ describe JSONAPI::Deserializable::Relationship, '.has_many' do
|
||||
let(:deserializable_foo) do
|
||||
Class.new(JSONAPI::Deserializable::Relationship) do
|
||||
has_many do |rel, ids, types|
|
||||
field foo_ids: ids
|
||||
field foo_types: types
|
||||
field foo_rel: rel
|
||||
{ foo_ids: ids, foo_types: types, foo_rel: rel }
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -29,12 +27,10 @@ describe JSONAPI::Deserializable::Relationship, '.has_many' do
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
it 'defaults to creating a relationship field' do
|
||||
klass = Class.new(JSONAPI::Deserializable::Relationship) do
|
||||
has_many
|
||||
end
|
||||
it 'defaults to creating ids and types fields' do
|
||||
klass = Class.new(JSONAPI::Deserializable::Relationship)
|
||||
actual = klass.call(payload)
|
||||
expected = { relationship: payload }
|
||||
expected = { ids: %w(bar baz), types: %w(foo foo) }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
@ -51,20 +47,19 @@ describe JSONAPI::Deserializable::Relationship, '.has_many' do
|
||||
end
|
||||
|
||||
context 'data is absent' do
|
||||
it 'does not create corresponding fields' do
|
||||
it 'raises InvalidDocument' do
|
||||
payload = {}
|
||||
actual = deserializable_foo.call(payload)
|
||||
expected = {}
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
expect { deserializable_foo.call(payload) }
|
||||
.to raise_error(JSONAPI::Parser::InvalidDocument)
|
||||
end
|
||||
end
|
||||
|
||||
context 'relationship is not to-many' do
|
||||
it 'does not create corresponding fields' do
|
||||
it 'falls back to default to-one deserialization scheme' do
|
||||
payload = { 'data' => nil }
|
||||
actual = deserializable_foo.call(payload)
|
||||
expected = {}
|
||||
expected = { id: nil, type: nil }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
@ -4,9 +4,7 @@ describe JSONAPI::Deserializable::Relationship, '.has_one' do
|
||||
let(:deserializable_foo) do
|
||||
Class.new(JSONAPI::Deserializable::Relationship) do
|
||||
has_one do |rel, id, type|
|
||||
field foo_id: id
|
||||
field foo_type: type
|
||||
field foo_rel: rel
|
||||
{ foo_id: id, foo_type: type, foo_rel: rel }
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -23,12 +21,10 @@ describe JSONAPI::Deserializable::Relationship, '.has_one' do
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
it 'defaults to creating a relationship field' do
|
||||
klass = Class.new(JSONAPI::Deserializable::Relationship) do
|
||||
has_one
|
||||
end
|
||||
it 'defaults to creating id and type fields' do
|
||||
klass = Class.new(JSONAPI::Deserializable::Relationship)
|
||||
actual = klass.call(payload)
|
||||
expected = { relationship: payload }
|
||||
expected = { id: 'bar', type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
@ -45,20 +41,19 @@ describe JSONAPI::Deserializable::Relationship, '.has_one' do
|
||||
end
|
||||
|
||||
context 'data is absent' do
|
||||
it 'does not create corresponding fields' do
|
||||
it 'raises InvalidDocument' do
|
||||
payload = {}
|
||||
actual = deserializable_foo.call(payload)
|
||||
expected = {}
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
expect { deserializable_foo.call(payload) }
|
||||
.to raise_error(JSONAPI::Parser::InvalidDocument)
|
||||
end
|
||||
end
|
||||
|
||||
context 'relationship is not to-one' do
|
||||
it 'does not create corresponding fields' do
|
||||
it 'falls back to default has_many deserialization scheme ' do
|
||||
payload = { 'data' => [] }
|
||||
actual = deserializable_foo.call(payload)
|
||||
expected = {}
|
||||
expected = { ids: [], types: [] }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
@ -9,21 +9,21 @@ describe JSONAPI::Deserializable::Resource, '.attribute' do
|
||||
}
|
||||
}
|
||||
klass = Class.new(JSONAPI::Deserializable::Resource) do
|
||||
attribute(:foo) { |foo| field foo: foo }
|
||||
attribute(:foo) { |foo| Hash[foo: foo] }
|
||||
end
|
||||
actual = klass.call(payload)
|
||||
expected = { foo: 'bar' }
|
||||
expected = { foo: 'bar', type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
it 'does not create corresponding field if attribute is absent' do
|
||||
payload = { 'data' => { 'type' => 'foo' }, 'attributes' => {} }
|
||||
payload = { 'data' => { 'type' => 'foo', 'attributes' => {} } }
|
||||
klass = Class.new(JSONAPI::Deserializable::Resource) do
|
||||
attribute(:foo) { |foo| field foo: foo }
|
||||
attribute(:foo) { |foo| Hash[foo: foo] }
|
||||
end
|
||||
actual = klass.call(payload)
|
||||
expected = {}
|
||||
expected = { type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
@ -31,10 +31,10 @@ describe JSONAPI::Deserializable::Resource, '.attribute' do
|
||||
it 'does not create corresponding field if no attribute specified' do
|
||||
payload = { 'data' => { 'type' => 'foo' } }
|
||||
klass = Class.new(JSONAPI::Deserializable::Resource) do
|
||||
attribute(:foo) { |foo| field foo: foo }
|
||||
attribute(:foo) { |foo| Hash[foo: foo] }
|
||||
end
|
||||
actual = klass.call(payload)
|
||||
expected = {}
|
||||
expected = { type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
@ -46,11 +46,9 @@ describe JSONAPI::Deserializable::Resource, '.attribute' do
|
||||
'attributes' => { 'foo' => 'bar' }
|
||||
}
|
||||
}
|
||||
klass = Class.new(JSONAPI::Deserializable::Resource) do
|
||||
attribute :foo
|
||||
end
|
||||
klass = JSONAPI::Deserializable::Resource
|
||||
actual = klass.call(payload)
|
||||
expected = { foo: 'bar' }
|
||||
expected = { foo: 'bar', type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
@ -4,9 +4,7 @@ describe JSONAPI::Deserializable::Resource, '.has_many' do
|
||||
let(:deserializable_foo) do
|
||||
Class.new(JSONAPI::Deserializable::Resource) do
|
||||
has_many :foo do |rel, ids, types|
|
||||
field foo_ids: ids
|
||||
field foo_types: types
|
||||
field foo_rel: rel
|
||||
Hash[foo_ids: ids, foo_types: types, foo_rel: rel]
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -31,17 +29,16 @@ describe JSONAPI::Deserializable::Resource, '.has_many' do
|
||||
it 'creates corresponding fields' do
|
||||
actual = deserializable_foo.call(payload)
|
||||
expected = { foo_ids: %w(bar baz), foo_types: %w(foo foo),
|
||||
foo_rel: payload['data']['relationships']['foo'] }
|
||||
foo_rel: payload['data']['relationships']['foo'],
|
||||
type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
it 'defaults to creating a field of the same name' do
|
||||
klass = Class.new(JSONAPI::Deserializable::Resource) do
|
||||
has_many :foo
|
||||
end
|
||||
it 'defaults to creating a #{name}_ids and #{name}_types fields' do
|
||||
klass = JSONAPI::Deserializable::Resource
|
||||
actual = klass.call(payload)
|
||||
expected = { foo: payload['data']['relationships']['foo'] }
|
||||
expected = { foo_ids: %w(bar baz), foo_types: %w(foo foo), type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
@ -61,7 +58,8 @@ describe JSONAPI::Deserializable::Resource, '.has_many' do
|
||||
}
|
||||
actual = deserializable_foo.call(payload)
|
||||
expected = { foo_ids: [], foo_types: [],
|
||||
foo_rel: payload['data']['relationships']['foo'] }
|
||||
foo_rel: payload['data']['relationships']['foo'],
|
||||
type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
@ -76,7 +74,7 @@ describe JSONAPI::Deserializable::Resource, '.has_many' do
|
||||
}
|
||||
}
|
||||
actual = deserializable_foo.call(payload)
|
||||
expected = {}
|
||||
expected = { type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
@ -90,27 +88,7 @@ describe JSONAPI::Deserializable::Resource, '.has_many' do
|
||||
}
|
||||
}
|
||||
actual = deserializable_foo.call(payload)
|
||||
expected = {}
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
end
|
||||
|
||||
context 'relationship is not to-many' do
|
||||
it 'does not create corresponding fields' do
|
||||
payload = {
|
||||
'data' => {
|
||||
'type' => 'foo',
|
||||
'relationships' => {
|
||||
'foo' => {
|
||||
'data' => { 'type' => 'foo', 'id' => 'bar' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
actual = deserializable_foo.call(payload)
|
||||
expected = {}
|
||||
expected = { type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
@ -4,9 +4,7 @@ describe JSONAPI::Deserializable::Resource, '.has_one' do
|
||||
let(:deserializable_foo) do
|
||||
Class.new(JSONAPI::Deserializable::Resource) do
|
||||
has_one :foo do |rel, id, type|
|
||||
field foo_id: id
|
||||
field foo_type: type
|
||||
field foo_rel: rel
|
||||
Hash[foo_id: id, foo_type: type, foo_rel: rel]
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -28,23 +26,22 @@ describe JSONAPI::Deserializable::Resource, '.has_one' do
|
||||
it 'creates corresponding fields' do
|
||||
actual = deserializable_foo.call(payload)
|
||||
expected = { foo_id: 'bar', foo_type: 'foo',
|
||||
foo_rel: payload['data']['relationships']['foo'] }
|
||||
foo_rel: payload['data']['relationships']['foo'],
|
||||
type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
it 'defaults to creating a field of the same name' do
|
||||
klass = Class.new(JSONAPI::Deserializable::Resource) do
|
||||
has_one :foo
|
||||
end
|
||||
it 'defaults to creating #{name}_id and #{name}_type' do
|
||||
klass = JSONAPI::Deserializable::Resource
|
||||
actual = klass.call(payload)
|
||||
expected = { foo: payload['data']['relationships']['foo'] }
|
||||
expected = { foo_id: 'bar', foo_type: 'foo', type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
end
|
||||
|
||||
context 'relationship is nil' do
|
||||
context 'relationship value is nil' do
|
||||
it 'creates corresponding fields' do
|
||||
payload = {
|
||||
'data' => {
|
||||
@ -56,9 +53,11 @@ describe JSONAPI::Deserializable::Resource, '.has_one' do
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
actual = deserializable_foo.call(payload)
|
||||
expected = { foo_id: nil, foo_type: nil,
|
||||
foo_rel: payload['data']['relationships']['foo'] }
|
||||
foo_rel: payload['data']['relationships']['foo'],
|
||||
type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
@ -73,7 +72,7 @@ describe JSONAPI::Deserializable::Resource, '.has_one' do
|
||||
}
|
||||
}
|
||||
actual = deserializable_foo.call(payload)
|
||||
expected = {}
|
||||
expected = { type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
@ -87,26 +86,7 @@ describe JSONAPI::Deserializable::Resource, '.has_one' do
|
||||
}
|
||||
}
|
||||
actual = deserializable_foo.call(payload)
|
||||
expected = {}
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
end
|
||||
|
||||
context 'relationship is not to-one' do
|
||||
it 'does not create corresponding fields' do
|
||||
payload = {
|
||||
'data' => {
|
||||
'type' => 'foo',
|
||||
'relationships' => {
|
||||
'foo' => {
|
||||
'data' => []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
actual = deserializable_foo.call(payload)
|
||||
expected = {}
|
||||
expected = { type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
@ -4,10 +4,10 @@ describe JSONAPI::Deserializable::Resource, '.id' do
|
||||
it 'creates corresponding field if id is present' do
|
||||
payload = { 'data' => { 'type' => 'foo', 'id' => 'bar' } }
|
||||
klass = Class.new(JSONAPI::Deserializable::Resource) do
|
||||
id { |i| field id: i }
|
||||
id { |i| Hash[id: i] }
|
||||
end
|
||||
actual = klass.call(payload)
|
||||
expected = { id: 'bar' }
|
||||
expected = { id: 'bar', type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
@ -15,21 +15,19 @@ describe JSONAPI::Deserializable::Resource, '.id' do
|
||||
it 'does not create corresponding field if id is absent' do
|
||||
payload = { 'data' => { 'type' => 'foo' } }
|
||||
klass = Class.new(JSONAPI::Deserializable::Resource) do
|
||||
id { |i| field id: i }
|
||||
id { |i| Hash[id: i] }
|
||||
end
|
||||
actual = klass.call(payload)
|
||||
expected = {}
|
||||
expected = { type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
it 'defaults to creating an id field' do
|
||||
payload = { 'data' => { 'type' => 'foo', 'id' => 'bar' } }
|
||||
klass = Class.new(JSONAPI::Deserializable::Resource) do
|
||||
id
|
||||
end
|
||||
klass = JSONAPI::Deserializable::Resource
|
||||
actual = klass.call(payload)
|
||||
expected = { id: 'bar' }
|
||||
expected = { id: 'bar', type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
@ -4,7 +4,7 @@ describe JSONAPI::Deserializable::Resource, '.type' do
|
||||
it 'creates corresponding field' do
|
||||
payload = { 'data' => { 'type' => 'foo' } }
|
||||
klass = Class.new(JSONAPI::Deserializable::Resource) do
|
||||
type { |t| field type: t }
|
||||
type { |t| Hash[type: t] }
|
||||
end
|
||||
actual = klass.call(payload)
|
||||
expected = { type: 'foo' }
|
||||
@ -14,9 +14,7 @@ describe JSONAPI::Deserializable::Resource, '.type' do
|
||||
|
||||
it 'defaults to creating a type field' do
|
||||
payload = { 'data' => { 'type' => 'foo' } }
|
||||
klass = Class.new(JSONAPI::Deserializable::Resource) do
|
||||
type
|
||||
end
|
||||
klass = JSONAPI::Deserializable::Resource
|
||||
actual = klass.call(payload)
|
||||
expected = { type: 'foo' }
|
||||
|
||||
|
||||
108
spec/resource/configuration_spec.rb
Normal file
108
spec/resource/configuration_spec.rb
Normal file
@ -0,0 +1,108 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe JSONAPI::Deserializable::Resource, '.configure' do
|
||||
it 'overrides global default attribute deserialization scheme' do
|
||||
payload = {
|
||||
'data' => {
|
||||
'type' => 'foo',
|
||||
'attributes' => {
|
||||
'foo' => 'bar',
|
||||
'baz' => 'foo'
|
||||
}
|
||||
}
|
||||
}
|
||||
begin
|
||||
JSONAPI::Deserializable::Resource.configure do |cfg|
|
||||
cfg.default_attribute = proc do |key, value|
|
||||
{ "custom_#{key}".to_sym => value }
|
||||
end
|
||||
end
|
||||
klass = JSONAPI::Deserializable::Resource
|
||||
actual = klass.call(payload)
|
||||
expected = { custom_foo: 'bar', custom_baz: 'foo', type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
ensure
|
||||
JSONAPI::Deserializable::Resource.configuration =
|
||||
JSONAPI::Deserializable::Resource::Configuration.new
|
||||
end
|
||||
end
|
||||
|
||||
it 'overrides default attribute deserialization scheme' do
|
||||
payload = {
|
||||
'data' => {
|
||||
'type' => 'foo',
|
||||
'attributes' => {
|
||||
'foo' => 'bar',
|
||||
'baz' => 'foo'
|
||||
}
|
||||
}
|
||||
}
|
||||
JSONAPI::Deserializable::Resource[:c1].configure do |cfg|
|
||||
cfg.default_attribute = proc do |key, value|
|
||||
{ "custom_#{key}".to_sym => value }
|
||||
end
|
||||
end
|
||||
klass = JSONAPI::Deserializable::Resource[:c1]
|
||||
actual = klass.call(payload)
|
||||
expected = { custom_foo: 'bar', custom_baz: 'foo', type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
it 'overrides the default has_many relationship deserialization scheme' do
|
||||
payload = {
|
||||
'data' => {
|
||||
'type' => 'foo',
|
||||
'relationships' => {
|
||||
'foo' => {
|
||||
'data' => [{ 'type' => 'bar', 'id' => 'baz' },
|
||||
{ 'type' => 'foo', 'id' => 'bar' }]
|
||||
},
|
||||
'bar' => {
|
||||
'data' => [{ 'type' => 'baz', 'id' => 'foo' },
|
||||
{ 'type' => 'baz', 'id' => 'buz' }]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
JSONAPI::Deserializable::Resource[:c1].configure do |cfg|
|
||||
cfg.default_has_many = proc do |name, _value, ids, types|
|
||||
{ "custom_#{name}_ids".to_sym => ids,
|
||||
"custom_#{name}_types".to_sym => types }
|
||||
end
|
||||
end
|
||||
klass = JSONAPI::Deserializable::Resource[:c1]
|
||||
actual = klass.call(payload)
|
||||
expected = { custom_foo_ids: %w(baz bar), custom_foo_types: %w(bar foo),
|
||||
custom_bar_ids: %w(foo buz), custom_bar_types: %w(baz baz),
|
||||
type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
it 'overrides the default has_one relationship deserialization scheme' do
|
||||
payload = {
|
||||
'data' => {
|
||||
'type' => 'foo',
|
||||
'relationships' => {
|
||||
'foo' => { 'data' => { 'type' => 'bar', 'id' => 'baz' } },
|
||||
'bar' => { 'data' => { 'type' => 'foo', 'id' => 'bar' } }
|
||||
}
|
||||
}
|
||||
}
|
||||
JSONAPI::Deserializable::Resource[:c1].configure do |cfg|
|
||||
cfg.default_has_one = proc do |name, _value, id, type|
|
||||
{ "custom_#{name}_id".to_sym => id,
|
||||
"custom_#{name}_type".to_sym => type }
|
||||
end
|
||||
end
|
||||
klass = JSONAPI::Deserializable::Resource[:c1]
|
||||
actual = klass.call(payload)
|
||||
expected = { custom_foo_id: 'baz', custom_foo_type: 'bar',
|
||||
custom_bar_id: 'bar', custom_bar_type: 'foo',
|
||||
type: 'foo' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
end
|
||||
231
spec/resource/reverse_mapping_spec.rb
Normal file
231
spec/resource/reverse_mapping_spec.rb
Normal file
@ -0,0 +1,231 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe JSONAPI::Deserializable::Resource, '#reverse_mapping' do
|
||||
it 'generates reverse mapping for default type' do
|
||||
payload = { 'data' => { 'type' => 'foo' } }
|
||||
klass = JSONAPI::Deserializable::Resource
|
||||
actual = klass.new(payload).reverse_mapping
|
||||
expected = { type: '/data/type' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
it 'generates reverse mapping for overriden type' do
|
||||
payload = { 'data' => { 'type' => 'foo' } }
|
||||
klass = Class.new(JSONAPI::Deserializable::Resource) do
|
||||
type { |t| { custom_type: t } }
|
||||
end
|
||||
actual = klass.new(payload).reverse_mapping
|
||||
expected = { custom_type: '/data/type' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
it 'generates reverse mapping for default id' do
|
||||
payload = { 'data' => { 'type' => 'foo', 'id' => 'bar' } }
|
||||
klass = JSONAPI::Deserializable::Resource
|
||||
actual = klass.new(payload).reverse_mapping
|
||||
expected = { id: '/data/id', type: '/data/type' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
it 'generates reverse mapping for overriden id' do
|
||||
payload = { 'data' => { 'type' => 'foo', 'id' => 'bar' } }
|
||||
klass = Class.new(JSONAPI::Deserializable::Resource) do
|
||||
id { |i| { custom_id: i } }
|
||||
end
|
||||
actual = klass.new(payload).reverse_mapping
|
||||
expected = { custom_id: '/data/id', type: '/data/type' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
it 'generates reverse mapping for default attributes' do
|
||||
payload = {
|
||||
'data' => {
|
||||
'type' => 'foo',
|
||||
'attributes' => {
|
||||
'foo' => 'bar',
|
||||
'baz' => 'fiz'
|
||||
}
|
||||
}
|
||||
}
|
||||
klass = JSONAPI::Deserializable::Resource
|
||||
actual = klass.new(payload).reverse_mapping
|
||||
expected = { type: '/data/type',
|
||||
foo: '/data/attributes/foo',
|
||||
baz: '/data/attributes/baz' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
it 'generates reverse mapping for locally overriden attributes' do
|
||||
payload = {
|
||||
'data' => {
|
||||
'type' => 'foo',
|
||||
'attributes' => {
|
||||
'foo' => 'bar',
|
||||
'baz' => 'fiz'
|
||||
}
|
||||
}
|
||||
}
|
||||
klass = Class.new(JSONAPI::Deserializable::Resource) do
|
||||
attribute(:foo) { |foo| { custom_foo: foo } }
|
||||
end
|
||||
actual = klass.new(payload).reverse_mapping
|
||||
expected = { type: '/data/type',
|
||||
custom_foo: '/data/attributes/foo',
|
||||
baz: '/data/attributes/baz' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
it 'generates reverse mapping for globally overriden attributes' do
|
||||
payload = {
|
||||
'data' => {
|
||||
'type' => 'foo',
|
||||
'attributes' => {
|
||||
'foo' => 'bar',
|
||||
'baz' => 'fiz'
|
||||
}
|
||||
}
|
||||
}
|
||||
klass = Class.new(JSONAPI::Deserializable::Resource) do
|
||||
attribute(:foo) { |foo| { other_foo: foo } }
|
||||
end
|
||||
klass.configure do |config|
|
||||
config.default_attribute = proc do |key, value|
|
||||
{ "custom_#{key}".to_sym => value }
|
||||
end
|
||||
end
|
||||
actual = klass.new(payload).reverse_mapping
|
||||
expected = { type: '/data/type',
|
||||
other_foo: '/data/attributes/foo',
|
||||
custom_baz: '/data/attributes/baz' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
it 'generates reverse mapping for default has_many' do
|
||||
payload = {
|
||||
'data' => {
|
||||
'type' => 'foo',
|
||||
'relationships' => {
|
||||
'foo' => {
|
||||
'data' => nil
|
||||
},
|
||||
'baz' => {
|
||||
'data' => nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
klass = JSONAPI::Deserializable::Resource
|
||||
actual = klass.new(payload).reverse_mapping
|
||||
expected = { type: '/data/type',
|
||||
foo_id: '/data/relationships/foo',
|
||||
foo_type: '/data/relationships/foo',
|
||||
baz_id: '/data/relationships/baz',
|
||||
baz_type: '/data/relationships/baz' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
it 'generates reverse mapping for overriden has_one' do
|
||||
payload = {
|
||||
'data' => {
|
||||
'type' => 'foo',
|
||||
'relationships' => {
|
||||
'foo' => {
|
||||
'data' => nil
|
||||
},
|
||||
'baz' => {
|
||||
'data' => nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
klass = Class.new(JSONAPI::Deserializable::Resource) do
|
||||
has_one(:foo) do |_val, id, type|
|
||||
{ other_foo_id: id,
|
||||
other_foo_type: type }
|
||||
end
|
||||
end
|
||||
klass.configure do |config|
|
||||
config.default_has_one = proc do |key, _val, id, type|
|
||||
{ "custom_#{key}_id".to_sym => id,
|
||||
"custom_#{key}_type".to_sym => type }
|
||||
end
|
||||
end
|
||||
actual = klass.new(payload).reverse_mapping
|
||||
expected = { type: '/data/type',
|
||||
other_foo_id: '/data/relationships/foo',
|
||||
other_foo_type: '/data/relationships/foo',
|
||||
custom_baz_id: '/data/relationships/baz',
|
||||
custom_baz_type: '/data/relationships/baz' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
it 'generates reverse mapping for default has_many' do
|
||||
payload = {
|
||||
'data' => {
|
||||
'type' => 'foo',
|
||||
'relationships' => {
|
||||
'foo' => {
|
||||
'data' => []
|
||||
},
|
||||
'baz' => {
|
||||
'data' => []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
klass = JSONAPI::Deserializable::Resource
|
||||
actual = klass.new(payload).reverse_mapping
|
||||
expected = { type: '/data/type',
|
||||
foo_ids: '/data/relationships/foo',
|
||||
foo_types: '/data/relationships/foo',
|
||||
baz_ids: '/data/relationships/baz',
|
||||
baz_types: '/data/relationships/baz' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
|
||||
it 'generates reverse mapping for overriden has_many' do
|
||||
payload = {
|
||||
'data' => {
|
||||
'type' => 'foo',
|
||||
'relationships' => {
|
||||
'foo' => {
|
||||
'data' => []
|
||||
},
|
||||
'baz' => {
|
||||
'data' => []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
klass = Class.new(JSONAPI::Deserializable::Resource) do
|
||||
has_many(:foo) do |_val, ids, types|
|
||||
{ other_foo_ids: ids,
|
||||
other_foo_types: types }
|
||||
end
|
||||
end
|
||||
klass.configure do |config|
|
||||
config.default_has_many = proc do |key, _val, ids, types|
|
||||
{ "custom_#{key}_ids".to_sym => ids,
|
||||
"custom_#{key}_types".to_sym => types }
|
||||
end
|
||||
end
|
||||
actual = klass.new(payload).reverse_mapping
|
||||
expected = { type: '/data/type',
|
||||
other_foo_ids: '/data/relationships/foo',
|
||||
other_foo_types: '/data/relationships/foo',
|
||||
custom_baz_ids: '/data/relationships/baz',
|
||||
custom_baz_types: '/data/relationships/baz' }
|
||||
|
||||
expect(actual).to eq(expected)
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue
Block a user