yaks 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8e26f0f65962ea64eee11346ede1401347525188
4
- data.tar.gz: 40881b570c5b8145e79a92251a35275b62da9994
3
+ metadata.gz: 17023a2415d74fcf0ea0c840dfa7f1d2fcb8afb4
4
+ data.tar.gz: 3fc6a562a7ba74d7064d2898f04ed32d1ba1b3b4
5
5
  SHA512:
6
- metadata.gz: 9b4370186b9d44d56404df20e65bfb58bedd666b6bd5be2cb78684d1022ed74bb2df1a01c6381dc743c9769cadbdc3fcff441d52298108137a4fe60f873c2dab
7
- data.tar.gz: 340ea7d369824f9ac7d0ea98e0995c0d4192b63c6a66f9faff2033b78b7b281d494c9044508c7a9bdd085e8a20ffb76b89a54716aef447c8824a27d3080a37ff
6
+ metadata.gz: 159c85fc53b2b7aa8e6fbc9a23fa3c1fa969f667e36e96c58e97ac4bc76b52bf7aaa1e95e309d319c121eeaafd30dc71a7de7fef3cca0173aae844593f3a5238
7
+ data.tar.gz: c0ec3d24906a2b12accd6548e574679e239def3d9e71228e1c35812c302c8c92afc118d3af47927b82e5c329bfeee0190df7b8b8f54300c1f3bc7eb1ca6a21db
data/README.md CHANGED
@@ -43,6 +43,7 @@ requested. These formats are presently supported:
43
43
  - [Filtering](#user-content-filtering)
44
44
  - [Links](#user-content-links)
45
45
  - [Associations](#user-content-associations)
46
+ - [Behaviours](#user-content-behaviours)
46
47
  - [Calling Yaks](#user-content-calling-yaks)
47
48
  - [Rack env](#user-content-rack-env)
48
49
  - [Namespace](#user-content-namespace)
@@ -68,6 +69,12 @@ requested. These formats are presently supported:
68
69
  - [How to contribute](#user-content-how-to-contribute)
69
70
  - [License](#user-content-license)
70
71
 
72
+ ## Packages
73
+
74
+ - [yaks-sinatra](yaks-sinatra/README.md)
75
+ - [yaks-html](yaks-html/README.md)
76
+ - [yaks-transit](yaks-transit/README.md)
77
+
71
78
  ## State of Development
72
79
 
73
80
  Recent focus has been on stabilizing the core classes, improving
@@ -309,6 +316,59 @@ class ShowMapper < Yaks::Mapper
309
316
  end
310
317
  ```
311
318
 
319
+ ### Behaviours
320
+
321
+ Yaks provides mixins to change how your mappers work. These need to be
322
+ required separately, they are not loaded by default.
323
+
324
+ #### OptionalIncludes
325
+
326
+ You may choose to not render associations by default, but to only do
327
+ so when the client explicitly asks for them. This can be done by
328
+ including `Yaks::Behaviour::OptionalIncludes`.
329
+
330
+ Which associations to load is specified with the the `include` query
331
+ parameter. You can use dots to load nested associated.
332
+
333
+ ```ruby
334
+ require "yaks/behaviour/optional_includes"
335
+
336
+ class PostMapper < Yaks::Mapper
337
+ include Yaks::Behaviour::OptionalIncludes
338
+
339
+ has_one :author
340
+ has_many :comments
341
+ end
342
+
343
+ class AuthorMapper < Yaks::Mapper
344
+ include Yaks::Behaviour::OptionalIncludes
345
+
346
+ has_one :profile
347
+ end
348
+ ```
349
+
350
+ ```
351
+ GET /post/42?include=comments,author.profile
352
+ ```
353
+
354
+ Note that this will only work when Yaks has access to the Rack
355
+ environment. When using an existing integration like `yaks-sinatra`
356
+ this will be handled for you.
357
+
358
+ To force an association to always be included, override its `if`
359
+ condition to always return true.
360
+
361
+ ```ruby
362
+ require "yaks/behaviour/optional_includes"
363
+
364
+ class PostMapper < Yaks::Mapper
365
+ include Yaks::Behaviour::OptionalIncludes
366
+
367
+ has_one :author
368
+ has_many :comments, if: ->{ true }
369
+ end
370
+ ```
371
+
312
372
  ## Calling Yaks
313
373
 
314
374
  Once you have a Yaks instance, you can call it with `call`
@@ -513,19 +573,6 @@ at your API entry point should do the trick.
513
573
 
514
574
  ### JSON-API
515
575
 
516
- The JSON-API spec has evolved since the Yaks formatter was
517
- implemented. It is also not the most suitable format for Yaks
518
- feature-set due to its strong convention-driven nature and weak
519
- support for hypermedia.
520
-
521
- At this time, The JSON-API specification has not reached a 1.0 release.
522
- Some changes to the Yaks JSON-API formatter may still be required
523
- before it is completely compatible with the latest version of the
524
- specification.
525
-
526
- If you would like to see better JSON-API support, get in touch. We
527
- might be able to work something out.
528
-
529
576
  ```ruby
530
577
  Yaks.new do
531
578
  default_format :json_api
@@ -552,6 +599,8 @@ yaks = Yaks.new do
552
599
  end
553
600
  ```
554
601
 
602
+ For optional includes, see [`Yaks::Behaviour::OptionalIncludes`](#user-content-behaviours).
603
+
555
604
  ### Collection+JSON
556
605
 
557
606
  Collection+JSON has support for write templates. To use them, the `:template`
@@ -802,15 +851,38 @@ end
802
851
 
803
852
  For JSON based formats, the "syntax tree" is merely a structure of Ruby primitives that have a JSON equivalent. If your mappers return non-primitive attribute values, you can define how they should be converted. For example, JSON has no notion of 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`
804
853
 
854
+ Here's an example with a custom `Currency` class, which can be represented as an integer.
855
+
805
856
  ```ruby
806
857
  Yaks.new do
807
- map_to_primitive Date, Time, DateTime do |date|
808
- date.iso8601
858
+ map_to_primitive Currency do |currency|
859
+ currency.to_i
809
860
  end
810
861
  end
811
862
  ```
812
863
 
813
- 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.
864
+ One notable use case is representing dates and times. The JSON
865
+ specification does not define any syntax for these, so the only
866
+ solution is to represent them either as numbers or strings. If you're
867
+ not sure what to do with these then the ISO8601 standard is a safe
868
+ bet. It defines a way to represent times and dates as strings, and is
869
+ also adopted by the W3C in [RFC3339](http://tools.ietf.org/html/rfc3339).
870
+
871
+ An alternative representation that is sometimes used is "unix time",
872
+ defined as the numbers of seconds passed since 1 January 1970.
873
+
874
+ Here's an example for a Rails app, so including ActiveSupport's `TimeWithZone`.
875
+
876
+ ```ruby
877
+ Yaks.new do
878
+ map_to_primitive Date, Time, DateTime, ActiveSupport::TimeWithZone, &:iso8601
879
+ end
880
+ ```
881
+
882
+ `map_to_primitive` can also be used to transform alternative data
883
+ structures, like those from [Hamster](https://github.com/hamstergem/hamster),
884
+ into Ruby arrays and hashes. Use `call()` to recursively turn things into
885
+ primitives.
814
886
 
815
887
  ```ruby
816
888
  Yaks.new do
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
- load '../rakelib/shared.rake'
1
+ load '../shared/rake_tasks.rb'
2
2
 
3
3
  gem_tasks(:yaks)
4
4
 
data/ataru_setup.rb CHANGED
@@ -33,6 +33,12 @@ class HomeMapper < Yaks::Mapper; end
33
33
 
34
34
  class SpecialMapper < Yaks::Mapper; end
35
35
 
36
+ module ActiveSupport
37
+ class TimeWithZone < Time ; end
38
+ end
39
+
40
+ class Currency ; end
41
+
36
42
  module Setup
37
43
  def setup
38
44
  # Do some nice setup that is run before every snippet
@@ -0,0 +1,29 @@
1
+ require "rack/utils"
2
+
3
+ module Yaks
4
+ module Behaviour
5
+ module OptionalIncludes
6
+ RACK_KEY = "yaks.optional_includes".freeze
7
+
8
+ def associations
9
+ super.select do |association|
10
+ association.if != Undefined || include_association?(association)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def include_association?(association)
17
+ includes = env.fetch(RACK_KEY) do
18
+ query_string = env.fetch("QUERY_STRING", nil)
19
+ query = Rack::Utils.parse_query(query_string)
20
+ env[RACK_KEY] = query.fetch("include", "").split(",").map { |r| r.split(".") }
21
+ end
22
+
23
+ includes.any? do |relationship|
24
+ relationship[mapper_stack.size].eql?(association.name.to_s)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -42,31 +42,31 @@ module Yaks
42
42
  attributes = resource.attributes.reject { |k| k.equal?(:id) }
43
43
  result[:attributes] = attributes if attributes.any?
44
44
 
45
- result[:links] = {}
46
- result[:links].update(serialize_subresource_links(resource.subresources))
47
- result[:links].update(serialize_links(resource.links))
48
- result.delete(:links) if result[:links].empty?
45
+ relationships = serialize_relationships(resource.subresources)
46
+ result[:relationships] = relationships unless relationships.empty?
47
+ links = serialize_links(resource.links)
48
+ result[:links] = links unless links.empty?
49
49
 
50
50
  result
51
51
  end
52
52
 
53
- # @param [Yaks::Resource] subresource
53
+ # @param [Array] subresources
54
54
  # @return [Hash]
55
- def serialize_subresource_links(subresources)
55
+ def serialize_relationships(subresources)
56
56
  subresources.each_with_object({}) do |resource, hsh|
57
- next if resource.null_resource?
58
- hsh[resource.rels.first.sub(/^rel:/, '')] = serialize_subresource_link(resource)
57
+ hsh[resource.rels.first.sub(/^rel:/, '').to_sym] = serialize_relationship(resource)
59
58
  end
60
59
  end
61
60
 
62
61
  # @param [Yaks::Resource] resource
63
- # @return [Array, String]
64
- def serialize_subresource_link(resource)
62
+ # @return [Array, Hash]
63
+ def serialize_relationship(resource)
65
64
  if resource.collection?
66
- {linkage: resource.map{|r| {type: pluralize(r.type), id: r[:id].to_s} }}
67
- else
68
- {linkage: {type: pluralize(resource.type), id: resource[:id].to_s}}
65
+ data = resource.map { |r| {type: pluralize(r.type), id: r[:id].to_s} }
66
+ elsif !resource.null_resource?
67
+ data = {type: pluralize(resource.type), id: resource[:id].to_s}
69
68
  end
69
+ {data: data}
70
70
  end
71
71
 
72
72
  # @param [Hash] subresources
@@ -25,7 +25,7 @@ module Yaks
25
25
  # it will receive the mapper instance as argument. Otherwise it is evaluated in the mapper context
26
26
  class Link
27
27
  extend Forwardable, Util
28
- include Attribs.new(:rel, :template, options: {}), Util
28
+ include Attribs.new(:rel, :template, options: {}.freeze), Util
29
29
 
30
30
  def self.create(*args)
31
31
  args, options = extract_options(args)
@@ -62,11 +62,16 @@ module Yaks
62
62
  uri = mapper.expand_uri(template, options.fetch(:expand, true))
63
63
  return if uri.nil?
64
64
 
65
- Resource::Link.new(
65
+ attrs = {
66
66
  rel: rel,
67
- uri: uri,
68
- options: resource_link_options(mapper)
69
- )
67
+ uri: uri
68
+ }
69
+
70
+ resource_link_options(mapper).tap do |opts|
71
+ attrs[:options] = opts unless opts.empty?
72
+ end
73
+
74
+ Resource::Link.new(attrs)
70
75
  end
71
76
 
72
77
  private
@@ -12,12 +12,12 @@ module Yaks
12
12
  else
13
13
  attributes = parsed_json['data'].dup
14
14
  links = attributes.delete('links') || {}
15
+ relationships = attributes.delete('relationships') || {}
15
16
  type = attributes.delete('type')
16
17
  attributes.merge!(attributes.delete('attributes') || {})
17
18
 
18
- association_links, resource_links = links.partition { |_k, v| v.is_a?(Hash) }
19
- embedded = convert_embedded(Hash[association_links], included)
20
- links = convert_links(Hash[resource_links])
19
+ embedded = convert_embedded(Hash[relationships], included)
20
+ links = convert_links(Hash[links])
21
21
 
22
22
  Resource.new(
23
23
  type: Util.singularize(type),
@@ -28,29 +28,33 @@ module Yaks
28
28
  end
29
29
  end
30
30
 
31
- def convert_embedded(links, included)
32
- links.flat_map do |rel, link_data|
33
- # A Link doesn't have to contain a `linkage` member.
31
+ def convert_embedded(relationships, included)
32
+ relationships.flat_map do |rel, relationship|
33
+ # A Link doesn't have to contain a `data` member.
34
34
  # It can contain URLs instead, or as well, but we are only worried about *embedded* links here.
35
- linkage = link_data['linkage']
36
- # Resource linkage MUST be represented as one of the following:
35
+ data = relationship['data']
36
+ # Resource data MUST be represented as one of the following:
37
37
  #
38
38
  # * `null` for empty to-one relationships.
39
- # * a "linkage object" for non-empty to-one relationships.
39
+ # * a "resource identifier object" for non-empty to-one relationships.
40
40
  # * an empty array ([]) for empty to-many relationships.
41
- # * an array of linkage objects for non-empty to-many relationships.
42
- if linkage.nil?
43
- nil
44
- elsif linkage.is_a? Array
45
- CollectionResource.new(
46
- members: linkage.map { |link|
47
- data = included.find{ |item| (item['id'] == link['id']) && (item['type'] == link['type']) }
48
- call('data' => data, 'included' => included)
49
- },
50
- rels: [rel]
51
- )
41
+ # * an array of resource identifier objects for non-empty to-many relationships.
42
+ if data.nil?
43
+ NullResource.new(rels: [rel])
44
+ elsif data.is_a? Array
45
+ if data.empty?
46
+ NullResource.new(collection: true, rels: [rel])
47
+ else
48
+ CollectionResource.new(
49
+ members: data.map { |link|
50
+ data = included.find{ |item| (item['id'] == link['id']) && (item['type'] == link['type']) }
51
+ call('data' => data, 'included' => included)
52
+ },
53
+ rels: [rel]
54
+ )
55
+ end
52
56
  else
53
- data = included.find{ |item| (item['id'] == linkage['id']) && (item['type'] == linkage['type']) }
57
+ data = included.find{ |item| (item['id'] == data['id']) && (item['type'] == data['type']) }
54
58
  call('data' => data, 'included' => included).with(rels: [rel])
55
59
  end
56
60
  end.compact
@@ -1,7 +1,7 @@
1
1
  module Yaks
2
2
  class Resource
3
3
  class Link
4
- include Attribs.new(:rel, :uri, options: {})
4
+ include Attribs.new(:rel, :uri, options: {}.freeze)
5
5
 
6
6
  def title
7
7
  options[:title]
data/lib/yaks/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Yaks
2
- VERSION = '0.10.0'
2
+ VERSION = '0.11.0'
3
3
  end
@@ -7,12 +7,16 @@
7
7
  "pinyin": "Kongzi",
8
8
  "latinized": "Confucius"
9
9
  },
10
- "links": {
11
- "profile": "http://literature.example.com/profiles/scholar",
10
+ "relationships": {
11
+ "works": {
12
+ "data": [{"type": "works", "id": "11"}, {"type": "works", "id": "12"}]
13
+ }
14
+ },
15
+ "links": {
16
+ "profile": "http://literature.example.com/profiles/scholar",
12
17
  "self": "http://literature.example.com/authors/kongzi",
13
- "http://literature.example.com/rels/quotes": "http://literature.example.com/quotes/?author=kongzi&q={query}",
14
- "works": {"linkage": [{"type": "works", "id": "11"}, {"type": "works", "id": "12"}]}
15
- }
18
+ "http://literature.example.com/rels/quotes": "http://literature.example.com/quotes/?author=kongzi&q={query}"
19
+ }
16
20
  },
17
21
  "included": [
18
22
  {
@@ -22,11 +26,15 @@
22
26
  "chinese_name": "論語",
23
27
  "english_name": "Analects"
24
28
  },
29
+ "relationships": {
30
+ "quotes": {
31
+ "data": [{"type": "quotes", "id": "17"}, {"type": "quotes", "id": "18"}]
32
+ },
33
+ "era": {"data": {"type": "erae", "id": "99"}}
34
+ },
25
35
  "links": {
26
36
  "profile": "http://literature.example.com/profiles/work",
27
- "self": "http://literature.example.com/work/11",
28
- "quotes": {"linkage": [{"type": "quotes", "id": "17"}, {"type": "quotes", "id": "18"}]},
29
- "era": {"linkage": {"type": "erae", "id": "99"}}
37
+ "self": "http://literature.example.com/work/11"
30
38
  }
31
39
  },
32
40
  {
@@ -57,6 +65,10 @@
57
65
  "chinese_name": "易經",
58
66
  "english_name": "Commentaries to the Yi-jing"
59
67
  },
68
+ "relationships": {
69
+ "quotes": { "data": [] },
70
+ "era": { "data": null }
71
+ },
60
72
  "links": {
61
73
  "profile": "http://literature.example.com/profiles/work",
62
74
  "self": "http://literature.example.com/work/12"
data/spec/spec_helper.rb CHANGED
@@ -1,13 +1,20 @@
1
- require 'rspec/its'
2
- require 'bogus/rspec'
3
- require 'timeout'
4
-
5
1
  require 'yaks'
6
2
  require 'yaks-html'
7
3
  require 'virtus'
8
4
 
5
+ require_relative '../../shared/rspec_config'
6
+
9
7
  require 'fixture_helpers'
10
8
 
9
+ RSpec.configure do |rspec|
10
+ rspec.include FixtureHelpers
11
+ end
12
+
13
+ Bogus.configure do |bogus|
14
+ bogus.search_modules << Yaks
15
+ bogus.search_modules << Yaks::Mapper
16
+ end
17
+
11
18
  require_relative 'support/models'
12
19
  require_relative 'support/pet_mapper'
13
20
  require_relative 'support/pet_peeve_mapper'
@@ -17,20 +24,3 @@ require_relative 'support/shared_contexts'
17
24
  require_relative 'support/youtypeit_models_mappers'
18
25
  require_relative 'support/deep_eql'
19
26
  require_relative 'support/classes_for_policy_testing'
20
-
21
- RSpec.configure do |rspec|
22
- rspec.include FixtureHelpers
23
- rspec.backtrace_exclusion_patterns = [] if ENV['FULLSTACK']
24
- rspec.disable_monkey_patching!
25
- rspec.raise_errors_for_deprecations!
26
- if defined?(Mutant)
27
- rspec.around(:each) do |example|
28
- Timeout.timeout(1, &example)
29
- end
30
- end
31
- end
32
-
33
- Bogus.configure do |bogus|
34
- bogus.search_modules << Yaks
35
- bogus.search_modules << Yaks::Mapper
36
- end
@@ -1,4 +1,4 @@
1
- shared_context 'fixtures' do
1
+ RSpec.shared_context 'fixtures' do
2
2
  let(:john) { Friend.new(id: 1, name: 'john', pets: [boingboing, wassup], pet_peeve: regexps) }
3
3
  let(:boingboing) { Pet.new(id: 2, name: 'boingboing', species: 'dog') }
4
4
  let(:wassup) { Pet.new(id: 3, name: 'wassup', species: 'cat') }
@@ -0,0 +1,63 @@
1
+ require "yaks/behaviour/optional_includes"
2
+
3
+ RSpec.describe Yaks::Behaviour::OptionalIncludes do
4
+ include_context 'yaks context'
5
+
6
+ subject(:mapper) { mapper_class.new(yaks_context) }
7
+ let(:resource) { mapper.call(instance) }
8
+
9
+ let(:mapper_class) do
10
+ Class.new(Yaks::Mapper).tap do |mapper_class|
11
+ mapper_class.send :include, Yaks::Behaviour::OptionalIncludes
12
+ mapper_class.type "user"
13
+ mapper_class.has_many :posts, mapper: post_mapper_class
14
+ mapper_class.has_one :account, mapper: account_mapper_class
15
+ end
16
+ end
17
+ let(:post_mapper_class) do
18
+ Class.new(Yaks::Mapper).tap do |mapper_class|
19
+ mapper_class.type "post"
20
+ mapper_class.has_many :comments, mapper: comment_mapper_class
21
+ end
22
+ end
23
+ let(:account_mapper_class) { Class.new(Yaks::Mapper) { type "account" } }
24
+ let(:comment_mapper_class) { Class.new(Yaks::Mapper) { type "comment" } }
25
+
26
+ let(:instance) { fake(posts: [fake(comments: [fake])], account: fake) }
27
+
28
+ it "includes the associations" do
29
+ rack_env["QUERY_STRING"] = "include=posts.comments,account"
30
+
31
+ expect(resource.type).to eq "user"
32
+ expect(resource.subresources[0].type).to eq "post"
33
+ expect(resource.subresources[0].members[0].type).to eq "post"
34
+ expect(resource.subresources[0].members[0].subresources[0].type).to eq "comment"
35
+ expect(resource.subresources[0].members[0].subresources[0].members[0].type).to eq "comment"
36
+ expect(resource.subresources[1].type).to eq "account"
37
+ end
38
+
39
+ it "excludes associations not specified in the QUERY_STRING" do
40
+ rack_env["QUERY_STRING"] = "include=posts"
41
+
42
+ expect(resource.subresources.count).to eq 1
43
+ end
44
+
45
+ it "doesn't include the associations when QUERY_STRING is empty" do
46
+ expect(resource.type).to eq "user"
47
+ expect(resource.subresources).to be_empty
48
+ end
49
+
50
+ it "allows :if to override the query parameter checking" do
51
+ mapper_class.has_one :account, mapper: account_mapper_class, if: true
52
+
53
+ expect(resource.subresources.count).to eq 1
54
+ end
55
+
56
+ it "caches parsing of the query parameter" do
57
+ rack_env["QUERY_STRING"] = "include=posts"
58
+ expect(mapper.call(instance).subresources.count).to eq 1
59
+
60
+ rack_env["QUERY_STRING"] = nil
61
+ expect(mapper.call(instance).subresources.count).to eq 1
62
+ end
63
+ end
@@ -30,7 +30,7 @@ RSpec.describe Yaks::DefaultPolicy, '#derive_mapper_from_collection' do
30
30
  it 'should propagate the error' do
31
31
  expect {
32
32
  policy.derive_mapper_from_object([])
33
- }.to raise_error
33
+ }.to raise_error(RuntimeError)
34
34
  end
35
35
  end
36
36
 
@@ -38,9 +38,9 @@ RSpec.describe Yaks::DefaultPolicy, '#derive_mapper_from_collection' do
38
38
  let(:options) { {namespace: DislikesOtherMappers} }
39
39
 
40
40
  it 'should propagate the error' do
41
- expect do
41
+ expect {
42
42
  policy.derive_mapper_from_object([Namespace::Nested::Rye.new])
43
- end.to raise_error
43
+ }.to raise_error(RuntimeError)
44
44
  end
45
45
  end
46
46
  end
@@ -47,10 +47,12 @@ RSpec.describe Yaks::Format::JsonAPI do
47
47
  expect(format.call(resource)).to eql(
48
48
  data: {
49
49
  type: 'wizards',
50
+ relationships: {
51
+ favourite_spell: {data: {type: "spells", id: "1"}}
52
+ },
50
53
  links: {
51
54
  self: "/the/self/link",
52
- profile: "/the/profile/link",
53
- 'favourite_spell' => {linkage: {type: "spells", id: "1"}},
55
+ profile: "/the/profile/link"
54
56
  }
55
57
  },
56
58
  included: [{type: 'spells', id: "1"}]
@@ -72,7 +74,7 @@ RSpec.describe Yaks::Format::JsonAPI do
72
74
  expect(format.call(resource)).to eql(
73
75
  data: {
74
76
  type: 'wizards',
75
- links: {'favourite_spell' => {linkage: {type: 'spells', id: "777"}}}
77
+ relationships: {favourite_spell: {data: {type: 'spells', id: "777"}}}
76
78
  },
77
79
  included: [{type: 'spells', id: "777", attributes: {name: 'Lucky Sevens'}}]
78
80
  )
@@ -103,10 +105,10 @@ RSpec.describe Yaks::Format::JsonAPI do
103
105
  it 'should include the each subresource only once' do
104
106
  expect(format.call(resource)).to eql(
105
107
  data: [
106
- {type: 'wizards', id: '7', links: {'favourite_spell' => {linkage: {type: 'spells', id: '1'}}}},
107
- {type: 'wizards', id: '3', links: {'favourite_spell' => {linkage: {type: 'spells', id: '1'}}}},
108
- {type: 'wizards', id: '2', links: {'favourite_spell' => {linkage: {type: 'spells', id: '12'}}}},
109
- {type: 'wizards', id: '9', links: {'wand' => {linkage: {type: 'wands', id: '1'}}}},
108
+ {type: 'wizards', id: '7', relationships: {favourite_spell: {data: {type: 'spells', id: '1'}}}},
109
+ {type: 'wizards', id: '3', relationships: {favourite_spell: {data: {type: 'spells', id: '1'}}}},
110
+ {type: 'wizards', id: '2', relationships: {favourite_spell: {data: {type: 'spells', id: '12'}}}},
111
+ {type: 'wizards', id: '9', relationships: {wand: {data: {type: 'wands', id: '1'}}}},
110
112
  ],
111
113
  included: [
112
114
  {type: 'spells', id: '1'},
@@ -121,14 +123,38 @@ RSpec.describe Yaks::Format::JsonAPI do
121
123
  let(:resource) {
122
124
  Yaks::Resource.new(
123
125
  type: 'wizard',
124
- subresources: [Yaks::NullResource.new]
126
+ subresources: [subresource]
125
127
  )
126
128
  }
127
129
 
128
- it 'should not include subresource links' do
129
- expect(format.call(resource)).to eql(
130
- data: {type: 'wizards'}
131
- )
130
+ context "non-collection subresouce" do
131
+ let(:subresource) { Yaks::NullResource.new.add_rel("rel:wand") }
132
+
133
+ it 'should include a nil linkage object' do
134
+ expect(format.call(resource)).to eql(
135
+ data: {
136
+ type: 'wizards',
137
+ relationships: {
138
+ wand: {data: nil}
139
+ }
140
+ }
141
+ )
142
+ end
143
+ end
144
+
145
+ context "collection subresouce" do
146
+ let(:subresource) { Yaks::NullResource.new(collection: true).add_rel("rel:wands") }
147
+
148
+ it 'should include a nil linkage object' do
149
+ expect(format.call(resource)).to eql(
150
+ data: {
151
+ type: 'wizards',
152
+ relationships: {
153
+ wands: {data: []}
154
+ }
155
+ }
156
+ )
157
+ end
132
158
  end
133
159
  end
134
160
 
data/yaks.gemspec CHANGED
@@ -38,8 +38,10 @@ Gem::Specification.new do |gem|
38
38
  gem.add_development_dependency 'benchmark-ips'
39
39
  gem.add_development_dependency 'bogus'
40
40
  gem.add_development_dependency 'hamster'
41
- gem.add_development_dependency 'mutant'
42
- gem.add_development_dependency 'mutant-rspec'
41
+ if RUBY_VERSION >= "2.1.0"
42
+ gem.add_development_dependency 'mutant'
43
+ gem.add_development_dependency 'mutant-rspec'
44
+ end
43
45
  gem.add_development_dependency 'rake'
44
46
  gem.add_development_dependency 'rubocop'
45
47
  gem.add_development_dependency 'rspec', '~> 3.0'
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.10.0
4
+ version: 0.11.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: 2015-05-19 00:00:00.000000000 Z
11
+ date: 2015-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: abstract_type
@@ -318,6 +318,7 @@ files:
318
318
  - ataru_setup.rb
319
319
  - find_missing_tests.rb
320
320
  - lib/yaks.rb
321
+ - lib/yaks/behaviour/optional_includes.rb
321
322
  - lib/yaks/breaking_changes.rb
322
323
  - lib/yaks/builder.rb
323
324
  - lib/yaks/changelog.rb
@@ -395,6 +396,7 @@ files:
395
396
  - spec/support/pet_peeve_mapper.rb
396
397
  - spec/support/shared_contexts.rb
397
398
  - spec/support/youtypeit_models_mappers.rb
399
+ - spec/unit/yaks/behaviour/optional_includes_spec.rb
398
400
  - spec/unit/yaks/builder_spec.rb
399
401
  - spec/unit/yaks/collection_mapper_spec.rb
400
402
  - spec/unit/yaks/collection_resource_spec.rb
@@ -463,7 +465,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
463
465
  version: '0'
464
466
  requirements: []
465
467
  rubyforge_project:
466
- rubygems_version: 2.4.5
468
+ rubygems_version: 2.2.3
467
469
  signing_key:
468
470
  specification_version: 4
469
471
  summary: Serialize to hypermedia. HAL, JSON-API, etc.
@@ -497,6 +499,7 @@ test_files:
497
499
  - spec/support/pet_peeve_mapper.rb
498
500
  - spec/support/shared_contexts.rb
499
501
  - spec/support/youtypeit_models_mappers.rb
502
+ - spec/unit/yaks/behaviour/optional_includes_spec.rb
500
503
  - spec/unit/yaks/builder_spec.rb
501
504
  - spec/unit/yaks/collection_mapper_spec.rb
502
505
  - spec/unit/yaks/collection_resource_spec.rb