yaks 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +13 -5
- data/.gitignore +1 -0
- data/.travis.yml +23 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +32 -6
- data/README.md +182 -80
- data/examples/hal01.rb +126 -0
- data/examples/jsonapi01.rb +68 -0
- data/examples/jsonapi02.rb +62 -0
- data/examples/jsonapi03.rb +86 -0
- data/lib/yaks.rb +55 -25
- data/lib/yaks/collection_mapper.rb +33 -0
- data/lib/yaks/collection_resource.rb +65 -0
- data/lib/yaks/default_policy.rb +13 -0
- data/lib/yaks/hal_serializer.rb +59 -0
- data/lib/yaks/json_api_serializer.rb +59 -0
- data/lib/yaks/link_lookup.rb +23 -0
- data/lib/yaks/mapper.rb +59 -0
- data/lib/yaks/mapper/association.rb +43 -0
- data/lib/yaks/mapper/class_methods.rb +36 -0
- data/lib/yaks/mapper/config.rb +79 -0
- data/lib/yaks/mapper/has_many.rb +15 -0
- data/lib/yaks/mapper/has_one.rb +10 -0
- data/lib/yaks/mapper/link.rb +74 -0
- data/lib/yaks/mapper/lookup.rb +19 -0
- data/lib/yaks/mapper/map_links.rb +17 -0
- data/lib/yaks/null_resource.rb +28 -0
- data/lib/yaks/primitivize.rb +34 -15
- data/lib/yaks/profile_registry.rb +60 -0
- data/lib/yaks/rel_registry.rb +20 -0
- data/lib/yaks/resource.rb +28 -0
- data/lib/yaks/resource/link.rb +21 -0
- data/lib/yaks/serializer.rb +11 -53
- data/lib/yaks/shared_options.rb +15 -0
- data/lib/yaks/util.rb +76 -5
- data/lib/yaks/version.rb +1 -1
- data/spec/integration/map_to_resource_spec.rb +30 -0
- data/spec/json/hal_plant_collection.json +34 -0
- data/spec/spec_helper.rb +10 -1
- data/spec/support/friends_mapper.rb +29 -0
- data/spec/support/pet_mapper.rb +5 -0
- data/spec/support/pet_peeve_mapper.rb +3 -0
- data/spec/support/serializers.rb +11 -11
- data/spec/support/shared_contexts.rb +47 -0
- data/spec/support/shorthands.rb +22 -0
- data/spec/yaks/collection_resource_spec.rb +9 -0
- data/spec/yaks/hal_serializer_spec.rb +9 -0
- data/spec/yaks/mapper/association_spec.rb +21 -0
- data/spec/yaks/mapper/class_methods_spec.rb +28 -0
- data/spec/yaks/mapper/config_spec.rb +77 -0
- data/spec/yaks/mapper/has_one_spec.rb +16 -0
- data/spec/yaks/mapper/link_spec.rb +42 -0
- data/spec/yaks/mapper/map_links_spec.rb +46 -0
- data/spec/yaks/mapper_spec.rb +47 -0
- data/spec/yaks/resource_spec.rb +23 -0
- data/yaks.gemspec +6 -3
- metadata +115 -27
- data/lib/yaks/dumper.rb +0 -23
- data/lib/yaks/fold_ams_compat.rb +0 -33
- data/lib/yaks/fold_json_api.rb +0 -61
- data/lib/yaks/serializable_association.rb +0 -21
- data/lib/yaks/serializable_collection.rb +0 -10
- data/lib/yaks/serializable_object.rb +0 -18
- data/lib/yaks/serializer/class_methods.rb +0 -76
- data/spec/integration_spec.rb +0 -57
- data/spec/yaks/json_api_folder_spec.rb +0 -63
- data/spec/yaks/serializer_spec.rb +0 -12
@@ -0,0 +1,59 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Yaks
|
4
|
+
class JsonApiSerializer < Serializer
|
5
|
+
def serialize
|
6
|
+
serialized = Yaks::Hash(
|
7
|
+
pluralize(profile_name.to_s) => resource.map(&method(:serialize_resource))
|
8
|
+
)
|
9
|
+
|
10
|
+
if options[:embed] == :resources
|
11
|
+
linked = resource.reduce(Yaks::Hash()) do |memo, res|
|
12
|
+
serialize_linked_subresources(res.subresources, memo)
|
13
|
+
end
|
14
|
+
serialized = serialized.put('linked', linked)
|
15
|
+
end
|
16
|
+
|
17
|
+
Primitivize.( serialized )
|
18
|
+
end
|
19
|
+
alias to_json_api serialize
|
20
|
+
|
21
|
+
def serialize_resource(resource)
|
22
|
+
result = resource.attributes
|
23
|
+
result = result.merge(Yaks::Hash(:links => serialize_links(resource.subresources))) unless resource.subresources.empty?
|
24
|
+
result
|
25
|
+
end
|
26
|
+
|
27
|
+
def serialize_links(subresources)
|
28
|
+
Yaks::Hash(subresources.map &method(:serialize_link))
|
29
|
+
end
|
30
|
+
|
31
|
+
def serialize_link(name, resource)
|
32
|
+
if options[:embed] == :links
|
33
|
+
[ name, resource.uri ]
|
34
|
+
else
|
35
|
+
[ name, resource.collection? ? resource.map(&curry_symbol(:[], :id)) : resource[:id] ]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def serialize_linked_subresources(subresources, linked)
|
40
|
+
subresources.reduce(linked) do |memo, name, resources|
|
41
|
+
serialize_linked_resources(resources, memo)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def serialize_linked_resources(resources, linked)
|
46
|
+
resources.reduce(linked) do |memo, resource|
|
47
|
+
serialize_subresource(resource, memo)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# {shows => [{id: 3, name: 'foo'}]}
|
52
|
+
def serialize_subresource(resource, linked)
|
53
|
+
key = pluralize(profile_registry.find_by_uri(resource.profile).to_s)
|
54
|
+
set = linked.fetch(key) { Hamster.set }
|
55
|
+
linked = linked.put(key, set << serialize_resource(resource))
|
56
|
+
serialize_linked_subresources(resource.subresources, linked)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Yaks
|
2
|
+
module LinkLookup
|
3
|
+
|
4
|
+
def uri
|
5
|
+
self_link = links_by_rel(:self).first
|
6
|
+
self_link.uri if self_link
|
7
|
+
end
|
8
|
+
|
9
|
+
def profile
|
10
|
+
link = links_by_rel(:profile).first
|
11
|
+
link.uri if link
|
12
|
+
end
|
13
|
+
|
14
|
+
def profile_type
|
15
|
+
profile_registry.find_by_uri(profile)
|
16
|
+
end
|
17
|
+
|
18
|
+
def links_by_rel(rel)
|
19
|
+
links.select {|link| link.rel == rel}
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
data/lib/yaks/mapper.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Yaks
|
4
|
+
class Mapper
|
5
|
+
extend ClassMethods, Forwardable
|
6
|
+
include Util, MapLinks, SharedOptions
|
7
|
+
|
8
|
+
def_delegators 'self.class', :config
|
9
|
+
def_delegators :config, :attributes, :links, :associations
|
10
|
+
|
11
|
+
attr_reader :subject, :options
|
12
|
+
private :subject, :options
|
13
|
+
alias object subject
|
14
|
+
|
15
|
+
def initialize(subject, options = {})
|
16
|
+
@subject = subject
|
17
|
+
@options = YAKS_DEFAULT_OPTIONS.merge(options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_resource
|
21
|
+
return NullResource.new if subject.nil?
|
22
|
+
|
23
|
+
Resource.new(
|
24
|
+
map_attributes,
|
25
|
+
map_links,
|
26
|
+
map_subresources
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def profile_type
|
31
|
+
config.profile || policy.derive_profile_from_mapper(self)
|
32
|
+
end
|
33
|
+
|
34
|
+
def map_attributes
|
35
|
+
filter(attributes).map &juxt(identity_function, method(:load_attribute))
|
36
|
+
end
|
37
|
+
|
38
|
+
def map_subresources
|
39
|
+
filtered = filter(associations.map(&:name))
|
40
|
+
associations.select{|assoc| filtered.include? assoc.name}.map do |association|
|
41
|
+
association.map_to_resource_pair(profile_type, method(:load_association), options)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def load_attribute(name)
|
46
|
+
respond_to?(name) ? send(name) : subject.send(name)
|
47
|
+
end
|
48
|
+
alias load_association load_attribute
|
49
|
+
|
50
|
+
def profile
|
51
|
+
config.profile || policy.derive_missing_profile_from_mapper(self)
|
52
|
+
end
|
53
|
+
|
54
|
+
def filter(attrs)
|
55
|
+
attrs
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Yaks
|
2
|
+
class Mapper
|
3
|
+
class Association
|
4
|
+
include Equalizer.new(:name, :_mapper, :links)
|
5
|
+
include SharedOptions
|
6
|
+
|
7
|
+
attr_reader :name, :key, :links, :options
|
8
|
+
private :links, :options
|
9
|
+
|
10
|
+
def initialize(name, key, mapper, links, options)
|
11
|
+
@name = name
|
12
|
+
@key = key
|
13
|
+
@mapper = mapper
|
14
|
+
@links = links
|
15
|
+
@options = options
|
16
|
+
end
|
17
|
+
|
18
|
+
def self_link
|
19
|
+
links.detect {|link| link.rel? :self }
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param [Symbol] src_type
|
23
|
+
# The profile type of the resource that contains the association
|
24
|
+
# @param [Proc] loader
|
25
|
+
# A lambda that can retrieve an association by its name
|
26
|
+
# @param [Hash] options
|
27
|
+
def map_to_resource_pair(src_type, loader, options)
|
28
|
+
[ options[:rel_registry].lookup(src_type, key), map_resource(loader.(name), options) ]
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def mapper(opts = nil)
|
34
|
+
return _mapper unless _mapper == Undefined
|
35
|
+
opts[:policy].derive_missing_mapper_for_association(self)
|
36
|
+
end
|
37
|
+
|
38
|
+
def _mapper
|
39
|
+
@mapper
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Yaks
|
4
|
+
class Mapper
|
5
|
+
module ClassMethods
|
6
|
+
include Forwardable
|
7
|
+
include Util
|
8
|
+
|
9
|
+
CONFIG_METHODS = [
|
10
|
+
:attributes,
|
11
|
+
:link,
|
12
|
+
:profile,
|
13
|
+
# :embed,
|
14
|
+
:has_one,
|
15
|
+
:has_many
|
16
|
+
]
|
17
|
+
|
18
|
+
def config
|
19
|
+
@config ||= Config.new
|
20
|
+
@config = yield(@config) if block_given?
|
21
|
+
@config
|
22
|
+
end
|
23
|
+
|
24
|
+
def inherited(child)
|
25
|
+
child.config { @config }
|
26
|
+
end
|
27
|
+
|
28
|
+
CONFIG_METHODS.each do |method_name|
|
29
|
+
define_method method_name do |*args|
|
30
|
+
config &σ(method_name, *args)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Yaks
|
2
|
+
class Mapper
|
3
|
+
class Config
|
4
|
+
include Equalizer.new(:attributes)
|
5
|
+
|
6
|
+
def initialize(attributes = Hamster.list, links = Hamster.list, associations = Hamster.list, profile = nil)
|
7
|
+
@attributes = attributes
|
8
|
+
@links = links
|
9
|
+
@associations = associations
|
10
|
+
@profile = profile
|
11
|
+
freeze
|
12
|
+
end
|
13
|
+
|
14
|
+
def new(updates)
|
15
|
+
self.class.new(
|
16
|
+
updates.fetch(:attributes) { attributes },
|
17
|
+
updates.fetch(:links) { links },
|
18
|
+
updates.fetch(:associations) { associations },
|
19
|
+
updates.fetch(:profile) { profile },
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def attributes(*attrs)
|
24
|
+
return @attributes if attrs.empty?
|
25
|
+
new(
|
26
|
+
attributes: @attributes + attrs.to_list
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def link(rel, template, options = {})
|
31
|
+
new(
|
32
|
+
links: @links.cons(Link.new(rel, template, options))
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def profile(type = Undefined)
|
37
|
+
return @profile if type == Undefined
|
38
|
+
new(
|
39
|
+
profile: type
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
# key
|
44
|
+
# embed_style
|
45
|
+
# rel
|
46
|
+
# (profile)
|
47
|
+
|
48
|
+
def has_one(name, options = {})
|
49
|
+
add_association(HasOne, name, options)
|
50
|
+
end
|
51
|
+
|
52
|
+
def has_many(name, options = {})
|
53
|
+
add_association(HasMany, name, options)
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_association(type, name, options = {})
|
57
|
+
new(
|
58
|
+
associations: @associations.cons(
|
59
|
+
type.new(
|
60
|
+
name,
|
61
|
+
options.fetch(:as) { name },
|
62
|
+
options.fetch(:mapper) { Undefined },
|
63
|
+
options.fetch(:links) { Yaks::List() },
|
64
|
+
options.reject {|k,v| [:as, :mapper, :links].include?(k) }
|
65
|
+
)
|
66
|
+
)
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
def links
|
71
|
+
@links
|
72
|
+
end
|
73
|
+
|
74
|
+
def associations
|
75
|
+
@associations
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Yaks
|
2
|
+
class Mapper
|
3
|
+
class HasMany < Association
|
4
|
+
def map_resource(collection, opts)
|
5
|
+
opts = opts.merge(options)
|
6
|
+
collection_mapper(opts).new(collection, mapper(opts), opts).to_resource
|
7
|
+
end
|
8
|
+
|
9
|
+
def collection_mapper(opts)
|
10
|
+
opts = opts.merge(options)
|
11
|
+
opts.fetch(:collection_mapper) { CollectionMapper }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Yaks
|
2
|
+
class Mapper
|
3
|
+
class Link
|
4
|
+
include Concord.new(:rel, :template, :options)
|
5
|
+
include Util
|
6
|
+
|
7
|
+
def initialize(rel, template, options = {})
|
8
|
+
@rel, @template, @options = rel, template, options
|
9
|
+
end
|
10
|
+
|
11
|
+
def rel?(rel)
|
12
|
+
self.rel == rel
|
13
|
+
end
|
14
|
+
|
15
|
+
def expand?
|
16
|
+
options.fetch(:expand) {true}
|
17
|
+
end
|
18
|
+
|
19
|
+
def expand_with(callable)
|
20
|
+
return template unless expand?
|
21
|
+
expand(
|
22
|
+
variables.map.with_object({}) do |var, hsh|
|
23
|
+
hsh[var] = callable.(var)
|
24
|
+
end
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def map_to_resource_link(mapper)
|
29
|
+
make_resource_link(
|
30
|
+
rel,
|
31
|
+
expand_with(mapper.method(:load_attribute)),
|
32
|
+
resource_link_options(mapper)
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def expand(variables)
|
37
|
+
uri_template.expand(variables)
|
38
|
+
end
|
39
|
+
|
40
|
+
def uri_template
|
41
|
+
@uri_template ||= URITemplate.new(template)
|
42
|
+
end
|
43
|
+
|
44
|
+
def variables
|
45
|
+
uri_template.variables
|
46
|
+
end
|
47
|
+
|
48
|
+
# Link properties defined in HAL
|
49
|
+
# href
|
50
|
+
# templated
|
51
|
+
# typed
|
52
|
+
# deprecation
|
53
|
+
# name
|
54
|
+
# profile
|
55
|
+
# title
|
56
|
+
# hreflang
|
57
|
+
|
58
|
+
def resource_link_options(mapper)
|
59
|
+
options = self.options
|
60
|
+
options = options.merge(title: resolve_title(options[:title], mapper)) if options.has_key?(:title)
|
61
|
+
options = options.merge( templated: true ) unless expand?
|
62
|
+
options.reject{|k,v| [:expand].include? k}
|
63
|
+
end
|
64
|
+
|
65
|
+
def resolve_title(title_proc, mapper)
|
66
|
+
Resolve(title_proc, mapper)
|
67
|
+
end
|
68
|
+
|
69
|
+
def make_resource_link(rel, uri, options)
|
70
|
+
Resource::Link.new(rel, uri, options)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Yaks
|
2
|
+
class Mapper
|
3
|
+
module Lookup
|
4
|
+
|
5
|
+
def serializer_for(object_or_key)
|
6
|
+
serializer_class_for(object_or_key).new(object_or_key, options)
|
7
|
+
end
|
8
|
+
|
9
|
+
def serializer_class_for(object_or_key)
|
10
|
+
if object_or_key.respond_to?(:to_str) || object_or_key.is_a?(Symbol)
|
11
|
+
serializer_lookup.(Object.const_get(Util.singular(Util.camelize(obj.to_s))))
|
12
|
+
else
|
13
|
+
serializer_lookup.(object_or_key)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Yaks
|
4
|
+
class Mapper
|
5
|
+
module MapLinks
|
6
|
+
|
7
|
+
def map_links
|
8
|
+
mapped = links.map &send_with_args(:map_to_resource_link, self)
|
9
|
+
unless links.any? {|link| link.rel? :profile }
|
10
|
+
mapped = mapped.cons(Resource::Link.new(:profile, profile_registry.find_by_type(profile_type), {}))
|
11
|
+
end
|
12
|
+
mapped
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|