jsonapi-resources 0.4.4 → 0.5.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.
@@ -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