jei 0.2.0 → 0.3.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: 7bf0d7bbd5077695a2e1219961576f894a5cd41f
4
- data.tar.gz: 34cb6f17efada6cd82e01d23e8e67a16f7f2414e
3
+ metadata.gz: 171c14fb585c7f29a0c740386776f278d423a72a
4
+ data.tar.gz: c8eeded9a3066460a0ff6946bac4d3b6e78d6adb
5
5
  SHA512:
6
- metadata.gz: 89a27cbfe0af409953efaf4da6d83f7a8f78659bf2ca58a40a22a1b148d8df1eb9d48a2b65e76f374b38598618a2fcf8310b425d001977ef0580a2a3186ed59b
7
- data.tar.gz: 5972dc16d4c2df09654730607ffbfa92165751a12fc3c6284923b4516af448c987e2b7747f99c4d39bf7266bfb09a5915b0bec0b2496685049bd5388c78764e3
6
+ metadata.gz: 1cc6431cfd6b4959b7af2fe826e5dec6a0f062b091f76aeff1bdcda88dbbc1926105c6f3bc89e62a5169021e617814e71988c6bb4057c3c65a010d299ad87793
7
+ data.tar.gz: 35d9daae8116b08430315435ae4b5f3e36e5781eb2fedca6531597c55192d38b0a9d21d1d687728903142a7914900c67accf6a9d0ef0beb9878548d8e3b37ebf
@@ -1,4 +1,5 @@
1
+ sudo: false
1
2
  language: ruby
2
3
  rvm:
3
- - 2.3.0
4
- before_install: gem install bundler -v 1.11.2
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.12.1
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## HEAD
4
4
 
5
+ ## 0.3.0 (2016-05-03)
6
+
7
+ * [CHANGE] Compound documents include full linkage. A relationship path
8
+ overrides a relationship's `:data` option.
9
+ * [ADD] Add `:errors` document build option to override primary data with an
10
+ array of error objects.
11
+ * [CHANGE] Directly build a document rather than building and evaluating a
12
+ parse tree. The tree ended up not being a very useful intermediate
13
+ representation, and removing it improves synthetic benchmarks by ~25%.
14
+
5
15
  ## 0.2.0 (2016-03-22)
6
16
 
7
17
  * [ADD] Add `:fields` document build option for sparse fieldsets.
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Jei
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/jei.svg)](https://badge.fury.io/rb/jei)
4
+ [![Build Status](https://travis-ci.org/zaeleus/jei.svg?branch=master)](https://travis-ci.org/zaeleus/jei)
5
+
3
6
  **Jei** is a simple serializer for Ruby that formats a JSON document described
4
7
  by [JSON API][jsonapi].
5
8
 
@@ -71,6 +74,33 @@ class Artist; end
71
74
  class ArtistSerializer < Jei::Serializer; end
72
75
  ```
73
76
 
77
+ #### Resource Identifiers
78
+
79
+ A serializer wraps a resource, which has an id and type. By default, the
80
+ serializer assumes the resource responds to `id`. If not, override
81
+ `Jei::Serializer#id` and return a custom id as a string.
82
+
83
+ ```ruby
84
+ class ArtistSerializer < Jei::Serializer
85
+ def id
86
+ resource.uuid
87
+ end
88
+ end
89
+ ```
90
+
91
+ The default resource type is the resource's lowercased class name with an 's'
92
+ appended. For example, `Artist` becomes "artists" and `Person` becomes
93
+ "persons". For a custom type, override `Jei::Serializer#type` and return a
94
+ string.
95
+
96
+ ```ruby
97
+ class PersonSerializer < Jei::Serializer
98
+ def type
99
+ 'people'.freeze
100
+ end
101
+ end
102
+ ```
103
+
74
104
  #### Attributes
75
105
 
76
106
  Attributes represent model data. They are defined in a serializer by using the
@@ -181,6 +211,9 @@ Each relationship object can be modified with the following options.
181
211
  building a data object with resource identifiers. Note that doing so does not
182
212
  emit a valid JSON API document unless a links or meta object is present.
183
213
 
214
+ To ensure full linkage, this option is overridden to `true` when the resource
215
+ is on the included relationship path.
216
+
184
217
  ```ruby
185
218
  class ArtistSerializer < Jei::Serializer
186
219
  has_many :albums, data: false
@@ -287,6 +320,38 @@ string (`#to_json`).
287
320
 
288
321
  Top level objects can be added using the following options.
289
322
 
323
+ * `:errors`: (`Array<Hash<Symbol, Object>>`) An array of
324
+ [error objects][error-objects]. Setting this prevents the primary data
325
+ member from being added to the document.
326
+
327
+ ```ruby
328
+ artist = Artist.new(id: 1, name: '')
329
+
330
+ errors = [{
331
+ status: '422',
332
+ source: {
333
+ pointer: '/data/attributes/name'
334
+ },
335
+ detail: "Name can't be blank"
336
+ }]
337
+
338
+ Jei::Document.build(artist, errors: errors)
339
+ ```
340
+
341
+ ```json
342
+ {
343
+ "errors": [{
344
+ "status": "422",
345
+ "source": {
346
+ "pointer": "/data/attributes/name"
347
+ },
348
+ "detail": "Name can't be blank"
349
+ }]
350
+ }
351
+ ```
352
+
353
+ [error-objects]: http://jsonapi.org/format/1.0/#error-objects
354
+
290
355
  * `:fields`: (`Hash<String, String>`) A map of resource type-fields that define
291
356
  sparse fieldsets. Keys are resource types, and fields are a comma-separated
292
357
  list of field names. For example, `{ 'artists' => 'name,albums', 'albums' =>
data/lib/jei.rb CHANGED
@@ -3,34 +3,8 @@ require 'set'
3
3
 
4
4
  require 'jei/error'
5
5
 
6
- require 'jei/nodes/node'
7
-
8
- require 'jei/nodes/attribute_node'
9
- require 'jei/nodes/attributes_node'
10
- require 'jei/nodes/document_node'
11
- require 'jei/nodes/included_node'
12
- require 'jei/nodes/json_api_node'
13
- require 'jei/nodes/link_node'
14
- require 'jei/nodes/links_node'
15
- require 'jei/nodes/meta_node'
16
- require 'jei/nodes/relationship_node'
17
- require 'jei/nodes/relationships_node'
18
- require 'jei/nodes/resource_identifier_node'
19
- require 'jei/nodes/resource_node'
20
-
21
- require 'jei/nodes/data_node'
22
- require 'jei/nodes/collection_data_node'
23
-
24
- require 'jei/builders/attributes_node_builder'
25
- require 'jei/builders/data_node_builder'
26
- require 'jei/builders/document_builder'
27
- require 'jei/builders/included_node_builder'
28
- require 'jei/builders/links_node_builder'
29
- require 'jei/builders/relationship_node_builder'
30
- require 'jei/builders/relationships_node_builder'
31
- require 'jei/builders/resource_node_builder'
32
-
33
6
  require 'jei/document'
7
+ require 'jei/document/builder'
34
8
  require 'jei/fieldset'
35
9
  require 'jei/link'
36
10
  require 'jei/path'
@@ -1,15 +1,18 @@
1
1
  module Jei
2
2
  class Document
3
3
  # @return [String]
4
- VERSION = '1.0'
4
+ VERSION = '1.0'.freeze
5
5
 
6
- # @return [DocumentNode]
6
+ # @return [Hash<Symbol, Object>]
7
7
  attr_reader :root
8
+ alias_method :to_h, :root
8
9
 
9
10
  # Builds a document from a resource.
10
11
  #
11
12
  # @param [Object] resource
12
13
  # @param [Hash<Symbol, Object>] options
14
+ # @option options [Array<Hash<Symbol, Object>>] :errors override primary
15
+ # data with an array of error objects
13
16
  # @option options [Hash<String, String>] :fields restrict resource
14
17
  # attributes and relationships to a user-defined set of fields
15
18
  # @option options [String] :include a list of relationship paths
@@ -22,18 +25,11 @@ module Jei
22
25
  # @option options [Class] :serializer override the default serializer
23
26
  # @return [Document]
24
27
  def self.build(resource, options = {})
25
- Builders::DocumentBuilder.build(resource, options)
26
- end
27
-
28
- def initialize
29
- @root = Nodes::DocumentNode.new
28
+ Builder.build(resource, options)
30
29
  end
31
30
 
32
- # @return [Hash<Symbol, Object>]
33
- def to_h
34
- document = {}
35
- root.visit(document)
36
- document
31
+ def initialize(root = {})
32
+ @root = root
37
33
  end
38
34
 
39
35
  # @return [String]
@@ -0,0 +1,282 @@
1
+ module Jei
2
+ class Document
3
+ module Builder
4
+ EMPTY_COLLECTION = [].freeze
5
+ EMPTY_FIELDSETS = {}.freeze
6
+
7
+ module_function
8
+
9
+ # @param [Object] resource
10
+ # @param [Hash<Symbol, Object>] options
11
+ # @return [Document]
12
+ def build(resource, options = {})
13
+ document = Document.new
14
+ root = document.root
15
+
16
+ build_json_api(root) if options[:jsonapi]
17
+ build_meta(root, options[:meta]) if options[:meta]
18
+ build_links(root, options[:links]) if options[:links]
19
+
20
+ if options[:errors]
21
+ build_errors(root, options[:errors])
22
+ elsif resource.nil?
23
+ root[:data] = nil
24
+ elsif resource.is_a?(Enumerable)
25
+ build_collection(root, resource, options)
26
+ else
27
+ build_single(root, resource, options)
28
+ end
29
+
30
+ document
31
+ end
32
+
33
+ # @see http://jsonapi.org/format/1.0/#document-jsonapi-object
34
+ # @param [Hash<Symbol, Object>] context
35
+ def build_json_api(context)
36
+ context[:jsonapi] = { version: Document::VERSION }
37
+ end
38
+
39
+ # @see http://jsonapi.org/format/1.0/#document-meta
40
+ # @param [Hash<Symbol, Object>] context
41
+ # @param [Hash<Symbol, Object>] meta
42
+ def build_meta(context, meta)
43
+ context[:meta] = meta
44
+ end
45
+
46
+ # @see http://jsonapi.org/format/1.0/#document-links
47
+ # @param [Hash<Symbol, Object>] context
48
+ # @param [Array<Link>] links
49
+ def build_links(context, links)
50
+ root = {}
51
+ links.each { |link| build_link(root, link) }
52
+ context[:links] = root
53
+ end
54
+
55
+ # @see http://jsonapi.org/format/1.0/#document-links
56
+ # @param [Hash<Symbol, Object>] context
57
+ # @param [Link] link
58
+ def build_link(context, link)
59
+ context[link.name] =
60
+ if link.meta.any?
61
+ { href: link.href, meta: link.meta }
62
+ else
63
+ link.href
64
+ end
65
+ end
66
+
67
+ # @see http://jsonapi.org/format/1.0/#document-top-level
68
+ # @param [Hash<Symbol, Object>] context
69
+ # @param [Object] resource
70
+ # @param [Hash<Symbol, Object>] options
71
+ def build_single(context, resource, options)
72
+ fieldsets = options[:fields] ? Fieldset.parse(options[:fields]) : EMPTY_FIELDSETS
73
+
74
+ serializer = Serializer.factory(resource, options[:serializer])
75
+ fieldset = fieldsets[serializer.type]
76
+
77
+ if options[:include]
78
+ paths = Path.parse(options[:include])
79
+ serializers = Set.new
80
+ Path.find(paths, serializer, serializers)
81
+ end
82
+
83
+ root = {}
84
+ build_resource(root, serializer, fieldset)
85
+ context[:data] = root
86
+
87
+ if options[:include]
88
+ build_included(context, serializers, fieldsets)
89
+ end
90
+ end
91
+
92
+ # @see http://jsonapi.org/format/1.0/#document-top-level
93
+ # @param [Hash<Symbol, Object>] context
94
+ # @param [Enumerable] resources
95
+ # @param [Hash<Symbol, Object>] options
96
+ def build_collection(context, resources, options)
97
+ if resources.empty?
98
+ context[:data] = EMPTY_COLLECTION
99
+ return
100
+ end
101
+
102
+ fieldsets = options[:fields] ? Fieldset.parse(options[:fields]) : EMPTY_FIELDSETS
103
+
104
+ if options[:include]
105
+ paths = Path.parse(options[:include])
106
+ serializers = Set.new
107
+
108
+ context[:data] = resources.map do |resource|
109
+ serializer = Serializer.factory(resource, options[:serializer])
110
+
111
+ Path.find(paths, serializer, serializers)
112
+
113
+ root = {}
114
+ fieldset = fieldsets[serializer.type]
115
+
116
+ build_resource(root, serializer, fieldset)
117
+
118
+ root
119
+ end
120
+
121
+ build_included(context, serializers, fieldsets)
122
+ else
123
+ context[:data] = resources.map do |resource|
124
+ root = {}
125
+ serializer = Serializer.factory(resource, options[:serializer])
126
+ fieldset = fieldsets[serializer.type]
127
+
128
+ build_resource(root, serializer, fieldset)
129
+
130
+ root
131
+ end
132
+ end
133
+ end
134
+
135
+ # @see http://jsonapi.org/format/1.0/#document-resource-identifier-objects
136
+ # @param [Hash<Symbol, Object>] context
137
+ # @param [Serializer] serializer
138
+ def build_resource_identifier(context, serializer)
139
+ context[:id] = serializer.id
140
+ context[:type] = serializer.type
141
+ end
142
+
143
+ # @see http://jsonapi.org/format/1.0/#document-resource-objects
144
+ # @param [Hash<Symbol, Object>] context
145
+ # @param [Serializer] serializer
146
+ # @param [Array<Symbol>, nil] fieldset
147
+ def build_resource(context, serializer, fieldset)
148
+ build_resource_identifier(context, serializer)
149
+
150
+ attributes = serializer.attributes(fieldset).values
151
+
152
+ if attributes.any?
153
+ build_attributes(context, attributes, serializer)
154
+ end
155
+
156
+ relationships = serializer.relationships(fieldset).values
157
+
158
+ if relationships.any?
159
+ build_relationships(context, relationships, serializer)
160
+ end
161
+
162
+ links = serializer.links
163
+
164
+ if links
165
+ build_links(context, links)
166
+ end
167
+ end
168
+
169
+ # @see http://jsonapi.org/format/1.0/#document-resource-object-attributes
170
+ # @param [Hash<Symbol, Object>] context
171
+ # @param [Array<Attribute>] attributes
172
+ # @param [Serializer] serializer
173
+ def build_attributes(context, attributes, serializer)
174
+ root = {}
175
+
176
+ attributes.each do |attribute|
177
+ root[attribute.name] = attribute.evaluate(serializer)
178
+ end
179
+
180
+ context[:attributes] = root
181
+ end
182
+
183
+ # @see http://jsonapi.org/format/1.0/#document-resource-object-relationships
184
+ # @param [Hash<Symbol, Object>] context
185
+ # @param [Array<Relationship>] relationships
186
+ # @param [Serializer] serializer
187
+ def build_relationships(context, relationships, serializer)
188
+ root = {}
189
+
190
+ relationships.each do |relationship|
191
+ build_relationship(root, relationship, serializer)
192
+ end
193
+
194
+ context[:relationships] = root
195
+ end
196
+
197
+ # @see http://jsonapi.org/format/1.0/#document-resource-object-relationships
198
+ # @see http://jsonapi.org/format/1.0/#document-resource-object-linkage
199
+ # @param [Hash<Symbol, Object>] context
200
+ # @param [Relationship] relationship
201
+ # @param [Serializer] serializer
202
+ def build_relationship(context, relationship, serializer)
203
+ root = {}
204
+
205
+ if relationship.options[:data] ||
206
+ override_data?(serializer.options[:linkages], relationship)
207
+ case relationship
208
+ when BelongsToRelationship
209
+ build_belongs_to_relationship(root, relationship, serializer)
210
+ when HasManyRelationship
211
+ build_has_many_relationship(root, relationship, serializer)
212
+ else
213
+ raise ArgumentError, 'invalid relationship type'
214
+ end
215
+ end
216
+
217
+ if relationship.options[:links]
218
+ links = relationship.links(serializer)
219
+ build_links(root, links)
220
+ end
221
+
222
+ context[relationship.name] = root
223
+ end
224
+
225
+ def override_data?(linkages, relationship)
226
+ linkages && linkages.include?(relationship.name)
227
+ end
228
+
229
+ # @see http://jsonapi.org/format/1.0/#document-resource-object-linkage
230
+ # @param [Hash<Symbol, Object>] context
231
+ # @param [Relationship] relationship
232
+ # @param [Serializer] serializer
233
+ def build_belongs_to_relationship(context, relationship, serializer)
234
+ root = {}
235
+ resource = relationship.evaluate(serializer)
236
+ serializer = Serializer.factory(resource, relationship.options[:serializer])
237
+ build_resource_identifier(root, serializer)
238
+ context[:data] = root
239
+ end
240
+
241
+ # @see http://jsonapi.org/format/1.0/#document-resource-object-linkage
242
+ # @param [Hash<Symbol, Object>] context
243
+ # @param [Relationship] relationship
244
+ # @param [Serializer] serializer
245
+ def build_has_many_relationship(context, relationship, serializer)
246
+ resources = relationship.evaluate(serializer)
247
+
248
+ context[:data] =
249
+ if resources.empty?
250
+ EMPTY_COLLECTION
251
+ else
252
+ resources.map do |resource|
253
+ root = {}
254
+ serializer = Serializer.factory(resource, relationship.options[:serializer])
255
+ build_resource_identifier(root, serializer)
256
+ root
257
+ end
258
+ end
259
+ end
260
+
261
+ # @see http://jsonapi.org/format/1.0/#document-compound-documents
262
+ # @param [Hash<Symbol, Object>] context
263
+ # @param [Set<Serializer>] serializers
264
+ # @param [Hash<String, Array<Symbol>>] fieldsets
265
+ def build_included(context, serializers, fieldsets)
266
+ context[:included] = serializers.map do |serializer|
267
+ root = {}
268
+ fieldset = fieldsets[serializer.type]
269
+ build_resource(root, serializer, fieldset)
270
+ root
271
+ end
272
+ end
273
+
274
+ # @see http://jsonapi.org/format/1.0/#error-objects
275
+ # @param [Hash<Symbol, Object>] context
276
+ # @param [Array<Hash<Symbol, Object>>] errors
277
+ def build_errors(context, errors)
278
+ context[:errors] = errors
279
+ end
280
+ end
281
+ end
282
+ end
@@ -1,6 +1,6 @@
1
1
  module Jei
2
2
  class Fieldset
3
- NAME_SEPARATOR = ','
3
+ NAME_SEPARATOR = ','.freeze
4
4
 
5
5
  def self.parse(raw_fields)
6
6
  fields = {}
@@ -2,8 +2,8 @@ module Jei
2
2
  class Path
3
3
  class NameError < Jei::Error; end
4
4
 
5
- PATH_SEPARATOR = ','
6
- NAME_SEPARATOR = '.'
5
+ PATH_SEPARATOR = ','.freeze
6
+ NAME_SEPARATOR = '.'.freeze
7
7
 
8
8
  # @return [Array<Symbol>]
9
9
  attr_reader :names
@@ -37,8 +37,15 @@ module Jei
37
37
  return if level >= @names.length
38
38
 
39
39
  name = @names[level]
40
+
40
41
  relationship = serializer.relationships[name]
41
42
  raise NameError, "invalid relationship name `#{name}'" unless relationship
43
+
44
+ unless relationship.options[:data]
45
+ serializer.options[:linkages] = Set.new unless serializer.options[:linkages]
46
+ serializer.options[:linkages] << name
47
+ end
48
+
42
49
  resources = [*relationship.evaluate(serializer)]
43
50
 
44
51
  resources.each do |resource|
@@ -1,6 +1,6 @@
1
1
  module Jei
2
2
  class Serializer
3
- # @return [#id]
3
+ # @return [Object]
4
4
  attr_reader :resource
5
5
 
6
6
  # @return [Hash<Symbol, Attribute>]
@@ -62,8 +62,10 @@ module Jei
62
62
  end
63
63
 
64
64
  # @param [#id] resource
65
- def initialize(resource)
65
+ # @param [Hash<Symbol, Object>] options
66
+ def initialize(resource, options = nil)
66
67
  @resource = resource
68
+ @options = options
67
69
  end
68
70
 
69
71
  # @return [String]
@@ -93,6 +95,11 @@ module Jei
93
95
  nil
94
96
  end
95
97
 
98
+ # @return [Hash<Symbol, Object>]
99
+ def options
100
+ @options ||= {}
101
+ end
102
+
96
103
  # @return [Array<String>]
97
104
  def key
98
105
  [type, id]
@@ -100,13 +107,17 @@ module Jei
100
107
 
101
108
  # @return [Boolean]
102
109
  def ==(other)
103
- key == other.key
110
+ hash == other.hash
104
111
  end
105
112
  alias_method :eql?, :==
106
113
 
114
+ # Returns a digest for this serializer.
115
+ #
116
+ # The second element is shifted to preserve order.
117
+ #
107
118
  # @return [Fixnum]
108
119
  def hash
109
- key.hash
120
+ type.hash ^ (id.hash >> 1)
110
121
  end
111
122
 
112
123
  private
@@ -1,3 +1,3 @@
1
1
  module Jei
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jei
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
  - Michael Macias
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-03-22 00:00:00.000000000 Z
11
+ date: 2016-05-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -86,34 +86,12 @@ files:
86
86
  - jei.gemspec
87
87
  - lib/jei.rb
88
88
  - lib/jei/attribute.rb
89
- - lib/jei/builders/attributes_node_builder.rb
90
- - lib/jei/builders/data_node_builder.rb
91
- - lib/jei/builders/document_builder.rb
92
- - lib/jei/builders/included_node_builder.rb
93
- - lib/jei/builders/links_node_builder.rb
94
- - lib/jei/builders/relationship_node_builder.rb
95
- - lib/jei/builders/relationships_node_builder.rb
96
- - lib/jei/builders/resource_node_builder.rb
97
89
  - lib/jei/document.rb
90
+ - lib/jei/document/builder.rb
98
91
  - lib/jei/error.rb
99
92
  - lib/jei/field.rb
100
93
  - lib/jei/fieldset.rb
101
94
  - lib/jei/link.rb
102
- - lib/jei/nodes/attribute_node.rb
103
- - lib/jei/nodes/attributes_node.rb
104
- - lib/jei/nodes/collection_data_node.rb
105
- - lib/jei/nodes/data_node.rb
106
- - lib/jei/nodes/document_node.rb
107
- - lib/jei/nodes/included_node.rb
108
- - lib/jei/nodes/json_api_node.rb
109
- - lib/jei/nodes/link_node.rb
110
- - lib/jei/nodes/links_node.rb
111
- - lib/jei/nodes/meta_node.rb
112
- - lib/jei/nodes/node.rb
113
- - lib/jei/nodes/relationship_node.rb
114
- - lib/jei/nodes/relationships_node.rb
115
- - lib/jei/nodes/resource_identifier_node.rb
116
- - lib/jei/nodes/resource_node.rb
117
95
  - lib/jei/path.rb
118
96
  - lib/jei/relationship.rb
119
97
  - lib/jei/serializer.rb
@@ -1,19 +0,0 @@
1
- module Jei
2
- module Builders
3
- module AttributesNodeBuilder
4
- include Nodes
5
-
6
- # @param [Serializer] serializer
7
- # @return [AttributesNode]
8
- def self.build(attributes, serializer)
9
- node = AttributesNode.new
10
-
11
- attributes.each do |attribute|
12
- node.children << AttributeNode.new(serializer, attribute)
13
- end
14
-
15
- node
16
- end
17
- end
18
- end
19
- end
@@ -1,15 +0,0 @@
1
- module Jei
2
- module Builders
3
- module DataNodeBuilder
4
- include Nodes
5
-
6
- # @param [Serializer] serializer
7
- # @return [DataNode]
8
- def self.build(serializer)
9
- node = DataNode.new
10
- node.children << ResourceNodeBuilder.build(serializer)
11
- node
12
- end
13
- end
14
- end
15
- end
@@ -1,70 +0,0 @@
1
- module Jei
2
- module Builders
3
- module DocumentBuilder
4
- include Nodes
5
-
6
- # @param [Object] resource
7
- # @param [Hash<Symbol, Object>] options
8
- # @return [Document]
9
- def self.build(resource, options = {})
10
- document = Document.new
11
- root = document.root
12
-
13
- root.children << JSONAPINode.new if options[:jsonapi]
14
- root.children << MetaNode.new(options[:meta]) if options[:meta]
15
- root.children << LinksNodeBuilder.build(options[:links]) if options[:links]
16
-
17
- if resource.nil?
18
- root.children << DataNode.new
19
- return document
20
- end
21
-
22
- fieldsets = options[:fields] ? Fieldset.parse(options[:fields]) : {}
23
-
24
- if resource.is_a?(Enumerable)
25
- node = CollectionDataNode.new
26
-
27
- if options[:include]
28
- paths = Path.parse(options[:include])
29
- serializers = Set.new
30
-
31
- resource.each do |r|
32
- serializer = Serializer.factory(r, options[:serializer])
33
- Path.find(paths, serializer, serializers)
34
- fieldset = fieldsets[serializer.type]
35
- node.children << ResourceNodeBuilder.build(serializer, fieldset)
36
- end
37
-
38
- root.children << IncludedNodeBuilder.build(serializers, fieldsets)
39
- else
40
- resource.each do |r|
41
- serializer = Serializer.factory(r, options[:serializer])
42
- fieldset = fieldsets[serializer.type]
43
- node.children << ResourceNodeBuilder.build(serializer, fieldset)
44
- end
45
- end
46
-
47
- root.children << node
48
- else
49
- node = DataNode.new
50
-
51
- serializer = Serializer.factory(resource, options[:serializer])
52
- fieldset = fieldsets[serializer.type]
53
-
54
- node.children << ResourceNodeBuilder.build(serializer, fieldset)
55
-
56
- if options[:include]
57
- paths = Path.parse(options[:include])
58
- serializers = Set.new
59
- Path.find(paths, serializer, serializers)
60
- root.children << IncludedNodeBuilder.build(serializers, fieldsets)
61
- end
62
-
63
- root.children << node
64
- end
65
-
66
- document
67
- end
68
- end
69
- end
70
- end
@@ -1,21 +0,0 @@
1
- module Jei
2
- module Builders
3
- module IncludedNodeBuilder
4
- include Nodes
5
-
6
- # @param [Set<Serializer>] serializers
7
- # @param [Hash<String, String>] fieldset
8
- # @return [IncludedNode]
9
- def self.build(serializers, fieldsets = {})
10
- node = IncludedNode.new
11
-
12
- serializers.each do |serializer|
13
- fieldset = fieldsets[serializer.type]
14
- node.children << ResourceNodeBuilder.build(serializer, fieldset)
15
- end
16
-
17
- node
18
- end
19
- end
20
- end
21
- end
@@ -1,19 +0,0 @@
1
- module Jei
2
- module Builders
3
- module LinksNodeBuilder
4
- include Nodes
5
-
6
- # @param [Array<Link>] links
7
- # @return [LinksNode]
8
- def self.build(links)
9
- links_node = LinksNode.new
10
-
11
- links.each do |link|
12
- links_node.children << LinkNode.new(link)
13
- end
14
-
15
- links_node
16
- end
17
- end
18
- end
19
- end
@@ -1,68 +0,0 @@
1
- module Jei
2
- module Builders
3
- module RelationshipNodeBuilder
4
- include Nodes
5
-
6
- # @param [Relationship] relationship
7
- # @param [Serializer] serializer
8
- # @return [RelationshipNode]
9
- def self.build(relationship, serializer)
10
- node = RelationshipNode.new(relationship)
11
-
12
- if relationship.options[:data]
13
- node.children <<
14
- case relationship
15
- when BelongsToRelationship
16
- build_data_node(relationship, serializer)
17
- when HasManyRelationship
18
- build_collection_data_node(relationship, serializer)
19
- else
20
- raise ArgumentError, 'invalid relationship type'
21
- end
22
- end
23
-
24
- if relationship.options[:links]
25
- node.children << build_links_node(relationship, serializer)
26
- end
27
-
28
- node
29
- end
30
-
31
- # @param [Relationship] relationship
32
- # @param [Serializer] serializer
33
- # @return [DataNode]
34
- def self.build_data_node(relationship, serializer)
35
- node = DataNode.new
36
- resource = relationship.evaluate(serializer)
37
-
38
- serializer = Serializer.factory(resource, relationship.options[:serializer])
39
- node.children << ResourceIdentifierNode.new(serializer)
40
-
41
- node
42
- end
43
-
44
- # @param [Relationship] relationship
45
- # @param [Serializer] serializer
46
- # @return [CollectionDataNode]
47
- def self.build_collection_data_node(relationship, serializer)
48
- node = CollectionDataNode.new
49
- resources = relationship.evaluate(serializer)
50
-
51
- resources.each do |resource|
52
- serializer = Serializer.factory(resource, relationship.options[:serializer])
53
- node.children << ResourceIdentifierNode.new(serializer)
54
- end
55
-
56
- node
57
- end
58
-
59
- # @param [Relationship] relationship
60
- # @param [Serializer] serializer
61
- # @return [LinksNode]
62
- def self.build_links_node(relationship, serializer)
63
- links = relationship.links(serializer)
64
- LinksNodeBuilder.build(links)
65
- end
66
- end
67
- end
68
- end
@@ -1,19 +0,0 @@
1
- module Jei
2
- module Builders
3
- module RelationshipsNodeBuilder
4
- include Nodes
5
-
6
- # @param [Serializer] serializer
7
- # @return [RelationshipsNode]
8
- def self.build(relationships, serializer)
9
- node = RelationshipsNode.new
10
-
11
- relationships.each do |relationship|
12
- node.children << RelationshipNodeBuilder.build(relationship, serializer)
13
- end
14
-
15
- node
16
- end
17
- end
18
- end
19
- end
@@ -1,36 +0,0 @@
1
- module Jei
2
- module Builders
3
- module ResourceNodeBuilder
4
- include Nodes
5
-
6
- # @param [Serializer] serializer
7
- # @param [Array<Symbol>] fieldset
8
- # @return [ResourceNode]
9
- def self.build(serializer, fieldset = nil)
10
- node = ResourceNode.new
11
-
12
- node.children << ResourceIdentifierNode.new(serializer)
13
-
14
- attributes = serializer.attributes(fieldset).values
15
-
16
- if attributes.any?
17
- node.children << AttributesNodeBuilder.build(attributes, serializer)
18
- end
19
-
20
- relationships = serializer.relationships(fieldset).values
21
-
22
- if relationships.any?
23
- node.children << RelationshipsNodeBuilder.build(relationships, serializer)
24
- end
25
-
26
- links = serializer.links
27
-
28
- if links
29
- node.children << LinksNodeBuilder.build(links)
30
- end
31
-
32
- node
33
- end
34
- end
35
- end
36
- end
@@ -1,19 +0,0 @@
1
- module Jei
2
- module Nodes
3
- # @see http://jsonapi.org/format/1.0/#document-resource-object-attributes
4
- class AttributeNode < Node
5
- # @param [Serializer] serializer
6
- # @param [Attribute] attribute
7
- def initialize(serializer, attribute)
8
- super()
9
- @serializer = serializer
10
- @attribute = attribute
11
- end
12
-
13
- # @param [Hash<Symbol, Object>] context
14
- def visit(context)
15
- context[@attribute.name] = @attribute.evaluate(@serializer)
16
- end
17
- end
18
- end
19
- end
@@ -1,13 +0,0 @@
1
- module Jei
2
- module Nodes
3
- # @see http://jsonapi.org/format/1.0/#document-resource-object-attributes
4
- class AttributesNode < Node
5
- # @param [Hash<Symbol, Object>] context
6
- def visit(context)
7
- attributes = {}
8
- children.each { |child| child.visit(attributes) }
9
- context[:attributes] = attributes
10
- end
11
- end
12
- end
13
- end
@@ -1,15 +0,0 @@
1
- module Jei
2
- module Nodes
3
- # @see http://jsonapi.org/format/#document-top-level
4
- class CollectionDataNode < DataNode
5
- # @param [Hash<Symbol, Object>] context
6
- def visit(context)
7
- context[:data] = children.map do |child|
8
- data = {}
9
- child.visit(data)
10
- data
11
- end
12
- end
13
- end
14
- end
15
- end
@@ -1,18 +0,0 @@
1
- module Jei
2
- module Nodes
3
- # @see http://jsonapi.org/format/#document-top-level
4
- class DataNode < Node
5
- # @param [Hash<Symbol, Object>] context
6
- def visit(context)
7
- context[:data] =
8
- if children.empty?
9
- nil
10
- else
11
- data = {}
12
- children.each { |child| child.visit(data) }
13
- data
14
- end
15
- end
16
- end
17
- end
18
- end
@@ -1,11 +0,0 @@
1
- module Jei
2
- module Nodes
3
- # @see http://jsonapi.org/format/1.0/#document-top-level
4
- class DocumentNode < Node
5
- # @param [Hash<Symbol, Object>] context
6
- def visit(context)
7
- children.each { |child| child.visit(context) }
8
- end
9
- end
10
- end
11
- end
@@ -1,14 +0,0 @@
1
- module Jei
2
- module Nodes
3
- # @see http://jsonapi.org/format/1.0/#document-compound-documents
4
- class IncludedNode < Node
5
- def visit(context)
6
- context[:included] = children.map do |child|
7
- data = {}
8
- child.visit(data)
9
- data
10
- end
11
- end
12
- end
13
- end
14
- end
@@ -1,11 +0,0 @@
1
- module Jei
2
- module Nodes
3
- # @see http://jsonapi.org/format/1.0/#document-jsonapi-object
4
- class JSONAPINode < Node
5
- # @param [Hash<Symbol, Object>] context
6
- def visit(context)
7
- context[:jsonapi] = { version: Document::VERSION }
8
- end
9
- end
10
- end
11
- end
@@ -1,22 +0,0 @@
1
- module Jei
2
- module Nodes
3
- # @see http://jsonapi.org/format/1.0/#document-links
4
- class LinkNode < Node
5
- # @param [Link] link
6
- def initialize(link)
7
- super()
8
- @link = link
9
- end
10
-
11
- # @param [Hash<Symbol, Object>] context
12
- def visit(context)
13
- context[@link.name] =
14
- if @link.meta.any?
15
- { href: @link.href, meta: @link.meta }
16
- else
17
- @link.href
18
- end
19
- end
20
- end
21
- end
22
- end
@@ -1,13 +0,0 @@
1
- module Jei
2
- module Nodes
3
- # @see http://jsonapi.org/format/1.0/#document-links
4
- class LinksNode < Node
5
- # @param [Hash<Symbol, Object>] context
6
- def visit(context)
7
- data = {}
8
- children.each { |child| child.visit(data) }
9
- context[:links] = data
10
- end
11
- end
12
- end
13
- end
@@ -1,17 +0,0 @@
1
- module Jei
2
- module Nodes
3
- # @see http://jsonapi.org/format/1.0/#document-meta
4
- class MetaNode < Node
5
- # param [Hash<Symbol, Object>] meta
6
- def initialize(meta)
7
- super()
8
- @meta = meta
9
- end
10
-
11
- # @param [Hash<Symbol, Object>] context
12
- def visit(context)
13
- context[:meta] = @meta
14
- end
15
- end
16
- end
17
- end
@@ -1,19 +0,0 @@
1
- module Jei
2
- module Nodes
3
- # @abstract
4
- class Node
5
- # @return [Array<Node>]
6
- attr_reader :children
7
-
8
- def initialize
9
- @children = []
10
- end
11
-
12
- # @abstract
13
- # @param [Hash<Symbol, Object>] _context
14
- def visit(_context)
15
- raise NotImplementedError
16
- end
17
- end
18
- end
19
- end
@@ -1,19 +0,0 @@
1
- module Jei
2
- module Nodes
3
- # @see http://jsonapi.org/format/1.0/#document-resource-object-relationships
4
- class RelationshipNode < Node
5
- # @param [Relationship] relationship
6
- def initialize(relationship)
7
- super()
8
- @relationship = relationship
9
- end
10
-
11
- # @param [Hash<Symbol, Object>] context
12
- def visit(context)
13
- data = {}
14
- children.each { |child| child.visit(data) }
15
- context[@relationship.name] = data
16
- end
17
- end
18
- end
19
- end
@@ -1,13 +0,0 @@
1
- module Jei
2
- module Nodes
3
- # @see http://jsonapi.org/format/1.0/#document-resource-object-relationships
4
- class RelationshipsNode < Node
5
- # @param [Hash<Symbol, Object>] context
6
- def visit(context)
7
- relationships = {}
8
- children.each { |child| child.visit(relationships) }
9
- context[:relationships] = relationships
10
- end
11
- end
12
- end
13
- end
@@ -1,18 +0,0 @@
1
- module Jei
2
- module Nodes
3
- # http://jsonapi.org/format/1.0/#document-resource-identifier-objects
4
- class ResourceIdentifierNode < Node
5
- # param [Serializer] serializer
6
- def initialize(serializer)
7
- super()
8
- @serializer = serializer
9
- end
10
-
11
- # @param [Hash<Symbol, Object>] context
12
- def visit(context)
13
- context[:id] = @serializer.id
14
- context[:type] = @serializer.type
15
- end
16
- end
17
- end
18
- end
@@ -1,11 +0,0 @@
1
- module Jei
2
- module Nodes
3
- # http://jsonapi.org/format/1.0/#document-resource-objects
4
- class ResourceNode < Node
5
- # @param [Hash<Symbol, Object>] context
6
- def visit(context)
7
- children.each { |child| child.visit(context) }
8
- end
9
- end
10
- end
11
- end