ivy-serializers 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 965769bbd1ab1c1e7345b7767e4ac2f35ced0890
4
- data.tar.gz: 8c8b913128925ad947f0810f05cb47d34d7c1f95
3
+ metadata.gz: 53cc58ca39399bdcc72ecb47fd0aeecac296be43
4
+ data.tar.gz: ad5208023b028606b093a3056added2c7574ba82
5
5
  SHA512:
6
- metadata.gz: 5ccc6afa7b0b08e61cbb53876c8239192c60683fd54bf707e06e02e34bf2ce2b8ea86b0ad8cc40b6d87471b1ff6e531d1f9f5aed5b0ee0303f3a27631d1bbe0d
7
- data.tar.gz: 255b0f79b33bec71c516fadcc0d471abec3e4e685a6b520b508076ac39f70033416813a6438fa54d813638dc6511ed492cd6eada2e44a91c0b4d74ff7ec7bf19
6
+ metadata.gz: 329051d45ee2fbbd118e992ffcd83e69fcac05128e96606ba2fdd64e53ee1b2b74ad75649d1c48c32fddd83fa04c121706b6f0b79b15eb67171f34e368f0cb12
7
+ data.tar.gz: 6a6540f204ccb754c0c0846924708e4ee0a5caed80827ef1abcea481176c4d7f2a8a3f8c44ed9f45deb93f203d7b450b3098fde5ab003029a75f9445b998880e
data/.travis.yml CHANGED
@@ -1,10 +1,8 @@
1
1
  language: ruby
2
2
 
3
3
  rvm:
4
+ - 2.2.2
4
5
  - 1.9.3
5
- - 2.0.0
6
- - 2.1
7
- - 2.2
8
6
 
9
7
  cache: bundler
10
8
 
data/README.md CHANGED
@@ -1,13 +1,10 @@
1
- # Ivy::Serializers
1
+ # ivy-serializers
2
2
 
3
3
  [![Build Status](https://travis-ci.org/IvyApp/ivy-serializers.svg?branch=master)](https://travis-ci.org/IvyApp/ivy-serializers)
4
4
 
5
- JSON serialization for client-side apps, with multiple output formats. Ships
6
- with [ActiveModel::Serializers][ams] and [JSON-API][jsonapi] RC3 support
7
- out of the box.
5
+ JSON serialization for client-side apps, with multiple output formats. Ships with [ActiveModel::Serializers](https://github.com/rails-api/active_model_serializers) and [JSON-API](http://jsonapi.org/) 1.0 support out of the box.
8
6
 
9
- [ams]: https://github.com/rails-api/active_model_serializers
10
- [jsonapi]: http://jsonapi.org/
7
+ If you're building a Rails project, take a look at [ivy-serializers-rails](https://github.com/IvyApp/ivy-serializers-rails) instead.
11
8
 
12
9
  ## Installation
13
10
 
@@ -45,40 +42,27 @@ class Comment < ActiveRecord::Base
45
42
  end
46
43
  ```
47
44
 
48
- Define a serializer in `app/serializers/serializer.rb`:
45
+ Define a serializer in `app/serializers/my_serializer.rb`:
49
46
 
50
47
  ```ruby
51
- class Serializer < Ivy::Serializers::Serializer
48
+ class MySerializer < Ivy::Serializers::Serializer
52
49
  map Post do
53
- attributes :id, :title
50
+ attributes :title
54
51
  has_many :comments
55
52
  end
56
53
 
57
54
  map Comment do
58
- attributes :id, :body
55
+ attributes :body
59
56
  belongs_to :post
60
57
  end
61
58
  end
62
59
  ```
63
60
 
64
- Then set up your controllers to use the serializer:
65
-
66
- ```ruby
67
- class ApplicationController < ActionController::Base
68
- self.serializer = Serializer
69
- end
70
- ```
71
-
72
- Note that you're not limited to a single serializer. If you have multiple
73
- serialization formats, such as one for admin and one for public-facing, you can
74
- define alternate serializers in `app/serializers` and use them as well.
61
+ **NOTE**: An `id` attribute is automatically defined for you. This is a consequence of supporting JSON-API, which requires all resources to have IDs.
75
62
 
76
63
  ### Sideloading
77
64
 
78
- The `#belongs_to` and `#has_many` methods support an optional `:embed_in_root`
79
- option, which will load the associated record into the root of the payload. For
80
- instance, if we wanted the list of comments to be included when fetching
81
- a post, we could define the `has_many` relationship like so:
65
+ The `#belongs_to` and `#has_many` methods support an optional `:embed_in_root` option, which will load the associated record into the root of the payload. For instance, if we wanted the list of comments to be included when fetching a post, we could define the `has_many` relationship like so:
82
66
 
83
67
  ```ruby
84
68
  map Post do
@@ -86,8 +70,7 @@ map Post do
86
70
  end
87
71
  ```
88
72
 
89
- The same thing also works with `belongs_to`, so if we wanted to ensure the post
90
- was included when fetching a comment:
73
+ The same thing also works with `belongs_to`, so if we wanted to ensure the post was included when fetching a comment:
91
74
 
92
75
  ```ruby
93
76
  map Comment do
@@ -97,8 +80,7 @@ end
97
80
 
98
81
  ### Polymorphic Associations
99
82
 
100
- There is also support for polymorphic associations. To use it, pass the
101
- `:polymorphic => true` option to the `#belongs_to` or `#has_many` methods:
83
+ There is also support for polymorphic associations. To use it, pass the `:polymorphic => true` option to the `#belongs_to` or `#has_many` methods:
102
84
 
103
85
  ```ruby
104
86
  map Post do
@@ -112,18 +94,15 @@ end
112
94
 
113
95
  ### Customizing Attributes
114
96
 
115
- By default, attributes are mapped directly to methods on the record being
116
- serialized. So defining:
97
+ By default, attributes are mapped directly to methods on the record being serialized. So defining:
117
98
 
118
99
  ```ruby
119
100
  map Post do
120
- attributes :id, :title
101
+ attributes :title
121
102
  end
122
103
  ```
123
104
 
124
- will read `id` and `title` from the post and write it into the hash under the
125
- `:id` and `:title` keys. If you want to customize the value, you can use the
126
- `#attribute` method instead, and pass it a block:
105
+ will read `title` from the post and write it into the hash under the `:title` key. If you want to customize the value, you can use the `#attribute` method instead, and pass it a block:
127
106
 
128
107
  ```ruby
129
108
  map Post do
@@ -131,20 +110,7 @@ map Post do
131
110
  end
132
111
  ```
133
112
 
134
- In the above example, we read the `headline` attribute from the post and write
135
- it into the payload under the `:title` key.
136
-
137
- ### Alternate Output Formats
138
-
139
- By default the ActiveModel::Serializers output format is used. If you'd rather
140
- use JSON-API (or a custom format), you can do so by setting
141
- `serialization_format` in your controller. For instance, to use JSON-API:
142
-
143
- ```ruby
144
- class ApplicationController < ActionController::Base
145
- self.serialization_format = Ivy::Serializers::Formats::JSONAPI
146
- end
147
- ```
113
+ In the above example, we read the `headline` attribute from the post and write it into the payload under the `:title` key.
148
114
 
149
115
  ## Contributing
150
116
 
@@ -17,7 +17,7 @@ module Ivy
17
17
  end
18
18
 
19
19
  def get(resource)
20
- @getter.call(resource)
20
+ @getter.call(resource) if resource
21
21
  end
22
22
  end
23
23
  end
@@ -8,46 +8,49 @@ module Ivy
8
8
  @serializer = serializer
9
9
  @primary_resource_name = primary_resource_name
10
10
  @primary_resource = primary_resource
11
- @linked_resources = Hash.new { |hash, klass| hash[klass] = Set.new }
11
+ @included_resources = Hash.new { |hash, klass| hash[klass] = Set.new }
12
12
  end
13
13
 
14
14
  def belongs_to(name, resource, options={})
15
- link(resource) if options[:embed_in_root]
15
+ include_resource(resource) if resource && options[:embed_in_root]
16
16
  end
17
17
 
18
18
  def generate(generator)
19
19
  generator.document(self)
20
20
  end
21
21
 
22
- def generate_linked(generator)
23
- generator.linked(self) unless @linked_resources.empty?
22
+ def generate_attributes(generator, resource)
23
+ @serializer.attributes(generator, resource)
24
24
  end
25
25
 
26
- def generate_linked_resources(generator)
27
- @linked_resources.each_pair { |klass, resources| generator.linked_resources(klass, resources) }
26
+ def generate_included(generator)
27
+ generator.included(self) unless @included_resources.empty?
28
28
  end
29
29
 
30
- def generate_links(generator, resource)
31
- @serializer.links(generator, resource)
30
+ def generate_included_resources(generator)
31
+ @included_resources.each_pair { |klass, resources| generator.included_resources(klass, resources) }
32
+ end
33
+
34
+ def generate_relationships(generator, resource)
35
+ @serializer.relationships(generator, resource)
32
36
  end
33
37
 
34
38
  def generate_resource(generator, resource)
35
39
  @serializer.resource(generator, resource)
36
- generator.links(resource)
37
40
  end
38
41
 
39
42
  def has_many(name, resources, options={})
40
- link_many(resources) if options[:embed_in_root]
43
+ include_resources(resources) if options[:embed_in_root]
41
44
  end
42
45
 
43
46
  private
44
47
 
45
- def link(resource)
46
- @serializer.links(self, resource) if @linked_resources[resource.class].add?(resource)
48
+ def include_resource(resource)
49
+ @serializer.relationships(self, resource) if @included_resources[resource.class].add?(resource)
47
50
  end
48
51
 
49
- def link_many(resources)
50
- resources.each { |resource| link(resource) }
52
+ def include_resources(resources)
53
+ resources.each { |resource| include_resource(resource) }
51
54
  end
52
55
  end
53
56
  end
@@ -4,8 +4,8 @@ module Ivy
4
4
  module Serializers
5
5
  module Documents
6
6
  class IndividualResource < Document
7
- def generate_linked(generator)
8
- @serializer.links(self, @primary_resource)
7
+ def generate_included(generator)
8
+ @serializer.relationships(self, @primary_resource)
9
9
  super
10
10
  end
11
11
 
@@ -4,8 +4,8 @@ module Ivy
4
4
  module Serializers
5
5
  module Documents
6
6
  class ResourceCollection < Document
7
- def generate_linked(generator)
8
- @primary_resource.each { |resource| @serializer.links(self, resource) }
7
+ def generate_included(generator)
8
+ @primary_resource.each { |resource| @serializer.relationships(self, resource) }
9
9
  super
10
10
  end
11
11
 
@@ -29,10 +29,6 @@ module Ivy
29
29
  super(singularize(primary_resource_name).to_sym, primary_resource)
30
30
  end
31
31
 
32
- def primary_resources(primary_resources_name, primary_resources)
33
- super(primary_resources_name, primary_resources)
34
- end
35
-
36
32
  private
37
33
 
38
34
  def polymorphic_resource(resource)
@@ -19,13 +19,17 @@ module Ivy
19
19
  @hash_gen.store(key, value)
20
20
  end
21
21
 
22
+ def attributes(resource)
23
+ @document.generate_attributes(self, resource)
24
+ end
25
+
22
26
  def belongs_to(name, resource, options={})
23
27
  id(name, resource)
24
28
  end
25
29
 
26
30
  def document(document)
27
31
  document.generate_primary_resource(self)
28
- document.generate_linked(self)
32
+ document.generate_included(self)
29
33
  end
30
34
 
31
35
  def has_many(name, resources, options={})
@@ -40,19 +44,15 @@ module Ivy
40
44
  attribute(key, resources.map { |resource| extract_id(resource) })
41
45
  end
42
46
 
43
- def linked(document)
44
- document.generate_linked_resources(self)
47
+ def included(document)
48
+ document.generate_included_resources(self)
45
49
  end
46
50
 
47
- def linked_resources(resource_class, resources)
51
+ def included_resources(resource_class, resources)
48
52
  key = key_for_collection(resource_class).to_sym
49
53
  @hash_gen.store_array(key) { resources(resources) }
50
54
  end
51
55
 
52
- def links(resource)
53
- @document.generate_links(self, resource)
54
- end
55
-
56
56
  def primary_resource(primary_resource_name, primary_resource)
57
57
  @hash_gen.store_object(primary_resource_name) { resource(primary_resource) }
58
58
  end
@@ -61,7 +61,12 @@ module Ivy
61
61
  @hash_gen.store_array(primary_resources_name) { resources(primary_resources) }
62
62
  end
63
63
 
64
+ def relationships(resource)
65
+ @document.generate_relationships(self, resource)
66
+ end
67
+
64
68
  def resource(resource)
69
+ id(:id, resource)
65
70
  @document.generate_resource(self, resource)
66
71
  end
67
72
 
@@ -76,11 +81,11 @@ module Ivy
76
81
  private
77
82
 
78
83
  def extract_id(resource)
79
- resource.id
84
+ resource.id if resource
80
85
  end
81
86
 
82
87
  def extract_type(resource)
83
- ActiveSupport::Inflector.dasherize(ActiveSupport::Inflector.underscore(resource.class.name))
88
+ ActiveSupport::Inflector.dasherize(key_for_individual(resource.class))
84
89
  end
85
90
 
86
91
  def key_for_collection(resource_class)
@@ -4,27 +4,26 @@ module Ivy
4
4
  module Serializers
5
5
  module Formats
6
6
  class JSONAPI < JSON
7
- def attribute(key, value)
8
- value = coerce_id(value) if key == :id
9
- super
7
+ def attributes(resource)
8
+ @hash_gen.store_object(:attributes) { super }
10
9
  end
11
10
 
12
11
  def belongs_to(name, resource, options={})
13
- @hash_gen.store_object(name) { linkage(resource) }
12
+ if resource
13
+ @hash_gen.store_object(name) { linkage(resource) }
14
+ else
15
+ @hash_gen.store(name, nil)
16
+ end
14
17
  end
15
18
 
16
19
  def has_many(name, resources, options={})
17
20
  @hash_gen.store_object(name) { linkages(resources) }
18
21
  end
19
22
 
20
- def linked(document)
23
+ def included(document)
21
24
  @hash_gen.store_object(:included) { super }
22
25
  end
23
26
 
24
- def links(document)
25
- @hash_gen.store_object(:links) { super }
26
- end
27
-
28
27
  def primary_resource(primary_resource_name, primary_resource)
29
28
  super(:data, primary_resource)
30
29
  end
@@ -33,6 +32,10 @@ module Ivy
33
32
  super(:data, primary_resources)
34
33
  end
35
34
 
35
+ def relationships(document)
36
+ @hash_gen.store_object(:relationships) { super }
37
+ end
38
+
36
39
  def resource(resource)
37
40
  super
38
41
  type(:type, resource)
@@ -49,7 +52,7 @@ module Ivy
49
52
  end
50
53
 
51
54
  def linkage(resource)
52
- @hash_gen.store_object(:linkage) { linkage_object(resource) }
55
+ @hash_gen.store_object(:data) { linkage_object(resource) }
53
56
  end
54
57
 
55
58
  def linkage_object(resource)
@@ -58,7 +61,7 @@ module Ivy
58
61
  end
59
62
 
60
63
  def linkages(resources)
61
- @hash_gen.store_array(:linkage) { linkage_objects(resources) }
64
+ @hash_gen.store_array(:data) { linkage_objects(resources) }
62
65
  end
63
66
 
64
67
  def linkage_objects(resources)
@@ -6,10 +6,8 @@ module Ivy
6
6
  class Mapping
7
7
  def initialize(klass)
8
8
  @attrs = {}
9
- @links = {}
9
+ @relationships = {}
10
10
  @klass = klass
11
-
12
- attribute(:id)
13
11
  end
14
12
 
15
13
  def attribute(name, &block)
@@ -21,19 +19,24 @@ module Ivy
21
19
  end
22
20
 
23
21
  def belongs_to(name, options={}, &block)
24
- @links[name] = Relationships::BelongsTo.new(name, options, &block)
22
+ @relationships[name] = Relationships::BelongsTo.new(name, options, &block)
23
+ end
24
+
25
+ def generate_attributes(generator, resource)
26
+ @attrs.each_value { |attr| attr.generate(generator, resource) }
25
27
  end
26
28
 
27
29
  def has_many(name, options={}, &block)
28
- @links[name] = Relationships::HasMany.new(name, options, &block)
30
+ @relationships[name] = Relationships::HasMany.new(name, options, &block)
29
31
  end
30
32
 
31
- def links(generator, resource)
32
- @links.each_value { |link| link.generate(generator, resource) }
33
+ def relationships(generator, resource)
34
+ @relationships.each_value { |relationship| relationship.generate(generator, resource) }
33
35
  end
34
36
 
35
37
  def resource(generator, resource)
36
- @attrs.each_value { |attr| attr.generate(generator, resource) }
38
+ generator.attributes(resource) unless @attrs.empty?
39
+ generator.relationships(resource) unless @relationships.empty?
37
40
  end
38
41
  end
39
42
  end
@@ -7,14 +7,18 @@ module Ivy
7
7
  @mappings = Hash.new { |hash, klass| hash[klass] = new_mapping(klass) }
8
8
  end
9
9
 
10
- def links(generator, resource)
11
- mapping_for(resource.class).links(generator, resource)
10
+ def attributes(generator, resource)
11
+ mapping_for(resource.class).generate_attributes(generator, resource)
12
12
  end
13
13
 
14
14
  def map(klass, &block)
15
15
  mapping_for(klass).instance_eval(&block)
16
16
  end
17
17
 
18
+ def relationships(generator, resource)
19
+ mapping_for(resource.class).relationships(generator, resource)
20
+ end
21
+
18
22
  def resource(generator, resource)
19
23
  mapping_for(resource.class).resource(generator, resource)
20
24
  end
@@ -6,12 +6,16 @@ module Ivy
6
6
  class << self
7
7
  attr_accessor :_registry
8
8
 
9
+ def attributes(generator, resource)
10
+ _registry.attributes(generator, resource)
11
+ end
12
+
9
13
  def inherited(base)
10
14
  base._registry = Registry.new
11
15
  end
12
16
 
13
- def links(generator, resource)
14
- _registry.links(generator, resource)
17
+ def relationships(generator, resource)
18
+ _registry.relationships(generator, resource)
15
19
  end
16
20
 
17
21
  def map(klass, &block)
@@ -23,8 +27,12 @@ module Ivy
23
27
  end
24
28
  end
25
29
 
26
- def links(generator, links)
27
- self.class.links(generator, links)
30
+ def attributes(generator, resource)
31
+ self.class.attributes(generator, resource)
32
+ end
33
+
34
+ def relationships(generator, resource)
35
+ self.class.relationships(generator, resource)
28
36
  end
29
37
 
30
38
  def resource(generator, resource)
@@ -1,5 +1,5 @@
1
1
  module Ivy
2
2
  module Serializers
3
- VERSION = '0.2.0'
3
+ VERSION = '0.3.0'
4
4
  end
5
5
  end
@@ -87,6 +87,12 @@ RSpec.describe Ivy::Serializers::Formats::ActiveModelSerializers do
87
87
  let(:resource) { post }
88
88
 
89
89
  it { should eq(:post => {:author_id => 1, :id => 1}) }
90
+
91
+ context 'with no related resource' do
92
+ let(:author) { nil }
93
+
94
+ it { should eq(:post => {:author_id => nil, :id => 1}) }
95
+ end
90
96
  end
91
97
 
92
98
  context 'for a resource collection' do
@@ -107,6 +113,12 @@ RSpec.describe Ivy::Serializers::Formats::ActiveModelSerializers do
107
113
  let(:resource) { post }
108
114
 
109
115
  it { should eq(:post => {:id => 1, :user_id => 1}) }
116
+
117
+ context 'with no related resource' do
118
+ let(:author) { nil }
119
+
120
+ it { should eq(:post => {:id => 1, :user_id => nil}) }
121
+ end
110
122
  end
111
123
 
112
124
  context 'for a resource collection' do
@@ -130,6 +142,12 @@ RSpec.describe Ivy::Serializers::Formats::ActiveModelSerializers do
130
142
  :authors => [{:id => 1}],
131
143
  :post => {:author_id => 1, :id => 1}
132
144
  ) }
145
+
146
+ context 'with no related resource' do
147
+ let(:author) { nil }
148
+
149
+ it { should eq(:post => {:author_id => nil, :id => 1}) }
150
+ end
133
151
  end
134
152
 
135
153
  context 'for a resource collection' do
@@ -153,6 +171,12 @@ RSpec.describe Ivy::Serializers::Formats::ActiveModelSerializers do
153
171
  let(:resource) { post }
154
172
 
155
173
  it { should eq(:post => {:author => {:id => 1, :type => 'author'}, :id => 1}) }
174
+
175
+ context 'with no related resource' do
176
+ let(:author) { nil }
177
+
178
+ it { should eq(:post => {:author => nil, :id => 1}) }
179
+ end
156
180
  end
157
181
 
158
182
  context 'for a resource collection' do
@@ -19,8 +19,7 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
19
19
  it { should eq({
20
20
  :data => {
21
21
  :type => 'post',
22
- :id => '1',
23
- :links => {}
22
+ :id => '1'
24
23
  }
25
24
  }) }
26
25
  end
@@ -31,8 +30,7 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
31
30
  it { should eq({
32
31
  :data => [{
33
32
  :type => 'post',
34
- :id => '1',
35
- :links => {}
33
+ :id => '1'
36
34
  }]
37
35
  }) }
38
36
  end
@@ -56,8 +54,9 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
56
54
  :data => {
57
55
  :type => 'post',
58
56
  :id => '1',
59
- :title => 'title',
60
- :links => {}
57
+ :attributes => {
58
+ :title => 'title'
59
+ }
61
60
  }
62
61
  }) }
63
62
  end
@@ -69,8 +68,9 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
69
68
  :data => [{
70
69
  :type => 'post',
71
70
  :id => '1',
72
- :title => 'title',
73
- :links => {}
71
+ :attributes => {
72
+ :title => 'title'
73
+ }
74
74
  }]
75
75
  }) }
76
76
  end
@@ -90,8 +90,9 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
90
90
  :data => {
91
91
  :type => 'post',
92
92
  :id => '1',
93
- :headline => 'title',
94
- :links => {}
93
+ :attributes => {
94
+ :headline => 'title'
95
+ }
95
96
  }
96
97
  }) }
97
98
  end
@@ -103,8 +104,9 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
103
104
  :data => [{
104
105
  :type => 'post',
105
106
  :id => '1',
106
- :headline => 'title',
107
- :links => {}
107
+ :attributes => {
108
+ :headline => 'title'
109
+ }
108
110
  }]
109
111
  }) }
110
112
  end
@@ -131,13 +133,27 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
131
133
  :data => {
132
134
  :type => 'post',
133
135
  :id => '1',
134
- :links => {
136
+ :relationships => {
135
137
  :author => {
136
- :linkage => {:id => '1', :type => 'author'}
138
+ :data => {:id => '1', :type => 'author'}
137
139
  }
138
140
  }
139
141
  }
140
142
  }) }
143
+
144
+ context 'with no related resource' do
145
+ let(:author) { nil }
146
+
147
+ it { should eq({
148
+ :data => {
149
+ :type => 'post',
150
+ :id => '1',
151
+ :relationships => {
152
+ :author => nil
153
+ }
154
+ }
155
+ }) }
156
+ end
141
157
  end
142
158
 
143
159
  context 'for a resource collection' do
@@ -147,9 +163,9 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
147
163
  :data => [{
148
164
  :type => 'post',
149
165
  :id => '1',
150
- :links => {
166
+ :relationships => {
151
167
  :author => {
152
- :linkage => {:id => '1', :type => 'author'}
168
+ :data => {:id => '1', :type => 'author'}
153
169
  }
154
170
  }
155
171
  }]
@@ -171,13 +187,27 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
171
187
  :data => {
172
188
  :type => 'post',
173
189
  :id => '1',
174
- :links => {
190
+ :relationships => {
175
191
  :user => {
176
- :linkage => {:id => '1', :type => 'author'}
192
+ :data => {:id => '1', :type => 'author'}
177
193
  }
178
194
  }
179
195
  }
180
196
  }) }
197
+
198
+ context 'with no related resource' do
199
+ let(:author) { nil }
200
+
201
+ it { should eq({
202
+ :data => {
203
+ :type => 'post',
204
+ :id => '1',
205
+ :relationships => {
206
+ :user => nil
207
+ }
208
+ }
209
+ }) }
210
+ end
181
211
  end
182
212
 
183
213
  context 'for a resource collection' do
@@ -187,9 +217,9 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
187
217
  :data => [{
188
218
  :type => 'post',
189
219
  :id => '1',
190
- :links => {
220
+ :relationships => {
191
221
  :user => {
192
- :linkage => {:id => '1', :type => 'author'}
222
+ :data => {:id => '1', :type => 'author'}
193
223
  }
194
224
  }
195
225
  }]
@@ -211,9 +241,9 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
211
241
  :data => {
212
242
  :type => 'post',
213
243
  :id => '1',
214
- :links => {
244
+ :relationships => {
215
245
  :author => {
216
- :linkage => {:id => '1', :type => 'author'}
246
+ :data => {:id => '1', :type => 'author'}
217
247
  }
218
248
  }
219
249
  },
@@ -221,11 +251,24 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
221
251
  :included => {
222
252
  :authors => [{
223
253
  :id => '1',
224
- :links => {},
225
254
  :type => 'author'
226
255
  }]
227
256
  }
228
257
  }) }
258
+
259
+ context 'with no related resource' do
260
+ let(:author) { nil }
261
+
262
+ it { should eq({
263
+ :data => {
264
+ :type => 'post',
265
+ :id => '1',
266
+ :relationships => {
267
+ :author => nil
268
+ }
269
+ }
270
+ }) }
271
+ end
229
272
  end
230
273
 
231
274
  context 'for a resource collection' do
@@ -235,9 +278,9 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
235
278
  :data => [{
236
279
  :type => 'post',
237
280
  :id => '1',
238
- :links => {
281
+ :relationships => {
239
282
  :author => {
240
- :linkage => {:id => '1', :type => 'author'}
283
+ :data => {:id => '1', :type => 'author'}
241
284
  }
242
285
  }
243
286
  }],
@@ -245,7 +288,6 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
245
288
  :included => {
246
289
  :authors => [{
247
290
  :id => '1',
248
- :links => {},
249
291
  :type => 'author'
250
292
  }]
251
293
  }
@@ -267,13 +309,27 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
267
309
  :data => {
268
310
  :type => 'post',
269
311
  :id => '1',
270
- :links => {
312
+ :relationships => {
271
313
  :author => {
272
- :linkage => {:id => '1', :type => 'author'}
314
+ :data => {:id => '1', :type => 'author'}
273
315
  }
274
316
  }
275
317
  }
276
318
  }) }
319
+
320
+ context 'with no related resource' do
321
+ let(:author) { nil }
322
+
323
+ it { should eq({
324
+ :data => {
325
+ :type => 'post',
326
+ :id => '1',
327
+ :relationships => {
328
+ :author => nil
329
+ }
330
+ }
331
+ }) }
332
+ end
277
333
  end
278
334
 
279
335
  context 'for a resource collection' do
@@ -283,9 +339,9 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
283
339
  :data => [{
284
340
  :type => 'post',
285
341
  :id => '1',
286
- :links => {
342
+ :relationships => {
287
343
  :author => {
288
- :linkage => {:id => '1', :type => 'author'}
344
+ :data => {:id => '1', :type => 'author'}
289
345
  }
290
346
  }
291
347
  }]
@@ -314,9 +370,9 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
314
370
  :data => {
315
371
  :type => 'post',
316
372
  :id => '1',
317
- :links => {
373
+ :relationships => {
318
374
  :comments => {
319
- :linkage => [{:id => '1', :type => 'comment'}]
375
+ :data => [{:id => '1', :type => 'comment'}]
320
376
  }
321
377
  }
322
378
  }
@@ -330,9 +386,9 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
330
386
  :data => [{
331
387
  :type => 'post',
332
388
  :id => '1',
333
- :links => {
389
+ :relationships => {
334
390
  :comments => {
335
- :linkage => [{:id => '1', :type => 'comment'}]
391
+ :data => [{:id => '1', :type => 'comment'}]
336
392
  }
337
393
  }
338
394
  }]
@@ -354,9 +410,9 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
354
410
  :data => {
355
411
  :type => 'post',
356
412
  :id => '1',
357
- :links => {
413
+ :relationships => {
358
414
  :replies => {
359
- :linkage => [{:id => '1', :type => 'comment'}]
415
+ :data => [{:id => '1', :type => 'comment'}]
360
416
  }
361
417
  }
362
418
  }
@@ -370,9 +426,9 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
370
426
  :data => [{
371
427
  :type => 'post',
372
428
  :id => '1',
373
- :links => {
429
+ :relationships => {
374
430
  :replies => {
375
- :linkage => [{:id => '1', :type => 'comment'}]
431
+ :data => [{:id => '1', :type => 'comment'}]
376
432
  }
377
433
  }
378
434
  }]
@@ -394,9 +450,9 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
394
450
  :data => {
395
451
  :type => 'post',
396
452
  :id => '1',
397
- :links => {
453
+ :relationships => {
398
454
  :comments => {
399
- :linkage => [{:id => '1', :type => 'comment'}]
455
+ :data => [{:id => '1', :type => 'comment'}]
400
456
  }
401
457
  }
402
458
  },
@@ -404,8 +460,7 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
404
460
  :included => {
405
461
  :comments => [{
406
462
  :type => 'comment',
407
- :id => '1',
408
- :links => {}
463
+ :id => '1'
409
464
  }]
410
465
  }
411
466
  }) }
@@ -418,9 +473,9 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
418
473
  :data => [{
419
474
  :type => 'post',
420
475
  :id => '1',
421
- :links => {
476
+ :relationships => {
422
477
  :comments => {
423
- :linkage => [{:id => '1', :type => 'comment'}]
478
+ :data => [{:id => '1', :type => 'comment'}]
424
479
  }
425
480
  }
426
481
  }],
@@ -428,8 +483,7 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
428
483
  :included => {
429
484
  :comments => [{
430
485
  :type => 'comment',
431
- :id => '1',
432
- :links => {}
486
+ :id => '1'
433
487
  }]
434
488
  }
435
489
  }) }
@@ -450,9 +504,9 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
450
504
  :data => {
451
505
  :type => 'post',
452
506
  :id => '1',
453
- :links => {
507
+ :relationships => {
454
508
  :comments => {
455
- :linkage => [{:id => '1', :type => 'comment'}]
509
+ :data => [{:id => '1', :type => 'comment'}]
456
510
  }
457
511
  }
458
512
  }
@@ -466,9 +520,9 @@ RSpec.describe Ivy::Serializers::Formats::JSONAPI do
466
520
  :data => [{
467
521
  :type => 'post',
468
522
  :id => '1',
469
- :links => {
523
+ :relationships => {
470
524
  :comments => {
471
- :linkage => [{:id => '1', :type => 'comment'}]
525
+ :data => [{:id => '1', :type => 'comment'}]
472
526
  }
473
527
  }
474
528
  }]
@@ -0,0 +1,283 @@
1
+ require 'ivy/serializers'
2
+
3
+ RSpec.describe Ivy::Serializers::Formats::JSON do
4
+ let(:format) { described_class.new(document) }
5
+
6
+ describe '#as_json' do
7
+ let(:registry) { Ivy::Serializers::Registry.new }
8
+ let(:document) { Ivy::Serializers::Documents.create(registry, :posts, resource) }
9
+
10
+ subject { format.as_json }
11
+
12
+ context 'with default mapping' do
13
+ let(:post) { double('post', :id => 1) }
14
+
15
+ context 'for an individual resource' do
16
+ let(:resource) { post }
17
+
18
+ it { should eq(:posts => {:id => 1}) }
19
+ end
20
+
21
+ context 'for a resource collection' do
22
+ let(:resource) { [post] }
23
+
24
+ it { should eq(:posts => [{:id => 1}]) }
25
+ end
26
+ end
27
+
28
+ context 'with an attribute' do
29
+ let(:post_class) { double('Post') }
30
+ let(:post) { double('post', :class => post_class, :id => 1, :title => 'title') }
31
+
32
+ context 'with default options' do
33
+ before do
34
+ registry.map post_class do
35
+ attribute :title
36
+ end
37
+ end
38
+
39
+ context 'for an individual resource' do
40
+ let(:resource) { post }
41
+
42
+ it { should eq(:posts => {:id => 1, :title => 'title'}) }
43
+ end
44
+
45
+ context 'for a resource collection' do
46
+ let(:resource) { [post] }
47
+
48
+ it { should eq(:posts => [{:id => 1, :title => 'title'}]) }
49
+ end
50
+ end
51
+
52
+ context 'with a block provided' do
53
+ before do
54
+ registry.map post_class do
55
+ attribute(:headline) { |post| post.title }
56
+ end
57
+ end
58
+
59
+ context 'for an individual resource' do
60
+ let(:resource) { post }
61
+
62
+ it { should eq(:posts => {:id => 1, :headline => 'title'}) }
63
+ end
64
+
65
+ context 'for a resource collection' do
66
+ let(:resource) { [post] }
67
+
68
+ it { should eq(:posts => [{:id => 1, :headline => 'title'}]) }
69
+ end
70
+ end
71
+ end
72
+
73
+ context 'with a belongs_to relationship' do
74
+ let(:author_class) { double('Author', :name => 'Author') }
75
+ let(:author) { double('author', :class => author_class, :id => 1) }
76
+ let(:post_class) { double('Post') }
77
+ let(:post) { double('post', :author => author, :class => post_class, :id => 1) }
78
+
79
+ context 'with default options' do
80
+ before do
81
+ registry.map post_class do
82
+ belongs_to :author
83
+ end
84
+ end
85
+
86
+ context 'for an individual resource' do
87
+ let(:resource) { post }
88
+
89
+ it { should eq(:posts => {:author => 1, :id => 1}) }
90
+
91
+ context 'with no related resource' do
92
+ let(:author) { nil }
93
+
94
+ it { should eq(:posts => {:author => nil, :id => 1}) }
95
+ end
96
+ end
97
+
98
+ context 'for a resource collection' do
99
+ let(:resource) { [post] }
100
+
101
+ it { should eq(:posts => [{:author => 1, :id => 1}]) }
102
+ end
103
+ end
104
+
105
+ context 'with a block provided' do
106
+ before do
107
+ registry.map post_class do
108
+ belongs_to(:user) { |post| post.author }
109
+ end
110
+ end
111
+
112
+ context 'for an individual resource' do
113
+ let(:resource) { post }
114
+
115
+ it { should eq(:posts => {:id => 1, :user => 1}) }
116
+
117
+ context 'with no related resource' do
118
+ let(:author) { nil }
119
+
120
+ it { should eq(:posts => {:id => 1, :user => nil}) }
121
+ end
122
+ end
123
+
124
+ context 'for a resource collection' do
125
+ let(:resource) { [post] }
126
+
127
+ it { should eq(:posts => [{:id => 1, :user => 1}]) }
128
+ end
129
+ end
130
+
131
+ context 'with the :embed_in_root option' do
132
+ before do
133
+ registry.map post_class do
134
+ belongs_to :author, :embed_in_root => true
135
+ end
136
+ end
137
+
138
+ context 'for an individual resource' do
139
+ let(:resource) { post }
140
+
141
+ it { should eq(
142
+ :authors => [{:id => 1}],
143
+ :posts => {:author => 1, :id => 1}
144
+ ) }
145
+
146
+ context 'with no related resource' do
147
+ let(:author) { nil }
148
+
149
+ it { should eq(:posts => {:author => nil, :id => 1}) }
150
+ end
151
+ end
152
+
153
+ context 'for a resource collection' do
154
+ let(:resource) { [post] }
155
+
156
+ it { should eq(
157
+ :authors => [{:id => 1}],
158
+ :posts => [{:author => 1, :id => 1}]
159
+ ) }
160
+ end
161
+ end
162
+
163
+ context 'with the :polymorphic option' do
164
+ before do
165
+ registry.map post_class do
166
+ belongs_to :author, :polymorphic => true
167
+ end
168
+ end
169
+
170
+ context 'for an individual resource' do
171
+ let(:resource) { post }
172
+
173
+ it { should eq(:posts => {:author => 1, :id => 1}) }
174
+
175
+ context 'with no related resource' do
176
+ let(:author) { nil }
177
+
178
+ it { should eq(:posts => {:author => nil, :id => 1}) }
179
+ end
180
+ end
181
+
182
+ context 'for a resource collection' do
183
+ let(:resource) { [post] }
184
+
185
+ it { should eq(:posts => [{:author => 1, :id => 1}]) }
186
+ end
187
+ end
188
+ end
189
+
190
+ context 'with a has_many relationship' do
191
+ let(:comment_class) { double('Comment', :name => 'Comment') }
192
+ let(:comment) { double('comment', :class => comment_class, :id => 1) }
193
+ let(:post_class) { double('Post') }
194
+ let(:post) { double('post', :class => post_class, :comments => [comment], :id => 1) }
195
+
196
+ context 'with default options' do
197
+ before do
198
+ registry.map post_class do
199
+ has_many :comments
200
+ end
201
+ end
202
+
203
+ context 'for an individual resource' do
204
+ let(:resource) { post }
205
+
206
+ it { should eq(:posts => {:comments => [1], :id => 1}) }
207
+ end
208
+
209
+ context 'for a resource collection' do
210
+ let(:resource) { [post] }
211
+
212
+ it { should eq(:posts => [{:comments => [1], :id => 1}]) }
213
+ end
214
+ end
215
+
216
+ context 'with a block provided' do
217
+ before do
218
+ registry.map post_class do
219
+ has_many(:replies) { |post| post.comments }
220
+ end
221
+ end
222
+
223
+ context 'for an individual resource' do
224
+ let(:resource) { post }
225
+
226
+ it { should eq(:posts => {:id => 1, :replies => [1]}) }
227
+ end
228
+
229
+ context 'for a resource collection' do
230
+ let(:resource) { [post] }
231
+
232
+ it { should eq(:posts => [{:id => 1, :replies => [1]}]) }
233
+ end
234
+ end
235
+
236
+ context 'with the :embed_in_root option' do
237
+ before do
238
+ registry.map post_class do
239
+ has_many :comments, :embed_in_root => true
240
+ end
241
+ end
242
+
243
+ context 'for an individual resource' do
244
+ let(:resource) { post }
245
+
246
+ it { should eq(
247
+ :comments => [{:id => 1}],
248
+ :posts => {:comments => [1], :id => 1}
249
+ ) }
250
+ end
251
+
252
+ context 'for a resource collection' do
253
+ let(:resource) { [post] }
254
+
255
+ it { should eq(
256
+ :comments => [{:id => 1}],
257
+ :posts => [{:comments => [1], :id => 1}]
258
+ ) }
259
+ end
260
+ end
261
+
262
+ context 'with the :polymorphic option' do
263
+ before do
264
+ registry.map post_class do
265
+ has_many :comments, :polymorphic => true
266
+ end
267
+ end
268
+
269
+ context 'for an individual resource' do
270
+ let(:resource) { post }
271
+
272
+ it { should eq(:posts => {:comments => [1], :id => 1}) }
273
+ end
274
+
275
+ context 'for a resource collection' do
276
+ let(:resource) { [post] }
277
+
278
+ it { should eq(:posts => [{:comments => [1], :id => 1}]) }
279
+ end
280
+ end
281
+ end
282
+ end
283
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ivy-serializers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dray Lacy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-05 00:00:00.000000000 Z
11
+ date: 2015-06-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -130,6 +130,7 @@ files:
130
130
  - lib/ivy/serializers/version.rb
131
131
  - spec/integration/formats/active_model_serializers_spec.rb
132
132
  - spec/integration/formats/json_api_spec.rb
133
+ - spec/integration/formats/json_spec.rb
133
134
  - spec/integration/serializer_spec.rb
134
135
  - spec/spec_helper.rb
135
136
  homepage: ''
@@ -159,5 +160,6 @@ summary: JSON serialization for client-side apps.
159
160
  test_files:
160
161
  - spec/integration/formats/active_model_serializers_spec.rb
161
162
  - spec/integration/formats/json_api_spec.rb
163
+ - spec/integration/formats/json_spec.rb
162
164
  - spec/integration/serializer_spec.rb
163
165
  - spec/spec_helper.rb