yaks 0.4.0.rc1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -3
- data/README.md +24 -0
- data/bench/bench.rb +17 -0
- data/lib/yaks/collection_mapper.rb +20 -9
- data/lib/yaks/collection_resource.rb +5 -5
- data/lib/yaks/config.rb +13 -6
- data/lib/yaks/default_policy.rb +20 -8
- data/lib/yaks/mapper/has_many.rb +6 -5
- data/lib/yaks/mapper/has_one.rb +3 -3
- data/lib/yaks/mapper.rb +4 -4
- data/lib/yaks/serializer.rb +3 -3
- data/lib/yaks/version.rb +1 -1
- data/lib/yaks.rb +2 -0
- data/spec/acceptance/acceptance_spec.rb +0 -2
- data/spec/fixture_helpers.rb +14 -0
- data/spec/integration/map_to_resource_spec.rb +4 -3
- data/spec/json/hal_plant_collection.json +1 -1
- data/spec/spec_helper.rb +3 -13
- data/spec/support/shared_contexts.rb +7 -1
- data/spec/unit/yaks/collection_mapper_spec.rb +31 -25
- data/spec/unit/yaks/collection_resource_spec.rb +2 -2
- data/spec/unit/yaks/config_spec.rb +2 -2
- data/spec/unit/yaks/mapper/has_many_spec.rb +15 -8
- data/spec/unit/yaks/mapper/link_spec.rb +6 -1
- data/spec/unit/yaks/mapper_spec.rb +15 -16
- data/spec/unit/yaks/serializer_spec.rb +1 -1
- data/yaks.gemspec +3 -1
- metadata +37 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f3292feb7cef328921828cc97b68f397d23b7269
|
4
|
+
data.tar.gz: 0d074d9a0ea85e905d22572182904fe4f6fb3e36
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a61962b2c69d90ba0b4ccbd2223bc14b27e0ee5ae773bcb12392c7d5bb9a9969497e5e86ae85761ebbab732230dd14f59dc3e8241b35282d9630c39b5c0f41fe
|
7
|
+
data.tar.gz: 12d9e712b28185dc1409518045da51d4eb4a9fd32c7be42a55747e7fd33b4698728a3c46bf170f54183576f7437eafa54db795fb13d4177594b9de62ed418165
|
data/CHANGELOG.md
CHANGED
@@ -1,10 +1,18 @@
|
|
1
|
-
# v0.4.0
|
1
|
+
# v0.4.0
|
2
|
+
|
3
|
+
* Introduce after {} post-processing hook
|
4
|
+
* Streamline interfaces and variable names
|
5
|
+
* Improve deriving mappers automatically, even with Rails style autoloading
|
6
|
+
* Give CollectionResource a members_rel, for HAL-like formats with no top-level collection concept
|
7
|
+
* Switch back to using `src` and `dest` as the rel-template keys
|
8
|
+
|
9
|
+
# v0.4.0.rc1
|
2
10
|
|
3
11
|
* Introduce Yaks.new as the main public interface
|
4
12
|
* Fix JsonApiSerializer and make it compliant with current spec
|
5
13
|
* Remove Hamster dependency, Yaks new uses plain old Ruby arrays and hashes
|
6
|
-
* Remove RelRegistry and ProfileRegistry in favor of a simpler explicit syntax + policy based fallback
|
7
|
-
* Add more policy derivation hooks, plus make DefaultPolicy template for rel urls configurable
|
14
|
+
* Remove `RelRegistry` and `ProfileRegistry` in favor of a simpler explicit syntax + policy based fallback
|
15
|
+
* Add more policy derivation hooks, plus make `DefaultPolicy` template for rel urls configurable
|
8
16
|
* Optionally take a Rack env hash, pass it around so mappers can inspect it
|
9
17
|
* Honor the HTTP Accept header if it is present in the rack env
|
10
18
|
* Add map_to_primitive configuration option
|
data/README.md
CHANGED
@@ -314,6 +314,30 @@ yaks = Yaks.new do
|
|
314
314
|
end
|
315
315
|
```
|
316
316
|
|
317
|
+
## Primitives
|
318
|
+
|
319
|
+
For JSON based formats, a final step in serializing is to turn the nested data into primitives that have a JSON equivalent. For example, JSON has no notion of symbols or dates. If your mappers return these types as attributes, then Yaks needs to know how to turn these into primitives. To add extra types, use `map_to_primitive`
|
320
|
+
|
321
|
+
```ruby
|
322
|
+
Yaks.new do
|
323
|
+
map_to_primitive Date, Time, DateTime do
|
324
|
+
object.iso8601
|
325
|
+
end
|
326
|
+
end
|
327
|
+
```
|
328
|
+
|
329
|
+
This can also be used to transform alternative data structures, like those from Hamster, into Ruby arrays and hashes. Use `call()` to recursively turn things into primitives.
|
330
|
+
|
331
|
+
```ruby
|
332
|
+
Yaks.new do
|
333
|
+
map_to_primitive Hamster::Vector, Hamster::List do
|
334
|
+
object.map do |item|
|
335
|
+
call(item)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
```
|
340
|
+
|
317
341
|
## Usage
|
318
342
|
|
319
343
|
Yaks is used in production by [Ticketsolve](http://www.ticketsolve.com/). You can find an example API endpoint [here](http://leicestersquaretheatre.ticketsolve.com/api).
|
data/bench/bench.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
require 'benchmark/ips'
|
3
|
+
require 'yaks'
|
4
|
+
|
5
|
+
require_relative '../spec/acceptance/models'
|
6
|
+
require_relative '../spec/fixture_helpers'
|
7
|
+
|
8
|
+
Benchmark.ips do |x|
|
9
|
+
$yaks = Yaks.new
|
10
|
+
|
11
|
+
input = FixtureHelpers.load_yaml_fixture 'confucius'
|
12
|
+
|
13
|
+
x.report "Simple HAL mapping" do
|
14
|
+
$yaks.serialize(input)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -5,34 +5,45 @@ module Yaks
|
|
5
5
|
attr_reader :collection
|
6
6
|
alias collection object
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
super(
|
8
|
+
def initialize(context)
|
9
|
+
super(context)
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
13
|
-
context
|
12
|
+
def member_mapper
|
13
|
+
context.fetch(:member_mapper) do
|
14
|
+
if collection.first
|
15
|
+
mapper_for_model(collection.first)
|
16
|
+
end
|
17
|
+
end
|
14
18
|
end
|
15
19
|
|
16
|
-
def
|
20
|
+
def call(collection)
|
21
|
+
@object = collection
|
22
|
+
|
17
23
|
CollectionResource.new(
|
18
24
|
type: collection_type,
|
25
|
+
members_rel: members_rel,
|
19
26
|
links: map_links,
|
20
27
|
attributes: map_attributes,
|
21
28
|
members: collection.map do |obj|
|
22
|
-
mapper_for_model(obj).new(
|
29
|
+
mapper_for_model(obj).new(context).call(obj)
|
23
30
|
end
|
24
31
|
)
|
25
32
|
end
|
26
33
|
|
27
34
|
private
|
28
35
|
|
36
|
+
def members_rel
|
37
|
+
policy.expand_rel( 'collection', pluralize( collection_type ) )
|
38
|
+
end
|
39
|
+
|
29
40
|
def collection_type
|
30
|
-
return unless
|
31
|
-
|
41
|
+
return unless member_mapper
|
42
|
+
member_mapper.config.type || policy.derive_type_from_mapper_class(member_mapper)
|
32
43
|
end
|
33
44
|
|
34
45
|
def mapper_for_model(model)
|
35
|
-
context.fetch(:
|
46
|
+
context.fetch(:member_mapper) do
|
36
47
|
policy.derive_mapper_from_object(model)
|
37
48
|
end
|
38
49
|
end
|
@@ -15,18 +15,19 @@ module Yaks
|
|
15
15
|
# In the second case a collection has a single "subresource", being its
|
16
16
|
# members.
|
17
17
|
class CollectionResource < Resource
|
18
|
-
include Equalizer.new(:type, :links, :attributes, :members)
|
18
|
+
include Equalizer.new(:type, :links, :attributes, :members, :members_rel)
|
19
19
|
include Enumerable
|
20
20
|
|
21
21
|
extend Forwardable
|
22
22
|
|
23
|
-
attr_reader :type, :links, :members
|
23
|
+
attr_reader :type, :links, :members, :members_rel
|
24
24
|
|
25
25
|
def_delegators :members, :each
|
26
26
|
|
27
27
|
def initialize(options)
|
28
28
|
super
|
29
|
-
@members
|
29
|
+
@members = options.fetch(:members, [])
|
30
|
+
@members_rel = options.fetch(:members_rel, 'members')
|
30
31
|
end
|
31
32
|
|
32
33
|
# Make a CollectionResource quack like a resource.
|
@@ -44,8 +45,7 @@ module Yaks
|
|
44
45
|
# :(
|
45
46
|
def subresources
|
46
47
|
if members.any?
|
47
|
-
|
48
|
-
{ profile_link => self }
|
48
|
+
{ members_rel => self }
|
49
49
|
else
|
50
50
|
{}
|
51
51
|
end
|
data/lib/yaks/config.rb
CHANGED
@@ -33,11 +33,16 @@ module Yaks
|
|
33
33
|
def mapper_namespace(namespace)
|
34
34
|
config.policy_options[:namespace] = namespace
|
35
35
|
end
|
36
|
+
alias namespace mapper_namespace
|
36
37
|
|
37
38
|
def map_to_primitive(*args, &blk)
|
38
39
|
config.primitivize.map(*args, &blk)
|
39
40
|
end
|
40
41
|
|
42
|
+
def after(&block)
|
43
|
+
config.steps << block
|
44
|
+
end
|
45
|
+
|
41
46
|
DefaultPolicy.public_instance_methods(false).each do |method|
|
42
47
|
define_method method do |&blk|
|
43
48
|
@policies << proc {
|
@@ -47,13 +52,14 @@ module Yaks
|
|
47
52
|
end
|
48
53
|
end
|
49
54
|
|
50
|
-
attr_accessor :format_options, :default_format, :policy_class, :policy_options, :primitivize
|
55
|
+
attr_accessor :format_options, :default_format, :policy_class, :policy_options, :primitivize, :steps
|
51
56
|
|
52
57
|
def initialize(&blk)
|
53
58
|
@format_options = Hash.new({})
|
54
59
|
@default_format = :hal
|
55
60
|
@policy_options = {}
|
56
|
-
@primitivize
|
61
|
+
@primitivize = Primitivize.create
|
62
|
+
@steps = [ @primitivize ]
|
57
63
|
DSL.new(self, &blk)
|
58
64
|
end
|
59
65
|
|
@@ -64,7 +70,7 @@ module Yaks
|
|
64
70
|
def serializer_class(opts, env)
|
65
71
|
if env.key? 'HTTP_ACCEPT'
|
66
72
|
accept = Rack::Accept::Charset.new(env['HTTP_ACCEPT'])
|
67
|
-
mime_type = accept.best_of(Yaks::Serializer.mime_types)
|
73
|
+
mime_type = accept.best_of(Yaks::Serializer.mime_types.values)
|
68
74
|
return Yaks::Serializer.by_mime_type(mime_type) if mime_type
|
69
75
|
end
|
70
76
|
Yaks::Serializer.by_name(opts.fetch(:format) { @default_format })
|
@@ -82,15 +88,16 @@ module Yaks
|
|
82
88
|
# Yaks::Resource => serialized structure
|
83
89
|
# serialized structure => serialized flat
|
84
90
|
|
85
|
-
def
|
91
|
+
def call(object, opts = {}, env = {})
|
86
92
|
context = {
|
87
93
|
policy: policy,
|
88
94
|
env: env
|
89
95
|
}
|
90
96
|
mapper = opts.fetch(:mapper) { policy.derive_mapper_from_object(object) }
|
91
|
-
resource = mapper.new(
|
97
|
+
resource = mapper.new(context).call(object)
|
92
98
|
serialized = serializer_class(opts, env).new(resource, format_options[format_name(opts)]).call
|
93
|
-
|
99
|
+
steps.inject(serialized) {|memo, step| step.call(memo) }
|
94
100
|
end
|
101
|
+
alias serialize call
|
95
102
|
end
|
96
103
|
end
|
data/lib/yaks/default_policy.rb
CHANGED
@@ -3,7 +3,7 @@ module Yaks
|
|
3
3
|
include Util
|
4
4
|
|
5
5
|
DEFAULTS = {
|
6
|
-
rel_template: "rel:src={
|
6
|
+
rel_template: "rel:src={src}&dest={dest}",
|
7
7
|
namespace: Kernel
|
8
8
|
}
|
9
9
|
|
@@ -15,9 +15,18 @@ module Yaks
|
|
15
15
|
|
16
16
|
def derive_mapper_from_object(model)
|
17
17
|
if model.respond_to? :to_ary
|
18
|
-
if
|
19
|
-
|
18
|
+
if m = model.first
|
19
|
+
name = m.class.name.split('::').last + 'CollectionMapper'
|
20
|
+
begin
|
21
|
+
return @options[:namespace].const_get(name)
|
22
|
+
rescue NameError
|
23
|
+
end
|
20
24
|
end
|
25
|
+
begin
|
26
|
+
return @options[:namespace].const_get(:CollectionMapper)
|
27
|
+
rescue NameError
|
28
|
+
end
|
29
|
+
Yaks::CollectionMapper
|
21
30
|
else
|
22
31
|
name = model.class.name.split('::').last
|
23
32
|
@options[:namespace].const_get(name + 'Mapper')
|
@@ -25,19 +34,22 @@ module Yaks
|
|
25
34
|
end
|
26
35
|
|
27
36
|
def derive_type_from_mapper_class(mapper_class)
|
28
|
-
underscore(mapper_class.
|
37
|
+
underscore(mapper_class.name.split('::').last.sub(/Mapper$/, ''))
|
29
38
|
end
|
30
39
|
|
31
40
|
def derive_mapper_from_association(association)
|
32
|
-
|
41
|
+
@options[:namespace].const_get("#{camelize(singularize(association.name.to_s))}Mapper")
|
33
42
|
end
|
34
43
|
|
35
44
|
def derive_rel_from_association(mapper, association)
|
45
|
+
expand_rel( derive_type_from_mapper_class(mapper.class), association.name )
|
46
|
+
end
|
47
|
+
|
48
|
+
def expand_rel(src_name, dest_name)
|
36
49
|
URITemplate.new(@options[:rel_template]).expand(
|
37
|
-
|
38
|
-
|
50
|
+
src: src_name,
|
51
|
+
dest: dest_name
|
39
52
|
)
|
40
53
|
end
|
41
|
-
|
42
54
|
end
|
43
55
|
end
|
data/lib/yaks/mapper/has_many.rb
CHANGED
@@ -2,14 +2,15 @@ module Yaks
|
|
2
2
|
class Mapper
|
3
3
|
class HasMany < Association
|
4
4
|
def map_resource(collection, context)
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
policy = context.fetch(:policy)
|
6
|
+
member_mapper = association_mapper(policy)
|
7
|
+
context = context.merge(member_mapper: member_mapper)
|
8
|
+
collection_mapper(collection, policy).new(context).call(collection)
|
8
9
|
end
|
9
10
|
|
10
|
-
def collection_mapper
|
11
|
+
def collection_mapper(collection, policy)
|
11
12
|
return @collection_mapper unless @collection_mapper.equal? Undefined
|
12
|
-
|
13
|
+
policy.derive_mapper_from_object(collection)
|
13
14
|
end
|
14
15
|
end
|
15
16
|
end
|
data/lib/yaks/mapper/has_one.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
module Yaks
|
2
2
|
class Mapper
|
3
3
|
class HasOne < Association
|
4
|
-
def map_resource(
|
4
|
+
def map_resource(object, context)
|
5
5
|
association_mapper(context.fetch(:policy))
|
6
|
-
.new(
|
7
|
-
.
|
6
|
+
.new(context)
|
7
|
+
.call(object)
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
data/lib/yaks/mapper.rb
CHANGED
@@ -10,8 +10,7 @@ module Yaks
|
|
10
10
|
|
11
11
|
attr_reader :object, :context
|
12
12
|
|
13
|
-
def initialize(
|
14
|
-
@object = object
|
13
|
+
def initialize(context)
|
15
14
|
@context = context
|
16
15
|
end
|
17
16
|
|
@@ -23,7 +22,9 @@ module Yaks
|
|
23
22
|
context.fetch(:env)
|
24
23
|
end
|
25
24
|
|
26
|
-
def call
|
25
|
+
def call(object)
|
26
|
+
@object = object
|
27
|
+
|
27
28
|
return NullResource.new if object.nil?
|
28
29
|
|
29
30
|
Resource.new(
|
@@ -33,7 +34,6 @@ module Yaks
|
|
33
34
|
subresources: map_subresources
|
34
35
|
)
|
35
36
|
end
|
36
|
-
alias to_resource call
|
37
37
|
|
38
38
|
def map_attributes
|
39
39
|
filter(attributes).each_with_object({}) do |attr, memo|
|
data/lib/yaks/serializer.rb
CHANGED
@@ -24,7 +24,7 @@ module Yaks
|
|
24
24
|
@serializers[name] = klass
|
25
25
|
|
26
26
|
@mime_types ||= {}
|
27
|
-
@mime_types[mime_type] = klass
|
27
|
+
@mime_types[mime_type] = [name, klass]
|
28
28
|
end
|
29
29
|
|
30
30
|
def by_name(name)
|
@@ -32,11 +32,11 @@ module Yaks
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def by_mime_type(mime_type)
|
35
|
-
@mime_types.fetch(mime_type)
|
35
|
+
@mime_types.fetch(mime_type)[1]
|
36
36
|
end
|
37
37
|
|
38
38
|
def mime_types
|
39
|
-
@mime_types.
|
39
|
+
@mime_types.inject({}) {|memo, (mime_type, (name, _))| memo[name] = mime_type ; memo }
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
data/lib/yaks/version.rb
CHANGED
data/lib/yaks.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'forwardable'
|
4
4
|
require 'set'
|
5
|
+
require 'pathname'
|
5
6
|
|
6
7
|
require 'concord'
|
7
8
|
require 'inflection'
|
@@ -16,6 +17,7 @@ require 'yaks/default_policy'
|
|
16
17
|
|
17
18
|
module Yaks
|
18
19
|
Undefined = Object.new
|
20
|
+
Root = Pathname(__FILE__).join('../..')
|
19
21
|
|
20
22
|
YAKS_DEFAULT_OPTIONS = {
|
21
23
|
singular_links: [:self, :profile]
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module FixtureHelpers
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def load_json_fixture(name)
|
8
|
+
JSON.parse(Yaks::Root.join('spec/json', name + '.json').read)
|
9
|
+
end
|
10
|
+
|
11
|
+
def load_yaml_fixture(name)
|
12
|
+
YAML.load(Yaks::Root.join('spec/yaml', name + '.yaml').read)
|
13
|
+
end
|
14
|
+
end
|
@@ -3,8 +3,8 @@ require 'spec_helper'
|
|
3
3
|
RSpec.describe 'Mapping domain models to Resource objects' do
|
4
4
|
include_context 'fixtures'
|
5
5
|
|
6
|
-
subject { mapper.
|
7
|
-
let(:mapper) { FriendMapper.new(
|
6
|
+
subject { mapper.call(john) }
|
7
|
+
let(:mapper) { FriendMapper.new(policy: Yaks::DefaultPolicy.new, env: {}) }
|
8
8
|
|
9
9
|
it { should be_a Yaks::Resource }
|
10
10
|
its(:type) { should eql 'friend' }
|
@@ -19,7 +19,8 @@ RSpec.describe 'Mapping domain models to Resource objects' do
|
|
19
19
|
members: [
|
20
20
|
Yaks::Resource.new(type: 'pet', attributes: {:id => 2, :species => "dog", :name => "boingboing"}),
|
21
21
|
Yaks::Resource.new(type: 'pet', attributes: {:id => 3, :species => "cat", :name => "wassup"})
|
22
|
-
]
|
22
|
+
],
|
23
|
+
members_rel: 'rel:src=collection&dest=pets'
|
23
24
|
)
|
24
25
|
)
|
25
26
|
}
|
data/spec/spec_helper.rb
CHANGED
@@ -1,13 +1,9 @@
|
|
1
|
-
require 'pathname'
|
2
1
|
require 'rspec/its'
|
3
2
|
|
4
|
-
ROOT = Pathname(__FILE__).join('../..')
|
5
|
-
|
6
|
-
$LOAD_PATH.unshift(ROOT.join('lib'))
|
7
|
-
|
8
3
|
require 'yaks'
|
9
4
|
require 'virtus'
|
10
|
-
|
5
|
+
|
6
|
+
require 'fixture_helpers'
|
11
7
|
|
12
8
|
require_relative 'support/models'
|
13
9
|
require_relative 'support/pet_mapper'
|
@@ -17,15 +13,9 @@ require_relative 'support/fixtures'
|
|
17
13
|
require_relative 'support/shared_contexts'
|
18
14
|
require_relative 'support/youtypeit_models_mappers'
|
19
15
|
|
20
|
-
def load_json_fixture(name)
|
21
|
-
JSON.parse(ROOT.join('spec/json', name + '.json').read)
|
22
|
-
end
|
23
|
-
|
24
|
-
def load_yaml_fixture(name)
|
25
|
-
YAML.load(ROOT.join('spec/yaml', name + '.yaml').read)
|
26
|
-
end
|
27
16
|
|
28
17
|
RSpec.configure do |rspec|
|
18
|
+
rspec.include FixtureHelpers
|
29
19
|
rspec.backtrace_exclusion_patterns = [] if ENV['FULLSTACK']
|
30
20
|
#rspec.disable_monkey_patching!
|
31
21
|
rspec.raise_errors_for_deprecations!
|
@@ -1,5 +1,11 @@
|
|
1
1
|
shared_context 'collection resource' do
|
2
|
-
let(:resource)
|
2
|
+
let(:resource) do
|
3
|
+
Yaks::CollectionResource.new(
|
4
|
+
links: links,
|
5
|
+
members: members,
|
6
|
+
members_rel: 'http://api.example.com/rels/plants'
|
7
|
+
)
|
8
|
+
end
|
3
9
|
let(:links) { [] }
|
4
10
|
let(:members) { [] }
|
5
11
|
end
|
@@ -3,48 +3,52 @@ require 'spec_helper'
|
|
3
3
|
RSpec.describe Yaks::CollectionMapper do
|
4
4
|
include_context 'fixtures'
|
5
5
|
|
6
|
-
subject(:mapper) { described_class.new(
|
6
|
+
subject(:mapper) { described_class.new(context) }
|
7
7
|
let(:context) {
|
8
|
-
{
|
8
|
+
{ member_mapper: member_mapper,
|
9
9
|
policy: policy,
|
10
10
|
env: {}
|
11
11
|
}
|
12
12
|
}
|
13
13
|
let(:collection) { [] }
|
14
|
-
let(:
|
14
|
+
let(:member_mapper) { Class.new(Yaks::Mapper) { type 'the_type' } }
|
15
15
|
let(:policy) { Yaks::DefaultPolicy.new }
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
17
|
+
it 'should map the collection' do
|
18
|
+
expect(mapper.call(collection)).to eql Yaks::CollectionResource.new(
|
19
|
+
type: 'the_type',
|
20
|
+
links: [],
|
21
|
+
attributes: {},
|
22
|
+
members: [],
|
23
|
+
members_rel: 'rel:src=collection&dest=the_types'
|
23
24
|
)
|
24
|
-
|
25
|
+
end
|
25
26
|
|
26
27
|
context 'with members' do
|
27
28
|
let(:collection) { [boingboing, wassup]}
|
28
|
-
let(:
|
29
|
+
let(:member_mapper) { PetMapper }
|
29
30
|
|
30
|
-
|
31
|
-
|
31
|
+
it 'should map the members' do
|
32
|
+
mapper.call(collection)
|
33
|
+
|
34
|
+
expect(mapper.call(collection)).to eql Yaks::CollectionResource.new(
|
32
35
|
type: 'pet',
|
33
36
|
links: [],
|
34
37
|
attributes: {},
|
35
38
|
members: [
|
36
39
|
Yaks::Resource.new(type: 'pet', attributes: {:id => 2, :species => "dog", :name => "boingboing"}),
|
37
40
|
Yaks::Resource.new(type: 'pet', attributes: {:id => 3, :species => "cat", :name => "wassup"})
|
38
|
-
]
|
41
|
+
],
|
42
|
+
members_rel: 'rel:src=collection&dest=pets'
|
39
43
|
)
|
40
|
-
|
44
|
+
end
|
41
45
|
end
|
42
46
|
|
43
47
|
context 'with collection attributes' do
|
44
48
|
subject(:mapper) {
|
45
49
|
Class.new(Yaks::CollectionMapper) do
|
46
50
|
attributes :foo, :bar
|
47
|
-
end.new(
|
51
|
+
end.new(context)
|
48
52
|
}
|
49
53
|
|
50
54
|
let(:collection) {
|
@@ -54,31 +58,33 @@ RSpec.describe Yaks::CollectionMapper do
|
|
54
58
|
end.new([])
|
55
59
|
}
|
56
60
|
|
57
|
-
|
58
|
-
|
61
|
+
it 'should map the attributes' do
|
62
|
+
expect(mapper.call(collection)).to eql Yaks::CollectionResource.new(
|
59
63
|
type: 'the_type',
|
60
64
|
links: [],
|
61
65
|
attributes: { foo: 123, bar: 'pi la~~~' },
|
62
|
-
members: []
|
66
|
+
members: [],
|
67
|
+
members_rel: 'rel:src=collection&dest=the_types'
|
63
68
|
)
|
64
|
-
|
69
|
+
end
|
65
70
|
end
|
66
71
|
|
67
72
|
context 'with collection links' do
|
68
73
|
subject(:mapper) {
|
69
74
|
Class.new(Yaks::CollectionMapper) do
|
70
75
|
link :self, 'http://api.example.com/orders'
|
71
|
-
end.new(
|
76
|
+
end.new(context)
|
72
77
|
}
|
73
78
|
|
74
|
-
|
75
|
-
|
79
|
+
it 'should map the links' do
|
80
|
+
expect(mapper.call(collection)).to eql Yaks::CollectionResource.new(
|
76
81
|
type: 'the_type',
|
77
82
|
links: [ Yaks::Resource::Link.new(:self, 'http://api.example.com/orders', {}) ],
|
78
83
|
attributes: { },
|
79
|
-
members: []
|
84
|
+
members: [],
|
85
|
+
members_rel: 'rel:src=collection&dest=the_types'
|
80
86
|
)
|
81
|
-
|
87
|
+
end
|
82
88
|
end
|
83
89
|
|
84
90
|
end
|
@@ -50,7 +50,7 @@ RSpec.describe Yaks::CollectionResource do
|
|
50
50
|
}
|
51
51
|
|
52
52
|
its(:subresources) { should eql(
|
53
|
-
'
|
53
|
+
'members' => Yaks::CollectionResource.new(
|
54
54
|
type: 'order',
|
55
55
|
attributes: { total: 10.00 },
|
56
56
|
links: [
|
@@ -63,7 +63,7 @@ RSpec.describe Yaks::CollectionResource do
|
|
63
63
|
links: [Yaks::Resource::Link.new(:self, 'http://order/10', {})],
|
64
64
|
attributes: { customer: 'John Doe', price: 10.00 }
|
65
65
|
)
|
66
|
-
]
|
66
|
+
],
|
67
67
|
)
|
68
68
|
)
|
69
69
|
}
|
@@ -56,12 +56,12 @@ RSpec.describe Yaks::Config do
|
|
56
56
|
|
57
57
|
describe '#serialize' do
|
58
58
|
configure do
|
59
|
-
rel_template 'http://api.mysuperfriends.com/{
|
59
|
+
rel_template 'http://api.mysuperfriends.com/{dest}'
|
60
60
|
format_options :hal, plural_links: [:copyright]
|
61
61
|
end
|
62
62
|
|
63
63
|
specify do
|
64
|
-
expect(config.
|
64
|
+
expect(config.call(john)).to eql(load_json_fixture 'john.hal')
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
@@ -3,9 +3,10 @@ require 'spec_helper'
|
|
3
3
|
RSpec.describe Yaks::Mapper::HasMany do
|
4
4
|
let(:closet_mapper) do
|
5
5
|
Class.new(Yaks::Mapper) do
|
6
|
+
type 'closet'
|
6
7
|
has_many :shoes,
|
7
8
|
rel: 'http://foo/shoes',
|
8
|
-
mapper: Class.new(Yaks::Mapper) { type '
|
9
|
+
mapper: Class.new(Yaks::Mapper) { type 'shoe' ; attributes :size, :color }
|
9
10
|
end
|
10
11
|
end
|
11
12
|
|
@@ -19,13 +20,14 @@ RSpec.describe Yaks::Mapper::HasMany do
|
|
19
20
|
}
|
20
21
|
|
21
22
|
it 'should map the subresources' do
|
22
|
-
expect(closet_mapper.new(
|
23
|
+
expect(closet_mapper.new(policy: Yaks::DefaultPolicy.new, env: {}).call(closet).subresources).to eql(
|
23
24
|
"http://foo/shoes" => Yaks::CollectionResource.new(
|
24
|
-
type: '
|
25
|
+
type: 'shoe',
|
25
26
|
members: [
|
26
|
-
Yaks::Resource.new(type: '
|
27
|
-
Yaks::Resource.new(type: '
|
28
|
-
]
|
27
|
+
Yaks::Resource.new(type: 'shoe', attributes: {:size => 9, :color => :blue}),
|
28
|
+
Yaks::Resource.new(type: 'shoe', attributes: {:size => 11.5, :color => :red})
|
29
|
+
],
|
30
|
+
members_rel: 'rel:src=collection&dest=shoes'
|
29
31
|
)
|
30
32
|
)
|
31
33
|
end
|
@@ -35,12 +37,17 @@ RSpec.describe Yaks::Mapper::HasMany do
|
|
35
37
|
subject(:has_many) { described_class.new(:name, :mapper, :rel, collection_mapper) }
|
36
38
|
|
37
39
|
context 'when the collection mapper is undefined' do
|
38
|
-
|
40
|
+
it 'should derive one from collection and policy' do
|
41
|
+
expect(has_many.collection_mapper([], Yaks::DefaultPolicy.new)).to equal Yaks::CollectionMapper
|
42
|
+
end
|
39
43
|
end
|
40
44
|
|
41
45
|
context 'when the collection mapper is specified' do
|
42
46
|
let(:collection_mapper) { :foo }
|
43
|
-
|
47
|
+
|
48
|
+
it 'should use the given collection mapper' do
|
49
|
+
expect(has_many.collection_mapper([], Yaks::DefaultPolicy.new)).to equal :foo
|
50
|
+
end
|
44
51
|
end
|
45
52
|
end
|
46
53
|
end
|
@@ -11,6 +11,9 @@ RSpec.describe Yaks::Mapper::Link do
|
|
11
11
|
its(:uri_template) { should eq URITemplate.new(template) }
|
12
12
|
its(:expand?) { should be true }
|
13
13
|
|
14
|
+
let(:policy) { Yaks::DefaultPolicy.new }
|
15
|
+
let(:context) { { policy: policy, env: {} } }
|
16
|
+
|
14
17
|
describe '#rel?' do
|
15
18
|
it 'should return true if the relation matches' do
|
16
19
|
expect(link.rel?(:next)).to be true
|
@@ -81,7 +84,9 @@ RSpec.describe Yaks::Mapper::Link do
|
|
81
84
|
let(:object) { Struct.new(:x,:y).new(3,4) }
|
82
85
|
|
83
86
|
let(:mapper) do
|
84
|
-
Yaks::Mapper.new(
|
87
|
+
Yaks::Mapper.new(context).tap do |mapper|
|
88
|
+
mapper.call(object)
|
89
|
+
end
|
85
90
|
end
|
86
91
|
|
87
92
|
context 'with attributes' do
|
@@ -1,7 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe Yaks::Mapper do
|
4
|
-
subject(:mapper)
|
4
|
+
subject(:mapper) { mapper_class.new(context) }
|
5
|
+
let(:resource) { mapper.call(instance) }
|
5
6
|
|
6
7
|
let(:mapper_class) { Class.new(Yaks::Mapper) { type 'foo' } }
|
7
8
|
let(:instance) { double(foo: 'hello', bar: 'world') }
|
@@ -19,7 +20,7 @@ RSpec.describe Yaks::Mapper do
|
|
19
20
|
end
|
20
21
|
|
21
22
|
it 'should load them from the model' do
|
22
|
-
expect(
|
23
|
+
expect(resource.attributes).to eq(foo: 'hello', bar: 'world')
|
23
24
|
end
|
24
25
|
|
25
26
|
context 'with attribute filtering' do
|
@@ -32,7 +33,7 @@ RSpec.describe Yaks::Mapper do
|
|
32
33
|
end
|
33
34
|
|
34
35
|
it 'should only map the non-filtered attributes' do
|
35
|
-
expect(
|
36
|
+
expect(resource.attributes).to eq(:bar => 'world')
|
36
37
|
end
|
37
38
|
end
|
38
39
|
end
|
@@ -43,13 +44,13 @@ RSpec.describe Yaks::Mapper do
|
|
43
44
|
end
|
44
45
|
|
45
46
|
it 'should map the link' do
|
46
|
-
expect(
|
47
|
+
expect(resource.links).to eq [
|
47
48
|
Yaks::Resource::Link.new(:profile, 'http://foo/bar', {})
|
48
49
|
]
|
49
50
|
end
|
50
51
|
|
51
52
|
it 'should use the link in the resource' do
|
52
|
-
expect(
|
53
|
+
expect(resource.links).to include Yaks::Resource::Link.new(:profile, 'http://foo/bar', {})
|
53
54
|
end
|
54
55
|
|
55
56
|
context 'with the same link rel defined multiple times' do
|
@@ -62,7 +63,7 @@ RSpec.describe Yaks::Mapper do
|
|
62
63
|
end
|
63
64
|
|
64
65
|
it 'should map all the links' do
|
65
|
-
expect(
|
66
|
+
expect(resource.links).to eq [
|
66
67
|
Yaks::Resource::Link.new(:profile, 'http://foo/bar', {}),
|
67
68
|
Yaks::Resource::Link.new(:self, 'http://foo/bam', {}),
|
68
69
|
Yaks::Resource::Link.new(:self, 'http://foo/baz', {}),
|
@@ -91,12 +92,12 @@ RSpec.describe Yaks::Mapper do
|
|
91
92
|
|
92
93
|
|
93
94
|
it 'should have the subresource in the resource' do
|
94
|
-
expect(
|
95
|
+
expect(resource.subresources).to eq("http://foo.bar/rels/widgets" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"}))
|
95
96
|
end
|
96
97
|
|
97
98
|
context 'with explicit mapper and rel' do
|
98
99
|
it 'should delegate to the given mapper' do
|
99
|
-
expect(
|
100
|
+
expect(resource.subresources).to eq(
|
100
101
|
"http://foo.bar/rels/widgets" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"})
|
101
102
|
)
|
102
103
|
end
|
@@ -112,7 +113,7 @@ RSpec.describe Yaks::Mapper do
|
|
112
113
|
expect(assoc).to be_a Yaks::Mapper::HasOne
|
113
114
|
widget_mapper
|
114
115
|
}
|
115
|
-
expect(
|
116
|
+
expect(resource.subresources).to eq(
|
116
117
|
"http://foo.bar/rels/widgets" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"})
|
117
118
|
)
|
118
119
|
end
|
@@ -129,7 +130,7 @@ RSpec.describe Yaks::Mapper do
|
|
129
130
|
expect(assoc).to be_a Yaks::Mapper::HasOne
|
130
131
|
'http://rel/rel'
|
131
132
|
}
|
132
|
-
expect(
|
133
|
+
expect(resource.subresources).to eq(
|
133
134
|
"http://rel/rel" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"})
|
134
135
|
)
|
135
136
|
end
|
@@ -143,7 +144,7 @@ RSpec.describe Yaks::Mapper do
|
|
143
144
|
end
|
144
145
|
|
145
146
|
it 'should not map the resource' do
|
146
|
-
expect(
|
147
|
+
expect(resource.subresources).to eq({})
|
147
148
|
end
|
148
149
|
end
|
149
150
|
end
|
@@ -162,16 +163,14 @@ RSpec.describe Yaks::Mapper do
|
|
162
163
|
end
|
163
164
|
|
164
165
|
it 'should get the attribute from the mapper' do
|
165
|
-
expect(
|
166
|
+
expect(resource.attributes).to eq(fooattr: 'hello my friend', bar: 'world')
|
166
167
|
end
|
167
168
|
end
|
168
169
|
end
|
169
170
|
|
170
|
-
describe '#
|
171
|
-
let(:instance) { nil }
|
172
|
-
|
171
|
+
describe '#call' do
|
173
172
|
it 'should return a NullResource when the subject is nil' do
|
174
|
-
expect(mapper.
|
173
|
+
expect(mapper.call(nil)).to be_a Yaks::NullResource
|
175
174
|
end
|
176
175
|
end
|
177
176
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Yaks::Serializer do
|
3
|
+
RSpec.describe Yaks::Serializer do
|
4
4
|
describe '.by_name' do
|
5
5
|
specify { expect(Yaks::Serializer.by_name(:hal)).to eql Yaks::Serializer::Hal }
|
6
6
|
specify { expect(Yaks::Serializer.by_name(:json_api)).to eql Yaks::Serializer::JsonApi }
|
data/yaks.gemspec
CHANGED
@@ -23,8 +23,10 @@ Gem::Specification.new do |gem|
|
|
23
23
|
gem.add_runtime_dependency 'rack-accept' , '~> 0.4.5'
|
24
24
|
|
25
25
|
gem.add_development_dependency 'virtus'
|
26
|
-
gem.add_development_dependency 'rspec', '~>
|
26
|
+
gem.add_development_dependency 'rspec', '~> 2.99'
|
27
27
|
gem.add_development_dependency 'rake'
|
28
28
|
gem.add_development_dependency 'mutant-rspec'
|
29
|
+
gem.add_development_dependency 'mutant', '0.5.12'
|
29
30
|
gem.add_development_dependency 'rspec-its'
|
31
|
+
gem.add_development_dependency 'benchmark-ips'
|
30
32
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yaks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.0
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Arne Brasseur
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-06-
|
11
|
+
date: 2014-06-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: inflection
|
@@ -86,14 +86,14 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - ~>
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
89
|
+
version: '2.99'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - ~>
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
96
|
+
version: '2.99'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: rake
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +122,20 @@ dependencies:
|
|
122
122
|
- - '>='
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: mutant
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - '='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 0.5.12
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - '='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 0.5.12
|
125
139
|
- !ruby/object:Gem::Dependency
|
126
140
|
name: rspec-its
|
127
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,6 +150,20 @@ dependencies:
|
|
136
150
|
- - '>='
|
137
151
|
- !ruby/object:Gem::Version
|
138
152
|
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: benchmark-ips
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - '>='
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - '>='
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
139
167
|
description: Serialize to hypermedia. HAL, JSON-API, etc.
|
140
168
|
email:
|
141
169
|
- arne@arnebrasseur.net
|
@@ -151,6 +179,7 @@ files:
|
|
151
179
|
- LICENSE
|
152
180
|
- README.md
|
153
181
|
- Rakefile
|
182
|
+
- bench/bench.rb
|
154
183
|
- examples/hal01.rb
|
155
184
|
- examples/jsonapi01.rb
|
156
185
|
- examples/jsonapi02.rb
|
@@ -182,6 +211,7 @@ files:
|
|
182
211
|
- shaved_yak.gif
|
183
212
|
- spec/acceptance/acceptance_spec.rb
|
184
213
|
- spec/acceptance/models.rb
|
214
|
+
- spec/fixture_helpers.rb
|
185
215
|
- spec/integration/map_to_resource_spec.rb
|
186
216
|
- spec/json/confucius.hal.json
|
187
217
|
- spec/json/confucius.json_api.json
|
@@ -230,9 +260,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
230
260
|
version: '0'
|
231
261
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
232
262
|
requirements:
|
233
|
-
- - '
|
263
|
+
- - '>='
|
234
264
|
- !ruby/object:Gem::Version
|
235
|
-
version:
|
265
|
+
version: '0'
|
236
266
|
requirements: []
|
237
267
|
rubyforge_project:
|
238
268
|
rubygems_version: 2.2.2
|
@@ -242,6 +272,7 @@ summary: Serialize to hypermedia. HAL, JSON-API, etc.
|
|
242
272
|
test_files:
|
243
273
|
- spec/acceptance/acceptance_spec.rb
|
244
274
|
- spec/acceptance/models.rb
|
275
|
+
- spec/fixture_helpers.rb
|
245
276
|
- spec/integration/map_to_resource_spec.rb
|
246
277
|
- spec/json/confucius.hal.json
|
247
278
|
- spec/json/confucius.json_api.json
|