jsonapi-resources 0.4.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,21 +7,24 @@ module JSONAPI
7
7
  # Example: ['comments','author','comments.tags','author.posts']
8
8
  # fields:
9
9
  # Purpose: determines which fields are serialized for a resource type. This encompasses both attributes and
10
- # association ids in the links section for a resource. Fields are global for a resource type.
10
+ # relationship ids in the links section for a resource. Fields are global for a resource type.
11
11
  # Example: { people: [:id, :email, :comments], posts: [:id, :title, :author], comments: [:id, :body, :post]}
12
12
  # key_formatter: KeyFormatter class to override the default configuration
13
13
  # base_url: a string to prepend to generated resource links
14
14
 
15
- def initialize(primary_resource_klass, options = {})
16
- @primary_resource_klass = primary_resource_klass
17
- @primary_class_name = @primary_resource_klass._type
15
+ attr_reader :url_generator
18
16
 
19
- @fields = options.fetch(:fields, {})
20
- @include = options.fetch(:include, [])
17
+ def initialize(primary_resource_klass, options = {})
18
+ @primary_class_name = primary_resource_klass._type
19
+ @fields = options.fetch(:fields, {})
20
+ @include = options.fetch(:include, [])
21
21
  @include_directives = options[:include_directives]
22
- @key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
23
- @route_formatter = options.fetch(:route_formatter, JSONAPI.configuration.route_formatter)
24
- @base_url = options.fetch(:base_url, '')
22
+ @key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
23
+ @url_generator = generate_link_builder(primary_resource_klass, options)
24
+ @always_include_to_one_linkage_data = options.fetch(:always_include_to_one_linkage_data,
25
+ JSONAPI.configuration.always_include_to_one_linkage_data)
26
+ @always_include_to_many_linkage_data = options.fetch(:always_include_to_many_linkage_data,
27
+ JSONAPI.configuration.always_include_to_many_linkage_data)
25
28
  end
26
29
 
27
30
  # Converts a single resource, or an array of resources to a hash, conforming to the JSONAPI structure
@@ -51,24 +54,24 @@ module JSONAPI
51
54
  primary_hash
52
55
  end
53
56
 
54
- def serialize_to_links_hash(source, requested_association)
55
- if requested_association.is_a?(JSONAPI::Association::HasOne)
56
- data = has_one_linkage(source, requested_association)
57
+ def serialize_to_links_hash(source, requested_relationship)
58
+ if requested_relationship.is_a?(JSONAPI::Relationship::ToOne)
59
+ data = to_one_linkage(source, requested_relationship)
57
60
  else
58
- data = has_many_linkage(source, requested_association)
61
+ data = to_many_linkage(source, requested_relationship)
59
62
  end
60
63
 
61
64
  {
62
65
  links: {
63
- self: self_link(source, requested_association),
64
- related: related_link(source, requested_association)
66
+ self: self_link(source, requested_relationship),
67
+ related: related_link(source, requested_relationship)
65
68
  },
66
69
  data: data
67
70
  }
68
71
  end
69
72
 
70
73
  def find_link(query_params)
71
- "#{@base_url}/#{formatted_module_path_from_klass(@primary_resource_klass)}#{@route_formatter.format(@primary_resource_klass._type.to_s)}?#{query_params.to_query}"
74
+ url_generator.query_link(query_params)
72
75
  end
73
76
 
74
77
  private
@@ -81,18 +84,18 @@ module JSONAPI
81
84
  if source.respond_to?(:to_ary)
82
85
  source.each do |resource|
83
86
  id = resource.id
84
- if already_serialized?(@primary_class_name, id)
87
+ if already_serialized?(resource.class._type, id)
85
88
  set_primary(@primary_class_name, id)
86
89
  end
87
90
 
88
- add_included_object(@primary_class_name, id, object_hash(resource, include_directives), true)
91
+ add_included_object(id, object_hash(resource, include_directives), true)
89
92
  end
90
93
  else
91
94
  return {} if source.nil?
92
95
 
93
96
  resource = source
94
97
  id = resource.id
95
- add_included_object(@primary_class_name, id, object_hash(source, include_directives), true)
98
+ add_included_object(id, object_hash(source, include_directives), true)
96
99
  end
97
100
  end
98
101
 
@@ -131,60 +134,60 @@ module JSONAPI
131
134
  fields.each_with_object({}) do |name, hash|
132
135
  format = source.class._attribute_options(name)[:format]
133
136
  unless name == :id
134
- hash[format_key(name)] = format_value(source.send(name), format)
137
+ hash[format_key(name)] = format_value(source.public_send(name), format)
135
138
  end
136
139
  end
137
140
  end
138
141
 
139
142
  def relationship_data(source, include_directives)
140
- associations = source.class._associations
143
+ relationships = source.class._relationships
141
144
  requested = requested_fields(source.class._type)
142
- fields = associations.keys
145
+ fields = relationships.keys
143
146
  fields = requested & fields unless requested.nil?
144
147
 
145
148
  field_set = Set.new(fields)
146
149
 
147
- included_associations = source.fetchable_fields & associations.keys
150
+ included_relationships = source.fetchable_fields & relationships.keys
148
151
 
149
152
  data = {}
150
153
 
151
- associations.each_with_object(data) do |(name, association), hash|
152
- if included_associations.include? name
154
+ relationships.each_with_object(data) do |(name, relationship), hash|
155
+ if included_relationships.include? name
153
156
  ia = include_directives[:include_related][name]
154
157
 
155
158
  include_linkage = ia && ia[:include]
156
159
  include_linked_children = ia && !ia[:include_related].empty?
157
160
 
158
161
  if field_set.include?(name)
159
- hash[format_key(name)] = link_object(source, association, include_linkage)
162
+ hash[format_key(name)] = link_object(source, relationship, include_linkage)
160
163
  end
161
164
 
162
- type = association.type
165
+ type = relationship.type
163
166
 
164
167
  # If the object has been serialized once it will be in the related objects list,
165
168
  # but it's possible all children won't have been captured. So we must still go
166
- # through the associations.
169
+ # through the relationships.
167
170
  if include_linkage || include_linked_children
168
- if association.is_a?(JSONAPI::Association::HasOne)
169
- resource = source.send(name)
171
+ if relationship.is_a?(JSONAPI::Relationship::ToOne)
172
+ resource = source.public_send(name)
170
173
  if resource
171
174
  id = resource.id
172
- type = association.type_for_source(source)
173
- associations_only = already_serialized?(type, id)
174
- if include_linkage && !associations_only
175
- add_included_object(type, id, object_hash(resource, ia))
176
- elsif include_linked_children || associations_only
175
+ type = relationship.type_for_source(source)
176
+ relationships_only = already_serialized?(type, id)
177
+ if include_linkage && !relationships_only
178
+ add_included_object(id, object_hash(resource, ia))
179
+ elsif include_linked_children || relationships_only
177
180
  relationship_data(resource, ia)
178
181
  end
179
182
  end
180
- elsif association.is_a?(JSONAPI::Association::HasMany)
181
- resources = source.send(name)
183
+ elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
184
+ resources = source.public_send(name)
182
185
  resources.each do |resource|
183
186
  id = resource.id
184
- associations_only = already_serialized?(type, id)
185
- if include_linkage && !associations_only
186
- add_included_object(type, id, object_hash(resource, ia))
187
- elsif include_linked_children || associations_only
187
+ relationships_only = already_serialized?(type, id)
188
+ if include_linkage && !relationships_only
189
+ add_included_object(id, object_hash(resource, ia))
190
+ elsif include_linked_children || relationships_only
188
191
  relationship_data(resource, ia)
189
192
  end
190
193
  end
@@ -196,46 +199,30 @@ module JSONAPI
196
199
 
197
200
  def relationship_links(source)
198
201
  links = {}
199
- links[:self] = self_href(source)
202
+ links[:self] = url_generator.self_link(source)
200
203
 
201
204
  links
202
205
  end
203
206
 
204
- def formatted_module_path(source)
205
- formatted_module_path_from_klass(source.class)
206
- end
207
-
208
- def formatted_module_path_from_klass(klass)
209
- klass.name =~ /::[^:]+\Z/ ? (@route_formatter.format($`).freeze.gsub('::', '/') + '/').downcase : ''
210
- end
211
-
212
- def self_href(source)
213
- "#{@base_url}/#{formatted_module_path(source)}#{@route_formatter.format(source.class._type.to_s)}/#{source.id}"
214
- end
215
-
216
207
  def already_serialized?(type, id)
217
208
  type = format_key(type)
218
209
  @included_objects.key?(type) && @included_objects[type].key?(id)
219
210
  end
220
211
 
221
- def format_route(route)
222
- @route_formatter.format(route.to_s)
223
- end
224
-
225
- def self_link(source, association)
226
- "#{self_href(source)}/relationships/#{format_route(association.name)}"
212
+ def self_link(source, relationship)
213
+ url_generator.relationships_self_link(source, relationship)
227
214
  end
228
215
 
229
- def related_link(source, association)
230
- "#{self_href(source)}/#{format_route(association.name)}"
216
+ def related_link(source, relationship)
217
+ url_generator.relationships_related_link(source, relationship)
231
218
  end
232
219
 
233
- def has_one_linkage(source, association)
220
+ def to_one_linkage(source, relationship)
234
221
  linkage = {}
235
- linkage_id = foreign_key_value(source, association)
222
+ linkage_id = foreign_key_value(source, relationship)
236
223
 
237
224
  if linkage_id
238
- linkage[:type] = format_key(association.type_for_source(source))
225
+ linkage[:type] = format_key(relationship.type_for_source(source))
239
226
  linkage[:id] = linkage_id
240
227
  else
241
228
  linkage = nil
@@ -243,9 +230,9 @@ module JSONAPI
243
230
  linkage
244
231
  end
245
232
 
246
- def has_many_linkage(source, association)
233
+ def to_many_linkage(source, relationship)
247
234
  linkage = []
248
- linkage_types_and_values = foreign_key_types_and_values(source, association)
235
+ linkage_types_and_values = foreign_key_types_and_values(source, relationship)
249
236
 
250
237
  linkage_types_and_values.each do |type, value|
251
238
  linkage.append({type: format_key(type), id: value})
@@ -253,48 +240,49 @@ module JSONAPI
253
240
  linkage
254
241
  end
255
242
 
256
- def link_object_has_one(source, association)
243
+ def link_object_to_one(source, relationship, include_linkage)
244
+ include_linkage = include_linkage | @always_include_to_one_linkage_data | relationship.always_include_linkage_data
257
245
  link_object_hash = {}
258
246
  link_object_hash[:links] = {}
259
- link_object_hash[:links][:self] = self_link(source, association)
260
- link_object_hash[:links][:related] = related_link(source, association)
261
- link_object_hash[:data] = has_one_linkage(source, association)
247
+ link_object_hash[:links][:self] = self_link(source, relationship)
248
+ link_object_hash[:links][:related] = related_link(source, relationship)
249
+ link_object_hash[:data] = to_one_linkage(source, relationship) if include_linkage
262
250
  link_object_hash
263
251
  end
264
252
 
265
- def link_object_has_many(source, association, include_linkage)
253
+ def link_object_to_many(source, relationship, include_linkage)
266
254
  link_object_hash = {}
267
255
  link_object_hash[:links] = {}
268
- link_object_hash[:links][:self] = self_link(source, association)
269
- link_object_hash[:links][:related] = related_link(source, association)
270
- link_object_hash[:data] = has_many_linkage(source, association) if include_linkage
256
+ link_object_hash[:links][:self] = self_link(source, relationship)
257
+ link_object_hash[:links][:related] = related_link(source, relationship)
258
+ link_object_hash[:data] = to_many_linkage(source, relationship) if include_linkage
271
259
  link_object_hash
272
260
  end
273
261
 
274
- def link_object(source, association, include_linkage = false)
275
- if association.is_a?(JSONAPI::Association::HasOne)
276
- link_object_has_one(source, association)
277
- elsif association.is_a?(JSONAPI::Association::HasMany)
278
- link_object_has_many(source, association, include_linkage)
262
+ def link_object(source, relationship, include_linkage = false)
263
+ if relationship.is_a?(JSONAPI::Relationship::ToOne)
264
+ link_object_to_one(source, relationship, include_linkage)
265
+ elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
266
+ link_object_to_many(source, relationship, include_linkage)
279
267
  end
280
268
  end
281
269
 
282
- # Extracts the foreign key value for a has_one association.
283
- def foreign_key_value(source, association)
284
- foreign_key = association.foreign_key
285
- value = source.send(foreign_key)
270
+ # Extracts the foreign key value for a to_one relationship.
271
+ def foreign_key_value(source, relationship)
272
+ foreign_key = relationship.foreign_key
273
+ value = source.public_send(foreign_key)
286
274
  IdValueFormatter.format(value)
287
275
  end
288
276
 
289
- def foreign_key_types_and_values(source, association)
290
- if association.is_a?(JSONAPI::Association::HasMany)
291
- if association.polymorphic?
292
- source.model.send(association.name).pluck(:type, :id).map do |type, id|
277
+ def foreign_key_types_and_values(source, relationship)
278
+ if relationship.is_a?(JSONAPI::Relationship::ToMany)
279
+ if relationship.polymorphic?
280
+ source.model.public_send(relationship.name).pluck(:type, :id).map do |type, id|
293
281
  [type.pluralize, IdValueFormatter.format(id)]
294
282
  end
295
283
  else
296
- source.send(association.foreign_key).map do |value|
297
- [association.type, IdValueFormatter.format(value)]
284
+ source.public_send(relationship.foreign_key).map do |value|
285
+ [relationship.type, IdValueFormatter.format(value)]
298
286
  end
299
287
  end
300
288
  end
@@ -307,12 +295,13 @@ module JSONAPI
307
295
  end
308
296
 
309
297
  # Collects the hashes for all objects processed by the serializer
310
- def add_included_object(type, id, object_hash, primary = false)
311
- type = format_key(type)
298
+ def add_included_object(id, object_hash, primary = false)
299
+ type = object_hash['type']
312
300
 
313
301
  @included_objects[type] = {} unless @included_objects.key?(type)
314
302
 
315
303
  if already_serialized?(type, id)
304
+ @included_objects[type][id][:object_hash].merge!(object_hash)
316
305
  set_primary(type, id) if primary
317
306
  else
318
307
  @included_objects[type].store(id, primary: primary, object_hash: object_hash)
@@ -327,5 +316,13 @@ module JSONAPI
327
316
  value_formatter = JSONAPI::ValueFormatter.value_formatter_for(format)
328
317
  value_formatter.format(value)
329
318
  end
319
+
320
+ def generate_link_builder(primary_resource_klass, options)
321
+ LinkBuilder.new(
322
+ base_url: options.fetch(:base_url, ''),
323
+ route_formatter: options.fetch(:route_formatter, JSONAPI.configuration.route_formatter),
324
+ primary_resource_klass: primary_resource_klass,
325
+ )
326
+ end
330
327
  end
331
328
  end
@@ -1,5 +1,5 @@
1
1
  module JSONAPI
2
2
  module Resources
3
- VERSION = '0.4.4'
3
+ VERSION = '0.5.0'
4
4
  end
5
5
  end
@@ -102,7 +102,7 @@ module JSONAPI
102
102
  serializer.serialize_to_hash(result.resources)
103
103
  when JSONAPI::LinksObjectOperationResult
104
104
  serializer.serialize_to_links_hash(result.parent_resource,
105
- result.association)
105
+ result.relationship)
106
106
  when JSONAPI::OperationResult
107
107
  {}
108
108
  end
@@ -45,13 +45,13 @@ module ActionDispatch
45
45
 
46
46
  def jsonapi_relationships(options = {})
47
47
  res = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(@resource_type))
48
- res._associations.each do |association_name, association|
49
- if association.is_a?(JSONAPI::Association::HasMany)
50
- jsonapi_links(association_name, options)
51
- jsonapi_related_resources(association_name, options)
48
+ res._relationships.each do |relationship_name, relationship|
49
+ if relationship.is_a?(JSONAPI::Relationship::ToMany)
50
+ jsonapi_links(relationship_name, options)
51
+ jsonapi_related_resources(relationship_name, options)
52
52
  else
53
- jsonapi_link(association_name, options)
54
- jsonapi_related_resource(association_name, options)
53
+ jsonapi_link(relationship_name, options)
54
+ jsonapi_related_resource(relationship_name, options)
55
55
  end
56
56
  end
57
57
  end
@@ -100,7 +100,7 @@ module ActionDispatch
100
100
 
101
101
  def jsonapi_link(*links)
102
102
  link_type = links.first
103
- formatted_association_name = format_route(link_type)
103
+ formatted_relationship_name = format_route(link_type)
104
104
  options = links.extract_options!.dup
105
105
 
106
106
  res = JSONAPI::Resource.resource_for(resource_type_with_module_prefix)
@@ -109,24 +109,24 @@ module ActionDispatch
109
109
  methods = links_methods(options)
110
110
 
111
111
  if methods.include?(:show)
112
- match "relationships/#{formatted_association_name}", controller: options[:controller],
113
- action: 'show_association', association: link_type.to_s, via: [:get]
112
+ match "relationships/#{formatted_relationship_name}", controller: options[:controller],
113
+ action: 'show_relationship', relationship: link_type.to_s, via: [:get]
114
114
  end
115
115
 
116
116
  if methods.include?(:update)
117
- match "relationships/#{formatted_association_name}", controller: options[:controller],
118
- action: 'update_association', association: link_type.to_s, via: [:put, :patch]
117
+ match "relationships/#{formatted_relationship_name}", controller: options[:controller],
118
+ action: 'update_relationship', relationship: link_type.to_s, via: [:put, :patch]
119
119
  end
120
120
 
121
121
  if methods.include?(:destroy)
122
- match "relationships/#{formatted_association_name}", controller: options[:controller],
123
- action: 'destroy_association', association: link_type.to_s, via: [:delete]
122
+ match "relationships/#{formatted_relationship_name}", controller: options[:controller],
123
+ action: 'destroy_relationship', relationship: link_type.to_s, via: [:delete]
124
124
  end
125
125
  end
126
126
 
127
127
  def jsonapi_links(*links)
128
128
  link_type = links.first
129
- formatted_association_name = format_route(link_type)
129
+ formatted_relationship_name = format_route(link_type)
130
130
  options = links.extract_options!.dup
131
131
 
132
132
  res = JSONAPI::Resource.resource_for(resource_type_with_module_prefix)
@@ -135,61 +135,61 @@ module ActionDispatch
135
135
  methods = links_methods(options)
136
136
 
137
137
  if methods.include?(:show)
138
- match "relationships/#{formatted_association_name}", controller: options[:controller],
139
- action: 'show_association', association: link_type.to_s, via: [:get]
138
+ match "relationships/#{formatted_relationship_name}", controller: options[:controller],
139
+ action: 'show_relationship', relationship: link_type.to_s, via: [:get]
140
140
  end
141
141
 
142
142
  if methods.include?(:create)
143
- match "relationships/#{formatted_association_name}", controller: options[:controller],
144
- action: 'create_association', association: link_type.to_s, via: [:post]
143
+ match "relationships/#{formatted_relationship_name}", controller: options[:controller],
144
+ action: 'create_relationship', relationship: link_type.to_s, via: [:post]
145
145
  end
146
146
 
147
147
  if methods.include?(:update)
148
- match "relationships/#{formatted_association_name}", controller: options[:controller],
149
- action: 'update_association', association: link_type.to_s, via: [:put, :patch]
148
+ match "relationships/#{formatted_relationship_name}", controller: options[:controller],
149
+ action: 'update_relationship', relationship: link_type.to_s, via: [:put, :patch]
150
150
  end
151
151
 
152
152
  if methods.include?(:destroy)
153
- match "relationships/#{formatted_association_name}/:keys", controller: options[:controller],
154
- action: 'destroy_association', association: link_type.to_s, via: [:delete]
153
+ match "relationships/#{formatted_relationship_name}/:keys", controller: options[:controller],
154
+ action: 'destroy_relationship', relationship: link_type.to_s, via: [:delete]
155
155
  end
156
156
  end
157
157
 
158
- def jsonapi_related_resource(*association)
158
+ def jsonapi_related_resource(*relationship)
159
159
  source = JSONAPI::Resource.resource_for(resource_type_with_module_prefix)
160
- options = association.extract_options!.dup
160
+ options = relationship.extract_options!.dup
161
161
 
162
- association_name = association.first
163
- association = source._associations[association_name]
162
+ relationship_name = relationship.first
163
+ relationship = source._relationships[relationship_name]
164
164
 
165
- formatted_association_name = format_route(association.name)
165
+ formatted_relationship_name = format_route(relationship.name)
166
166
 
167
- if association.polymorphic?
168
- options[:controller] ||= association.class_name.underscore.pluralize
167
+ if relationship.polymorphic?
168
+ options[:controller] ||= relationship.class_name.underscore.pluralize
169
169
  else
170
- related_resource = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(association.class_name.underscore.pluralize))
170
+ related_resource = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(relationship.class_name.underscore.pluralize))
171
171
  options[:controller] ||= related_resource._type.to_s
172
172
  end
173
173
 
174
- match "#{formatted_association_name}", controller: options[:controller],
175
- association: association.name, source: resource_type_with_module_prefix(source._type),
176
- action: 'get_related_resource', via: [:get]
174
+ match "#{formatted_relationship_name}", controller: options[:controller],
175
+ relationship: relationship.name, source: resource_type_with_module_prefix(source._type),
176
+ action: 'get_related_resource', via: [:get]
177
177
  end
178
178
 
179
- def jsonapi_related_resources(*association)
179
+ def jsonapi_related_resources(*relationship)
180
180
  source = JSONAPI::Resource.resource_for(resource_type_with_module_prefix)
181
- options = association.extract_options!.dup
181
+ options = relationship.extract_options!.dup
182
182
 
183
- association_name = association.first
184
- association = source._associations[association_name]
183
+ relationship_name = relationship.first
184
+ relationship = source._relationships[relationship_name]
185
185
 
186
- formatted_association_name = format_route(association.name)
187
- related_resource = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(association.class_name.underscore))
186
+ formatted_relationship_name = format_route(relationship.name)
187
+ related_resource = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(relationship.class_name.underscore))
188
188
  options[:controller] ||= related_resource._type.to_s
189
189
 
190
- match "#{formatted_association_name}", controller: options[:controller],
191
- association: association.name, source: resource_type_with_module_prefix(source._type),
192
- action: 'get_related_resources', via: [:get]
190
+ match "#{formatted_relationship_name}", controller: options[:controller],
191
+ relationship: relationship.name, source: resource_type_with_module_prefix(source._type),
192
+ action: 'get_related_resources', via: [:get]
193
193
  end
194
194
 
195
195
  private