sanger-jsonapi-resources 0.1.1 → 0.2.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +35 -12
  4. data/lib/bug_report_templates/rails_5_latest.rb +125 -0
  5. data/lib/bug_report_templates/rails_5_master.rb +140 -0
  6. data/lib/jsonapi/active_relation/adapters/join_left_active_record_adapter.rb +26 -0
  7. data/lib/jsonapi/active_relation/join_manager.rb +297 -0
  8. data/lib/jsonapi/active_relation_resource.rb +898 -0
  9. data/lib/jsonapi/acts_as_resource_controller.rb +130 -113
  10. data/lib/jsonapi/basic_resource.rb +1164 -0
  11. data/lib/jsonapi/cached_response_fragment.rb +129 -0
  12. data/lib/jsonapi/callbacks.rb +2 -0
  13. data/lib/jsonapi/compatibility_helper.rb +29 -0
  14. data/lib/jsonapi/compiled_json.rb +13 -1
  15. data/lib/jsonapi/configuration.rb +88 -21
  16. data/lib/jsonapi/error.rb +29 -0
  17. data/lib/jsonapi/error_codes.rb +4 -0
  18. data/lib/jsonapi/exceptions.rb +82 -50
  19. data/lib/jsonapi/formatter.rb +5 -3
  20. data/lib/jsonapi/include_directives.rb +22 -67
  21. data/lib/jsonapi/link_builder.rb +76 -80
  22. data/lib/jsonapi/mime_types.rb +6 -10
  23. data/lib/jsonapi/naive_cache.rb +2 -0
  24. data/lib/jsonapi/operation.rb +18 -5
  25. data/lib/jsonapi/operation_result.rb +76 -16
  26. data/lib/jsonapi/paginator.rb +2 -0
  27. data/lib/jsonapi/path.rb +45 -0
  28. data/lib/jsonapi/path_segment.rb +78 -0
  29. data/lib/jsonapi/processor.rb +193 -115
  30. data/lib/jsonapi/relationship.rb +145 -14
  31. data/lib/jsonapi/request.rb +734 -0
  32. data/lib/jsonapi/resource.rb +3 -1251
  33. data/lib/jsonapi/resource_controller.rb +2 -0
  34. data/lib/jsonapi/resource_controller_metal.rb +7 -1
  35. data/lib/jsonapi/resource_fragment.rb +56 -0
  36. data/lib/jsonapi/resource_identity.rb +44 -0
  37. data/lib/jsonapi/resource_serializer.rb +158 -284
  38. data/lib/jsonapi/resource_set.rb +196 -0
  39. data/lib/jsonapi/resource_tree.rb +236 -0
  40. data/lib/jsonapi/resources/railtie.rb +9 -0
  41. data/lib/jsonapi/resources/version.rb +1 -1
  42. data/lib/jsonapi/response_document.rb +107 -83
  43. data/lib/jsonapi/routing_ext.rb +50 -26
  44. data/lib/jsonapi-resources.rb +23 -5
  45. data/lib/tasks/check_upgrade.rake +52 -0
  46. metadata +43 -31
  47. data/lib/jsonapi/cached_resource_fragment.rb +0 -127
  48. data/lib/jsonapi/operation_dispatcher.rb +0 -88
  49. data/lib/jsonapi/operation_results.rb +0 -35
  50. data/lib/jsonapi/relationship_builder.rb +0 -167
  51. data/lib/jsonapi/request_parser.rb +0 -678
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  class Processor
3
5
  include Callbacks
@@ -17,30 +19,6 @@ module JSONAPI
17
19
  :remove_to_one_relationship,
18
20
  :operation
19
21
 
20
- class << self
21
- def processor_instance_for(resource_klass, operation_type, params)
22
- _processor_from_resource_type(resource_klass).new(resource_klass, operation_type, params)
23
- end
24
-
25
- def _processor_from_resource_type(resource_klass)
26
- processor = resource_klass.name.gsub(/Resource$/,'Processor').safe_constantize
27
- if processor.nil?
28
- processor = JSONAPI.configuration.default_processor_klass
29
- end
30
-
31
- return processor
32
- end
33
-
34
- def transactional_operation_type?(operation_type)
35
- case operation_type
36
- when :find, :show, :show_related_resource, :show_related_resources
37
- return false
38
- else
39
- return true
40
- end
41
- end
42
- end
43
-
44
22
  attr_reader :resource_klass, :operation_type, :params, :context, :result, :result_options
45
23
 
46
24
  def initialize(resource_klass, operation_type, params)
@@ -66,93 +44,124 @@ module JSONAPI
66
44
  def find
67
45
  filters = params[:filters]
68
46
  include_directives = params[:include_directives]
69
- sort_criteria = params.fetch(:sort_criteria, [])
47
+ sort_criteria = params[:sort_criteria]
70
48
  paginator = params[:paginator]
71
49
  fields = params[:fields]
50
+ serializer = params[:serializer]
72
51
 
73
52
  verified_filters = resource_klass.verify_filters(filters, context)
74
- find_options = {
53
+
54
+ options = {
75
55
  context: context,
76
- include_directives: include_directives,
77
56
  sort_criteria: sort_criteria,
78
57
  paginator: paginator,
79
- fields: fields
58
+ fields: fields,
59
+ filters: verified_filters,
60
+ include_directives: include_directives
80
61
  }
81
62
 
82
- resource_records = if params[:cache_serializer]
83
- resource_klass.find_serialized_with_caching(verified_filters,
84
- params[:cache_serializer],
85
- find_options)
86
- else
87
- resource_klass.find(verified_filters, find_options)
88
- end
63
+ resource_set = find_resource_set(include_directives, options)
64
+
65
+ resource_set.populate!(serializer, context, options)
89
66
 
90
- page_options = {}
91
- if (JSONAPI.configuration.top_level_meta_include_record_count ||
92
- (paginator && paginator.class.requires_record_count))
93
- page_options[:record_count] = resource_klass.find_count(verified_filters,
94
- context: context,
95
- include_directives: include_directives)
67
+ page_options = result_options
68
+ if (JSONAPI.configuration.top_level_meta_include_record_count || (paginator && paginator.class.requires_record_count))
69
+ page_options[:record_count] = resource_klass.count(verified_filters,
70
+ context: context,
71
+ include_directives: include_directives)
96
72
  end
97
73
 
98
- if (JSONAPI.configuration.top_level_meta_include_page_count && page_options[:record_count])
74
+ if (JSONAPI.configuration.top_level_meta_include_page_count && paginator && page_options[:record_count])
99
75
  page_options[:page_count] = paginator ? paginator.calculate_page_count(page_options[:record_count]) : 1
100
76
  end
101
77
 
102
78
  if JSONAPI.configuration.top_level_links_include_pagination && paginator
103
- page_options[:pagination_params] = paginator.links_page_params(page_options)
79
+ page_options[:pagination_params] = paginator.links_page_params(page_options.merge(fetched_resources: resource_set))
104
80
  end
105
81
 
106
- return JSONAPI::ResourcesOperationResult.new(:ok, resource_records, page_options)
82
+ JSONAPI::ResourcesSetOperationResult.new(:ok, resource_set, page_options)
107
83
  end
108
84
 
109
85
  def show
110
86
  include_directives = params[:include_directives]
111
87
  fields = params[:fields]
112
88
  id = params[:id]
89
+ serializer = params[:serializer]
113
90
 
114
91
  key = resource_klass.verify_key(id, context)
115
92
 
116
- find_options = {
93
+ options = {
117
94
  context: context,
118
- include_directives: include_directives,
119
- fields: fields
95
+ fields: fields,
96
+ filters: { resource_klass._primary_key => key },
97
+ include_directives: include_directives
120
98
  }
121
99
 
122
- resource_record = if params[:cache_serializer]
123
- resource_klass.find_by_key_serialized_with_caching(key,
124
- params[:cache_serializer],
125
- find_options)
126
- else
127
- resource_klass.find_by_key(key, find_options)
128
- end
100
+ resource_set = find_resource_set(include_directives, options)
129
101
 
130
- return JSONAPI::ResourceOperationResult.new(:ok, resource_record)
102
+ fail JSONAPI::Exceptions::RecordNotFound.new(id) if resource_set.resource_klasses.empty?
103
+ resource_set.populate!(serializer, context, options)
104
+
105
+ JSONAPI::ResourceSetOperationResult.new(:ok, resource_set, result_options)
131
106
  end
132
107
 
133
108
  def show_relationship
134
109
  parent_key = params[:parent_key]
135
110
  relationship_type = params[:relationship_type].to_sym
111
+ paginator = params[:paginator]
112
+ sort_criteria = params[:sort_criteria]
113
+ include_directives = params[:include_directives]
114
+ fields = params[:fields]
136
115
 
137
116
  parent_resource = resource_klass.find_by_key(parent_key, context: context)
138
117
 
139
- return JSONAPI::LinksObjectOperationResult.new(:ok,
140
- parent_resource,
141
- resource_klass._relationship(relationship_type))
118
+ options = {
119
+ context: context,
120
+ sort_criteria: sort_criteria,
121
+ paginator: paginator,
122
+ fields: fields,
123
+ include_directives: include_directives
124
+ }
125
+
126
+ resource_tree = find_related_resource_tree(
127
+ parent_resource,
128
+ relationship_type,
129
+ options,
130
+ nil
131
+ )
132
+
133
+ JSONAPI::RelationshipOperationResult.new(:ok,
134
+ parent_resource,
135
+ resource_klass._relationship(relationship_type),
136
+ resource_tree.fragments.keys,
137
+ result_options)
142
138
  end
143
139
 
144
140
  def show_related_resource
141
+ include_directives = params[:include_directives]
145
142
  source_klass = params[:source_klass]
146
143
  source_id = params[:source_id]
147
- relationship_type = params[:relationship_type].to_sym
144
+ relationship_type = params[:relationship_type]
145
+ serializer = params[:serializer]
148
146
  fields = params[:fields]
149
147
 
150
- # TODO Should fetch related_resource from cache if caching enabled
148
+ options = {
149
+ context: context,
150
+ fields: fields,
151
+ filters: {},
152
+ include_directives: include_directives
153
+ }
154
+
151
155
  source_resource = source_klass.find_by_key(source_id, context: context, fields: fields)
152
156
 
153
- related_resource = source_resource.public_send(relationship_type)
157
+ resource_set = find_related_resource_set(source_resource,
158
+ relationship_type,
159
+ include_directives,
160
+ options)
161
+
162
+ resource_set.populate!(serializer, context, options)
154
163
 
155
- return JSONAPI::ResourceOperationResult.new(:ok, related_resource)
164
+ JSONAPI::ResourceSetOperationResult.new(:ok, resource_set, result_options)
156
165
  end
157
166
 
158
167
  def show_related_resources
@@ -164,11 +173,12 @@ module JSONAPI
164
173
  paginator = params[:paginator]
165
174
  fields = params[:fields]
166
175
  include_directives = params[:include_directives]
176
+ serializer = params[:serializer]
167
177
 
168
- source_resource ||= source_klass.find_by_key(source_id, context: context, fields: fields)
178
+ verified_filters = resource_klass.verify_filters(filters, context)
169
179
 
170
- rel_opts = {
171
- filters: filters,
180
+ options = {
181
+ filters: verified_filters,
172
182
  sort_criteria: sort_criteria,
173
183
  paginator: paginator,
174
184
  fields: fields,
@@ -176,62 +186,66 @@ module JSONAPI
176
186
  include_directives: include_directives
177
187
  }
178
188
 
179
- related_resources = nil
180
- if params[:cache_serializer]
181
- # TODO Could also avoid instantiating source_resource as actual Resource by
182
- # allowing LinkBuilder to accept CachedResourceFragment as source in
183
- # relationships_related_link
184
- scope = source_resource.public_send(:"records_for_#{relationship_type}", rel_opts)
185
- relationship = source_klass._relationship(relationship_type)
186
- related_resources = relationship.resource_klass.find_serialized_with_caching(
187
- scope,
188
- params[:cache_serializer],
189
- rel_opts
190
- )
191
- else
192
- related_resources = source_resource.public_send(relationship_type, rel_opts)
193
- end
189
+ source_resource = source_klass.find_by_key(source_id, context: context, fields: fields)
194
190
 
191
+ resource_set = find_related_resource_set(source_resource,
192
+ relationship_type,
193
+ include_directives,
194
+ options)
195
+
196
+ resource_set.populate!(serializer, context, options)
197
+
198
+ opts = result_options
195
199
  if ((JSONAPI.configuration.top_level_meta_include_record_count) ||
196
- (paginator && paginator.class.requires_record_count) ||
197
- (JSONAPI.configuration.top_level_meta_include_page_count))
198
- related_resource_records = source_resource.public_send("records_for_" + relationship_type)
199
- records = resource_klass.filter_records(filters, {},
200
- related_resource_records)
200
+ (paginator && paginator.class.requires_record_count) ||
201
+ (JSONAPI.configuration.top_level_meta_include_page_count))
201
202
 
202
- record_count = resource_klass.count_records(records)
203
+ opts[:record_count] = source_resource.class.count_related(
204
+ source_resource,
205
+ relationship_type,
206
+ options)
203
207
  end
204
208
 
205
- if (JSONAPI.configuration.top_level_meta_include_page_count && record_count)
206
- page_count = paginator.calculate_page_count(record_count)
209
+ if (JSONAPI.configuration.top_level_meta_include_page_count && opts[:record_count])
210
+ opts[:page_count] = paginator.calculate_page_count(opts[:record_count])
207
211
  end
208
212
 
209
- pagination_params = if paginator && JSONAPI.configuration.top_level_links_include_pagination
210
- page_options = {}
211
- page_options[:record_count] = record_count if paginator.class.requires_record_count
212
- paginator.links_page_params(page_options)
213
- else
214
- {}
215
- end
216
-
217
- opts = {}
218
- opts.merge!(pagination_params: pagination_params) if JSONAPI.configuration.top_level_links_include_pagination
219
- opts.merge!(record_count: record_count) if JSONAPI.configuration.top_level_meta_include_record_count
220
- opts.merge!(page_count: page_count) if JSONAPI.configuration.top_level_meta_include_page_count
221
-
222
- return JSONAPI::RelatedResourcesOperationResult.new(:ok,
223
- source_resource,
224
- relationship_type,
225
- related_resources,
226
- opts)
213
+ opts[:pagination_params] = if paginator && JSONAPI.configuration.top_level_links_include_pagination
214
+ page_options = {}
215
+ page_options[:record_count] = opts[:record_count] if paginator.class.requires_record_count
216
+ paginator.links_page_params(page_options.merge(fetched_resources: resource_set))
217
+ else
218
+ {}
219
+ end
220
+
221
+ JSONAPI::RelatedResourcesSetOperationResult.new(:ok,
222
+ source_resource,
223
+ relationship_type,
224
+ resource_set,
225
+ opts)
227
226
  end
228
227
 
229
228
  def create_resource
229
+ include_directives = params[:include_directives]
230
+ fields = params[:fields]
231
+ serializer = params[:serializer]
232
+
230
233
  data = params[:data]
231
234
  resource = resource_klass.create(context)
232
235
  result = resource.replace_fields(data)
233
236
 
234
- return JSONAPI::ResourceOperationResult.new((result == :completed ? :created : :accepted), resource)
237
+ options = {
238
+ context: context,
239
+ fields: fields,
240
+ filters: { resource_klass._primary_key => resource.id },
241
+ include_directives: include_directives
242
+ }
243
+
244
+ resource_set = find_resource_set(include_directives, options)
245
+
246
+ resource_set.populate!(serializer, context, options)
247
+
248
+ JSONAPI::ResourceSetOperationResult.new((result == :completed ? :created : :accepted), resource_set, result_options)
235
249
  end
236
250
 
237
251
  def remove_resource
@@ -240,17 +254,33 @@ module JSONAPI
240
254
  resource = resource_klass.find_by_key(resource_id, context: context)
241
255
  result = resource.remove
242
256
 
243
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
257
+ JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
244
258
  end
245
259
 
246
260
  def replace_fields
247
261
  resource_id = params[:resource_id]
262
+ include_directives = params[:include_directives]
263
+ fields = params[:fields]
264
+ serializer = params[:serializer]
265
+
248
266
  data = params[:data]
249
267
 
250
268
  resource = resource_klass.find_by_key(resource_id, context: context)
269
+
251
270
  result = resource.replace_fields(data)
252
271
 
253
- return JSONAPI::ResourceOperationResult.new(result == :completed ? :ok : :accepted, resource)
272
+ options = {
273
+ context: context,
274
+ fields: fields,
275
+ filters: { resource_klass._primary_key => resource.id },
276
+ include_directives: include_directives
277
+ }
278
+
279
+ resource_set = find_resource_set(include_directives, options)
280
+
281
+ resource_set.populate!(serializer, context, options)
282
+
283
+ JSONAPI::ResourceSetOperationResult.new((result == :completed ? :ok : :accepted), resource_set, result_options)
254
284
  end
255
285
 
256
286
  def replace_to_one_relationship
@@ -261,7 +291,7 @@ module JSONAPI
261
291
  resource = resource_klass.find_by_key(resource_id, context: context)
262
292
  result = resource.replace_to_one_link(relationship_type, key_value)
263
293
 
264
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
294
+ JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
265
295
  end
266
296
 
267
297
  def replace_polymorphic_to_one_relationship
@@ -273,7 +303,7 @@ module JSONAPI
273
303
  resource = resource_klass.find_by_key(resource_id, context: context)
274
304
  result = resource.replace_polymorphic_to_one_link(relationship_type, key_value, key_type)
275
305
 
276
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
306
+ JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
277
307
  end
278
308
 
279
309
  def create_to_many_relationships
@@ -284,7 +314,7 @@ module JSONAPI
284
314
  resource = resource_klass.find_by_key(resource_id, context: context)
285
315
  result = resource.create_to_many_links(relationship_type, data)
286
316
 
287
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
317
+ JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
288
318
  end
289
319
 
290
320
  def replace_to_many_relationships
@@ -295,7 +325,7 @@ module JSONAPI
295
325
  resource = resource_klass.find_by_key(resource_id, context: context)
296
326
  result = resource.replace_to_many_links(relationship_type, data)
297
327
 
298
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
328
+ JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
299
329
  end
300
330
 
301
331
  def remove_to_many_relationships
@@ -312,7 +342,7 @@ module JSONAPI
312
342
  complete = false
313
343
  end
314
344
  end
315
- return JSONAPI::OperationResult.new(complete ? :no_content : :accepted)
345
+ JSONAPI::OperationResult.new(complete ? :no_content : :accepted, result_options)
316
346
  end
317
347
 
318
348
  def remove_to_one_relationship
@@ -322,7 +352,55 @@ module JSONAPI
322
352
  resource = resource_klass.find_by_key(resource_id, context: context)
323
353
  result = resource.remove_to_one_link(relationship_type)
324
354
 
325
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
355
+ JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
356
+ end
357
+
358
+ def result_options
359
+ options = {}
360
+ options[:warnings] = params[:warnings] if params[:warnings]
361
+ options
362
+ end
363
+
364
+ def find_resource_set(include_directives, options)
365
+ include_related = include_directives[:include_related] if include_directives
366
+
367
+ resource_tree = find_resource_tree(options, include_related)
368
+
369
+ JSONAPI::ResourceSet.new(resource_tree)
370
+ end
371
+
372
+ def find_related_resource_set(resource, relationship_name, include_directives, options)
373
+ include_related = include_directives[:include_related] if include_directives
374
+
375
+ resource_tree = find_resource_tree_from_relationship(resource, relationship_name, options, include_related)
376
+
377
+ JSONAPI::ResourceSet.new(resource_tree)
378
+ end
379
+
380
+ def find_resource_tree(options, include_related)
381
+ options[:cache] = resource_klass.caching?
382
+
383
+ fragments = resource_klass.find_fragments(options[:filters], options)
384
+ PrimaryResourceTree.new(fragments: fragments, include_related: include_related, options: options)
385
+ end
386
+
387
+ def find_related_resource_tree(parent_resource, relationship_name, options, include_related)
388
+ options = options.except(:include_directives)
389
+ options[:cache] = resource_klass.caching?
390
+
391
+ fragments = resource_klass.find_included_fragments([parent_resource], relationship_name, options)
392
+ PrimaryResourceTree.new(fragments: fragments, include_related: include_related, options: options)
393
+ end
394
+
395
+ def find_resource_tree_from_relationship(resource, relationship_name, options, include_related)
396
+ relationship = resource.class._relationship(relationship_name)
397
+
398
+ options = options.except(:include_directives)
399
+ options[:cache] = relationship.resource_klass.caching?
400
+
401
+ fragments = resource.class.find_related_fragments([resource], relationship_name, options)
402
+
403
+ PrimaryResourceTree.new(fragments: fragments, include_related: include_related, options: options)
326
404
  end
327
405
  end
328
406
  end
@@ -1,8 +1,15 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'compatibility_helper'
1
3
  module JSONAPI
2
4
  class Relationship
3
5
  attr_reader :acts_as_set, :foreign_key, :options, :name,
4
- :class_name, :polymorphic, :always_include_linkage_data,
5
- :parent_resource, :eager_load_on_include
6
+ :class_name, :polymorphic, :always_include_optional_linkage_data,
7
+ :parent_resource, :eager_load_on_include, :custom_methods,
8
+ :inverse_relationship, :allow_include
9
+
10
+ attr_writer :allow_include
11
+
12
+ attr_accessor :_routed, :_warned_missing_route
6
13
 
7
14
  def initialize(name, options = {})
8
15
  @name = name.to_s
@@ -12,22 +19,66 @@ module JSONAPI
12
19
  @parent_resource = options[:parent_resource]
13
20
  @relation_name = options.fetch(:relation_name, @name)
14
21
  @polymorphic = options.fetch(:polymorphic, false) == true
15
- @always_include_linkage_data = options.fetch(:always_include_linkage_data, false) == true
22
+ @polymorphic_types = options[:polymorphic_types]
23
+ if options[:polymorphic_relations]
24
+ JSONAPI::CompatibilityHelper.deprecation_warn('Use polymorphic_types instead of polymorphic_relations')
25
+ @polymorphic_types ||= options[:polymorphic_relations]
26
+ end
27
+
28
+ @always_include_optional_linkage_data = options.fetch(:always_include_optional_linkage_data, false) == true
16
29
  @eager_load_on_include = options.fetch(:eager_load_on_include, true) == true
30
+ @allow_include = options[:allow_include]
31
+ @class_name = nil
32
+ @inverse_relationship = nil
33
+
34
+ @_routed = false
35
+ @_warned_missing_route = false
36
+
37
+ exclude_links(options.fetch(:exclude_links, JSONAPI.configuration.default_exclude_links))
38
+
39
+ # Custom methods are reserved for future use
40
+ @custom_methods = options.fetch(:custom_methods, {})
17
41
  end
18
42
 
19
43
  alias_method :polymorphic?, :polymorphic
44
+ alias_method :parent_resource_klass, :parent_resource
20
45
 
21
46
  def primary_key
47
+ # :nocov:
22
48
  @primary_key ||= resource_klass._primary_key
49
+ # :nocov:
23
50
  end
24
51
 
25
52
  def resource_klass
26
- @resource_klass ||= @parent_resource.resource_for(@class_name)
53
+ @resource_klass ||= @parent_resource.resource_klass_for(@class_name)
27
54
  end
28
55
 
29
56
  def table_name
57
+ # :nocov:
30
58
  @table_name ||= resource_klass._table_name
59
+ # :nocov:
60
+ end
61
+
62
+ def self.polymorphic_types(name)
63
+ @poly_hash ||= {}.tap do |hash|
64
+ ObjectSpace.each_object do |klass|
65
+ next unless Module === klass
66
+ if ActiveRecord::Base > klass
67
+ klass.reflect_on_all_associations(:has_many).select{|r| r.options[:as] }.each do |reflection|
68
+ (hash[reflection.options[:as]] ||= []) << klass.name.downcase
69
+ end
70
+ end
71
+ end
72
+ end
73
+ @poly_hash[name.to_sym]
74
+ end
75
+
76
+ def resource_types
77
+ if polymorphic? && belongs_to?
78
+ @polymorphic_types ||= self.class.polymorphic_types(@relation_name).collect {|t| t.pluralize}
79
+ else
80
+ [resource_klass._type.to_s.pluralize]
81
+ end
31
82
  end
32
83
 
33
84
  def type
@@ -47,17 +98,35 @@ module JSONAPI
47
98
  end
48
99
  end
49
100
 
50
- def type_for_source(source)
51
- if polymorphic?
52
- resource = source.public_send(name)
53
- resource.class._type if resource
54
- else
55
- type
101
+ def belongs_to?
102
+ # :nocov:
103
+ false
104
+ # :nocov:
105
+ end
106
+
107
+ def readonly?
108
+ @options[:readonly]
109
+ end
110
+
111
+ def exclude_links(exclude)
112
+ case exclude
113
+ when :default, "default"
114
+ @_exclude_links = [:self, :related]
115
+ when :none, "none"
116
+ @_exclude_links = []
117
+ when Array
118
+ @_exclude_links = exclude.collect {|link| link.to_sym}
119
+ else
120
+ fail "Invalid exclude_links"
56
121
  end
57
122
  end
58
123
 
59
- def belongs_to?
60
- false
124
+ def _exclude_links
125
+ @_exclude_links ||= []
126
+ end
127
+
128
+ def exclude_link?(link)
129
+ _exclude_links.include?(link.to_sym)
61
130
  end
62
131
 
63
132
  class ToOne < Relationship
@@ -68,27 +137,89 @@ module JSONAPI
68
137
  @class_name = options.fetch(:class_name, name.to_s.camelize)
69
138
  @foreign_key ||= "#{name}_id".to_sym
70
139
  @foreign_key_on = options.fetch(:foreign_key_on, :self)
140
+ if parent_resource
141
+ @inverse_relationship = options.fetch(:inverse_relationship, parent_resource._type)
142
+ end
143
+ end
144
+
145
+ def to_s
146
+ # :nocov: useful for debugging
147
+ "#{parent_resource}.#{name}(#{belongs_to? ? 'BelongsToOne' : 'ToOne'})"
148
+ # :nocov:
71
149
  end
72
150
 
73
151
  def belongs_to?
152
+ # :nocov:
74
153
  foreign_key_on == :self
154
+ # :nocov:
75
155
  end
76
156
 
77
157
  def polymorphic_type
78
158
  "#{name}_type" if polymorphic?
79
159
  end
160
+
161
+ def include_optional_linkage_data?
162
+ @always_include_optional_linkage_data || JSONAPI::configuration.always_include_to_one_linkage_data
163
+ end
164
+
165
+ def allow_include?(context = nil)
166
+ strategy = if @allow_include.nil?
167
+ JSONAPI.configuration.default_allow_include_to_one
168
+ else
169
+ @allow_include
170
+ end
171
+
172
+ if !!strategy == strategy #check for boolean
173
+ return strategy
174
+ elsif strategy.is_a?(Symbol) || strategy.is_a?(String)
175
+ parent_resource.send(strategy, context)
176
+ else
177
+ strategy.call(context)
178
+ end
179
+ end
80
180
  end
81
181
 
82
182
  class ToMany < Relationship
83
- attr_reader :reflect, :inverse_relationship
183
+ attr_reader :reflect
84
184
 
85
185
  def initialize(name, options = {})
86
186
  super
87
187
  @class_name = options.fetch(:class_name, name.to_s.camelize.singularize)
88
188
  @foreign_key ||= "#{name.to_s.singularize}_ids".to_sym
89
189
  @reflect = options.fetch(:reflect, true) == true
90
- @inverse_relationship = options.fetch(:inverse_relationship, parent_resource._type.to_s.singularize.to_sym) if parent_resource
190
+ if parent_resource
191
+ @inverse_relationship = options.fetch(:inverse_relationship, parent_resource._type.to_s.singularize.to_sym)
192
+ end
193
+ end
194
+
195
+ def to_s
196
+ # :nocov: useful for debugging
197
+ "#{parent_resource}.#{name}(ToMany)"
198
+ # :nocov:
199
+ end
200
+
201
+ def include_optional_linkage_data?
202
+ # :nocov:
203
+ @always_include_optional_linkage_data || JSONAPI::configuration.always_include_to_many_linkage_data
204
+ # :nocov:
91
205
  end
206
+
207
+ def allow_include?(context = nil)
208
+ strategy = if @allow_include.nil?
209
+ JSONAPI.configuration.default_allow_include_to_many
210
+ else
211
+ @allow_include
212
+ end
213
+
214
+ if !!strategy == strategy #check for boolean
215
+ return strategy
216
+ elsif strategy.is_a?(Symbol) || strategy.is_a?(String)
217
+ parent_resource.send(strategy, context)
218
+ else
219
+ strategy.call(context)
220
+ end
221
+ end
222
+
92
223
  end
93
224
  end
94
225
  end