yaks 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,28 @@
|
|
1
|
+
module Yaks
|
2
|
+
class NullResource
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def each
|
6
|
+
return to_enum unless block_given?
|
7
|
+
end
|
8
|
+
|
9
|
+
def attributes
|
10
|
+
Yaks::Hash()
|
11
|
+
end
|
12
|
+
|
13
|
+
def links
|
14
|
+
Yaks::List()
|
15
|
+
end
|
16
|
+
|
17
|
+
def subresources
|
18
|
+
Yaks::Hash()
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](*)
|
22
|
+
end
|
23
|
+
|
24
|
+
def collection?
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/yaks/primitivize.rb
CHANGED
@@ -1,24 +1,43 @@
|
|
1
1
|
module Yaks
|
2
2
|
class Primitivize
|
3
|
+
include Concord.new(:object)
|
4
|
+
|
5
|
+
# TODO Global config, make this extensible in a per-instance way
|
6
|
+
MAPPINGS = {}
|
7
|
+
|
3
8
|
def self.call(object)
|
4
|
-
new
|
9
|
+
new(object).call
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.map(*types, &blk)
|
13
|
+
types.each do |type|
|
14
|
+
MAPPINGS[type] = blk
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
map String, TrueClass, FalseClass, NilClass, Numeric do
|
19
|
+
object
|
20
|
+
end
|
21
|
+
|
22
|
+
map Symbol do
|
23
|
+
object.to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
map Hash, Hamster::Hash do
|
27
|
+
object.to_enum(:each).with_object({}) do |(key, value), output|
|
28
|
+
output[self.class.(key)] = self.class.(value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
map Enumerable, Hamster::Enumerable do
|
33
|
+
object.map(&self.class.method(:call)).to_a
|
5
34
|
end
|
6
35
|
|
7
|
-
def call
|
8
|
-
|
9
|
-
|
10
|
-
object
|
11
|
-
when Symbol
|
12
|
-
object.to_s
|
13
|
-
when Hash, Hamster::Hash
|
14
|
-
object.to_enum(:each).with_object({}) do |(key, value), output|
|
15
|
-
output[self.(key)] = self.(value)
|
16
|
-
end
|
17
|
-
when Enumerable, Hamster::Enumerable
|
18
|
-
object.map(&method(:call)).to_a
|
19
|
-
else
|
20
|
-
raise "don't know how to turn #{object.class} (#{object.inspect}) into a primitive"
|
36
|
+
def call
|
37
|
+
MAPPINGS.each do |pattern, block|
|
38
|
+
return instance_eval(&block) if pattern === object
|
21
39
|
end
|
40
|
+
raise "don't know how to turn #{object.class} (#{object.inspect}) into a primitive"
|
22
41
|
end
|
23
42
|
end
|
24
43
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module Yaks
|
3
|
+
# RFC6906 The "profile" link relation http://tools.ietf.org/search/rfc6906
|
4
|
+
class ProfileRegistry
|
5
|
+
class << self
|
6
|
+
include Util
|
7
|
+
|
8
|
+
def create(&blk)
|
9
|
+
blk ||= ->{}
|
10
|
+
Class.new(self).tap(&σ(:instance_eval, &blk)).new
|
11
|
+
end
|
12
|
+
|
13
|
+
def profile(type, uri)
|
14
|
+
profiles {|reg| reg.put(type, uri)}
|
15
|
+
end
|
16
|
+
|
17
|
+
def profiles
|
18
|
+
@profiles ||= Yaks::Hash()
|
19
|
+
@profiles = yield(@profiles) if block_given?
|
20
|
+
@profiles
|
21
|
+
end
|
22
|
+
|
23
|
+
def inherited(child)
|
24
|
+
child.profiles { @profiles }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_by_type(type)
|
29
|
+
self.class.profiles[type]
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_by_uri(by_uri)
|
33
|
+
self.class.profiles.detect {|type, uri| uri == by_uri}.first
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class NullProfileRegistry
|
38
|
+
def find_by_type(type)
|
39
|
+
type.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_by_uri(uri)
|
43
|
+
uri.to_sym
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class TemplateProfileRegistry
|
48
|
+
def initialize(template)
|
49
|
+
@template = URITemplate.new(template)
|
50
|
+
end
|
51
|
+
|
52
|
+
def find_by_type(type)
|
53
|
+
@template.expand(type: type)
|
54
|
+
end
|
55
|
+
|
56
|
+
def find_by_uri(uri)
|
57
|
+
@template.extract(uri).fetch(:type) { uri }.to_sym
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Yaks
|
2
|
+
class RelRegistry
|
3
|
+
end
|
4
|
+
|
5
|
+
class TemplateRelRegistry < RelRegistry
|
6
|
+
def initialize(template)
|
7
|
+
@template = URITemplate.new(template)
|
8
|
+
end
|
9
|
+
|
10
|
+
def lookup(source, destination)
|
11
|
+
@template.expand(:src => source, :dest => destination)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class NullRelRegistry < TemplateRelRegistry
|
16
|
+
def initialize
|
17
|
+
super('rel:src={src}&dest={dest}')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Yaks
|
2
|
+
class Resource
|
3
|
+
include Equalizer.new(:links, :attributes, :subresources)
|
4
|
+
include Enumerable, LinkLookup
|
5
|
+
|
6
|
+
attr_reader :attributes, :links, :subresources
|
7
|
+
|
8
|
+
def initialize(attributes, links, subresources)
|
9
|
+
@attributes = Yaks::Hash(attributes)
|
10
|
+
@links = Yaks::List(links)
|
11
|
+
@subresources = Yaks::Hash(subresources)
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](attr)
|
15
|
+
attributes[attr]
|
16
|
+
end
|
17
|
+
|
18
|
+
def each
|
19
|
+
return to_enum unless block_given?
|
20
|
+
yield self
|
21
|
+
end
|
22
|
+
|
23
|
+
def collection?
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Yaks
|
2
|
+
class Resource
|
3
|
+
class Link
|
4
|
+
include Equalizer.new(:rel, :uri, :options)
|
5
|
+
|
6
|
+
attr_reader :rel, :uri, :options
|
7
|
+
|
8
|
+
def initialize(rel, uri, options)
|
9
|
+
@rel, @uri, @options = rel, uri, options
|
10
|
+
end
|
11
|
+
|
12
|
+
def name
|
13
|
+
options[:name]
|
14
|
+
end
|
15
|
+
|
16
|
+
def templated?
|
17
|
+
options.fetch(:templated) { false }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/yaks/serializer.rb
CHANGED
@@ -1,64 +1,22 @@
|
|
1
1
|
module Yaks
|
2
2
|
class Serializer
|
3
|
-
|
4
|
-
|
3
|
+
extend Forwardable
|
4
|
+
include Util, SharedOptions
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
attr_reader :resource, :options
|
7
|
+
def_delegators :resource, :links, :attributes, :subresources
|
8
8
|
|
9
|
-
|
10
|
-
@serializer_lookup = options.fetch(:serializer_lookup) { Yaks.default_serializer_lookup }
|
11
|
-
@root_key = options.fetch(:root_key) { self.class._root_key }
|
12
|
-
end
|
13
|
-
|
14
|
-
def identity_key
|
15
|
-
self.class._identity_key
|
16
|
-
end
|
17
|
-
|
18
|
-
def attributes
|
19
|
-
self.class._attributes
|
20
|
-
end
|
21
|
-
|
22
|
-
def associations
|
23
|
-
self.class._associations
|
24
|
-
end
|
25
|
-
|
26
|
-
def filter(attributes)
|
27
|
-
attributes
|
28
|
-
end
|
9
|
+
protected :resource, :links, :attributes, :subresources, :options
|
29
10
|
|
30
|
-
def
|
31
|
-
|
11
|
+
def initialize(resource, options = {})
|
12
|
+
@resource = resource
|
13
|
+
@options = YAKS_DEFAULT_OPTIONS.merge(options)
|
32
14
|
end
|
33
15
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
37
|
-
serializable_attributes(object),
|
38
|
-
serializable_associations(object)
|
39
|
-
)
|
16
|
+
def profile_name
|
17
|
+
(profile = resource.profile) &&
|
18
|
+
profile_registry.find_by_uri(profile)
|
40
19
|
end
|
41
20
|
|
42
|
-
def serializable_attributes(object)
|
43
|
-
Hash(filter(attributes).map {|attr| [attr, send(attr)] })
|
44
|
-
end
|
45
|
-
|
46
|
-
def serializable_associations(object)
|
47
|
-
Hamster.enumerate(filter(associations.map(&:last)).each).map do |name|
|
48
|
-
type = associations.detect {|type, n| name == n }.first
|
49
|
-
if type == :has_one
|
50
|
-
obj = send(name)
|
51
|
-
serializer = serializer_lookup.(obj).new
|
52
|
-
objects = List(serializer.serializable_object(obj))
|
53
|
-
else
|
54
|
-
serializer = nil
|
55
|
-
objects = Hamster.enumerate(send(name).each).map do |obj|
|
56
|
-
serializer ||= serializer_lookup.(obj).new
|
57
|
-
serializer.serializable_object(obj)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
SerializableAssociation.new( SerializableCollection.new(name, :id, objects), type == :has_one )
|
61
|
-
end
|
62
|
-
end
|
63
21
|
end
|
64
22
|
end
|
data/lib/yaks/util.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
1
2
|
module Yaks
|
2
3
|
module Util
|
3
4
|
extend self
|
@@ -7,14 +8,84 @@ module Yaks
|
|
7
8
|
|
8
9
|
def underscore(str)
|
9
10
|
str.gsub(/::/, '/')
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
.gsub(/(?<!^|\/)([A-Z])(?=[a-z$])|(?<=[a-z])([A-Z])/, '_\1\2')
|
12
|
+
.tr("-", "_")
|
13
|
+
.downcase
|
13
14
|
end
|
14
15
|
|
15
16
|
def camelize(str)
|
16
|
-
str.gsub(/\/(.?)/)
|
17
|
-
|
17
|
+
str.gsub(/\/(.?)/) { "::#{ $1.upcase }" }
|
18
|
+
.gsub!(/(?:^|_)(.)/) { $1.upcase }
|
19
|
+
end
|
20
|
+
|
21
|
+
def List(*args)
|
22
|
+
Hamster.list(*args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def Hash(*args)
|
26
|
+
Hamster.hash(*args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def Set(*args)
|
30
|
+
Hamster.set(*args)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Turn what is maybe a Proc into its result (or itself)
|
34
|
+
#
|
35
|
+
# When input can be either a value or a proc that returns a value,
|
36
|
+
# this conversion function can be used to resolve the thing to a
|
37
|
+
# value.
|
38
|
+
#
|
39
|
+
# The proc can be evaluated (instance_evaled) in a certain context,
|
40
|
+
# or evaluated as a closure.
|
41
|
+
#
|
42
|
+
# @param [Object|Proc] maybe_proc
|
43
|
+
# A proc or a plain value
|
44
|
+
# @param [Object] context
|
45
|
+
# (optional) A context used to instance_eval the proc
|
46
|
+
def Resolve(maybe_proc, context = nil)
|
47
|
+
if maybe_proc.respond_to?(:to_proc)
|
48
|
+
if context
|
49
|
+
if maybe_proc.to_proc.arity > 0
|
50
|
+
context.instance_eval(&maybe_proc)
|
51
|
+
else
|
52
|
+
# In case it's a lambda with zero arity instance_eval fails
|
53
|
+
context.instance_exec(&maybe_proc)
|
54
|
+
end
|
55
|
+
else
|
56
|
+
maybe_proc.()
|
57
|
+
end
|
58
|
+
else
|
59
|
+
maybe_proc
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def curry_method(name)
|
64
|
+
method(name).to_proc.curry
|
65
|
+
end
|
66
|
+
alias μ curry_method
|
67
|
+
|
68
|
+
def identity_function
|
69
|
+
->(x) {x}
|
70
|
+
end
|
71
|
+
alias ι identity_function
|
72
|
+
|
73
|
+
def juxt(*procs)
|
74
|
+
->(*args) { procs.map &σ(:call, *args) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def curry_symbol(symbol, *args, &blk)
|
78
|
+
->(obj) { obj.method(symbol).to_proc.curry.(*args, &blk) }
|
79
|
+
end
|
80
|
+
alias σ curry_symbol
|
81
|
+
alias send_with_args curry_symbol
|
82
|
+
|
83
|
+
def extract_options(args)
|
84
|
+
if args.last.is_a? Hash
|
85
|
+
[args.take(args.count-1), args.last]
|
86
|
+
else
|
87
|
+
[args, {}]
|
88
|
+
end
|
18
89
|
end
|
19
90
|
|
20
91
|
end
|
data/lib/yaks/version.rb
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Mapping domain models to Resource objects' do
|
4
|
+
include_context 'fixtures'
|
5
|
+
include_context 'shorthands'
|
6
|
+
|
7
|
+
subject { mapper.to_resource }
|
8
|
+
let(:mapper) { FriendMapper.new(john) }
|
9
|
+
|
10
|
+
|
11
|
+
it { should be_a Yaks::Resource }
|
12
|
+
its(:attributes) { should eq Yaks::Hash(id: 1, name: 'john') }
|
13
|
+
its(:links) { should eq Yaks::List(
|
14
|
+
resource_link[:profile, 'friend'],
|
15
|
+
resource_link[:copyright, '/api/copyright/2024']
|
16
|
+
)}
|
17
|
+
|
18
|
+
its(:subresources) {
|
19
|
+
should eq Yaks::Hash(
|
20
|
+
"rel:src=friend&dest=pet_peeve" => resource[{id: 4, type: 'parsing with regexps'}, [resource_link[:profile, 'pet_peeve']]],
|
21
|
+
"rel:src=friend&dest=pets" => Yaks::CollectionResource.new(
|
22
|
+
[resource_link[:profile, 'pet']],
|
23
|
+
[
|
24
|
+
resource[{:id => 2, :species => "dog", :name => "boingboing"}, [resource_link[:profile, 'pet']]],
|
25
|
+
resource[{:id => 3, :species => "cat", :name => "wassup"}, [resource_link[:profile, 'pet']]]
|
26
|
+
]
|
27
|
+
)
|
28
|
+
)
|
29
|
+
}
|
30
|
+
end
|